All programming languages provide a mechanism for passing arguments and control to functions and for returning control and a value from functions.
Here's a simple example in C: caller.c.
int u, v = 10, w = 20, x = 30;
int task2(int a, int b, int c) {
return a + b + c;
}
void task1() {
u = task2(v, w, x);
}
int main() {
task1();
printf("u = %d\n", u); //
should print 60
return 0;
}
In this example main calls task1, task1 calls task2, task2 returns control and a value to task1, task1 returns control to main, then main calls printf.
In most assembly languages arguments are passed via the stack. The caller's state is preserved on the stack, and local variables are allocated on the stack. The stack and state are restored at the end of the call either by the caller or the called function.
Every function call creates a stack frame where the function's parameters and local variables are stored. The stack frame also stores the address of the caller's stack frame as well as the address of the next instruction in the caller to be executed when control is returned to it.
The address of the current stack frame is held in the ebp register. Addresses of all arguments and locals are computed as offsets of ebp.
The esp register holds the address of the top of the stack. (Note: this is the top item on the stack, not the next available space on the stack. also note that the stack grows toward higher addresses.)
Here's a diagram of the stack at the point that task2 has been called:
On the x86 C uses a caller cleanup convention called cdecl. In this convention the caller pushes arguments from left to right onto the stack, then executes call.
Take a look at task1 calling task2 in caller.s:
_task1:
push ebp ;
save main's ebp = base of main's stack frame
mov ebp,
esp ; ebp = base of task1's
stack frame
sub esp,
12 ; allocate room for 3 4-byte arguments on stack
mov eax,
DWORD PTR _x ; load x onto
stack
mov DWORD
PTR [esp+8], eax
mov eax,
DWORD PTR _w ; load w onto
stack
mov DWORD
PTR [esp+4], eax
mov eax,
DWORD PTR _v ; load v onto
stack
mov DWORD
PTR [esp], eax
call _task2 ; call task2
mov DWORD
PTR _u, eax ; move return
value from eax into u
leave ;
tear down stack frame
ret ;
return control to main
Note: the leave instruction is equivalent to:
mov esp, ebp ; tear down stack frame
pop ebp ; ebp = base of
caller's stack frame
The call instruction does two things at once:
push eip ; save
caller's instruction pointer
mov eip, _task2 ; eip = first
instruction of callee
Let's look at task2:
_task2:
push ebp ; save caller's ebp
mov ebp,
esp ; ebp = base of
task2's stack frame
mov eax,
DWORD PTR [ebp+12] ; eax = u
add eax,
DWORD PTR [ebp+8] ; eax = eax + v
add eax,
DWORD PTR [ebp+16] ; eax = eax + x
pop ebp ; resotore caller's ebp
ret ;
return control to caller
Note that ret is equivalent to:
pop eip
Recall the nth triangle number is the number of bricks needed to build a staircase of height n:
Clearly the answer is 1 + 2 + 3 + ... + n.
We can compute the nth triangle number using recursion.
Here is our solution: