As we all know, function is a important concept in programming design. At this moment, I even
don't know what kind of programming language can working without function. ( maybe i am new).
maybe some special language can ? But this concept is very necessary indeed. Now, the question
is : How can we realize this concept in assemble language/machine code ?
1. Overview
Three issues will be explain by this example..
a). How can we call a function?
b). How can we build a stack frame for local variable ?
c). How can we pass parameters between caller and callee ?
#include/* * This is a empty function, it does nothing. we build it for show how can we call a function. */ void call() { } /* * Explain how can we build a stack frame. */ void frame() { int b; } /* * Explain something about pass parameters and return result. */ int parameters( int a, int b, int c) { int sum; sum = a + b + c; return sum; } int main() { int ret; call( ); frame( ); ret = parameters(1,2,3); return 0; }For discuss the issues, we need to translate it to a low level language, assemble language. In this example, I use a linux compiler--gcc. It will help us to get a assemble code. (Actually, there have a problem in here--different compiler may be use different convention, even use different Application Binary Interface, but they still have some common features.) The corresponding new code is :
...... call: pushl %ebp movl %esp, %ebp popl %ebp ret ...... frame: pushl %ebp movl %esp, %ebp subl $16, %esp leave ret ...... parameters: pushl %ebp movl %esp, %ebp subl $16, %esp movl 12(%ebp), %eax addl 8(%ebp), %eax addl 16(%ebp), %eax movl %eax, -4(%ebp) movl -4(%ebp), %eax leave ret ...... main: leal 4(%esp), %ecx andl $ -16, %esp pushl -4(%ecx) pushl %ebp movl %esp, %ebp pushl %ecx subl $28, %esp call call call frame movl $3, 8(%esp) movl $2, 4(%esp) movl $1, (%esp) call parameters movl %eax, -8(%ebp) movl $0, %eax addl $28, %esp popl %ecx popl %ebp leal -4(%ecx), %esp ret ......(Be careful, Here is AT&T syntax.)
2. How can we call a function?
From the view of machine, call a function is equal to change the instruction stream. It
seems like simple. Actually, there are another problem, How can we return to the instruction
stream of the caller ? A valid way is save the instruction pointer before jump to the callee.
Now, Let us see this example:int main() { ... call( ); ... } void call() { }This is simple function call, how can we realize it by assemble language ? examine the
corresponding code.main: ...... call call ......In @main function, it call a function @call by a assemble instruction--call. This instruction
does two things needed to be done. one, save the current value of register @IP in stack.
Two, revise the value of @IP to the address of caller.
pushl %IP;
movl call, %IP;call: .... retwhen we complete this subroutine, the next step is to return to the previous instruction
stream. The current status is
.... <-- %EBP for caller
....
0xeeee0000 <-- return address
<-- %ESP for caller
So, we just need to pop the data from stack.
pop %IP;
3. How can we build a stack frame for local variable ?
For local variable, there is a important feature that we need--reentrant. we want to local variable
can be independent in every function call, even call a recursive function. So we dynamically create
independent memory space for every function call, this is called --stack frame. Now , examine the code.int main() { ... frame( ); ... } void frame() { int b; }before we call @frame, all of thing is same with the example above. The curren