/*
* 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:
....
ret
when 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