Call/Return

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:

cdecl

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
  

Example: Triangle Numbers

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:

tri3.c

tri3.s