From Schmid.wiki
Jump to: navigation, search

Registers

%eip instruction pointer
%esp top of stack pointer
%ebp base pointer

C Calling Convention

How are functions called in C?

The callee function should have its own %ebp, thus, to restore the state after the function call, the caller's %ebp must be saved on the stack.

Caller:

  • push all function parameters onto stack (in reverse order)
  • issue call function - equivalent to:
pushl 2(%eip) # save return address (next instruction)
jmp function

Callee:

  • save %ebp containing the base pointer of the caller to the stack (pushl %ebp)
  • copy stack pointer to %ebp - now we can reference the parameters and return address relatively to %ebp
  • reserve space for local variables by moving stack pointer up (e.g. subl $8, %esp reserves space for 2 words)
  • perform computations using parameters (positive offsets to %ebp) and local variables (negative offsets to %ebp)
  • store return value in %eax
  • restore caller stack frame (leave) - equivalent to:
movl %ebp, %esp # copy base pointer to stack pointer again like before the call
                # - this has the effect of de-allocating all local variables
popl %ebp       # restore old base pointer from stack
  • return to caller (ret) - equivalent to:
popl %eip

In Code

    pushl $10        # push argument 1 to stack
    pushl $20        # push argument 2 to stack
    pushl 2(%eip)    # save return address (next instruction)
    jmp function
    ...

function:
    pushl %ebp       # save caller base pointer
    movl %esp, %ebp  # define callee base pointer
    subl $4, %esp    # allocate space for local variables
                     # (1 word, in this example)
    ...
    movl %ebp, %esp  # de-allocate local variables
    popl %ebp        # restore caller base pointer from stack
    popl %eip        # ret

Or the short version:

    pushl $10        # push argument 1 to stack
    pushl $20        # push argument 2 to stack
    call function
function:
    enter 4
    ...
    leave
    ret

References

gcc Example

A simple function call:

int addNumbers(int x, int y) {
    int z = x+y;              
    return z;                 
}                             
int main(void) {              
    return addNumbers(30, 50);
}                             

Compiles to:

        ...
addNumbers:
        pushl   %ebp            # save caller base pointer
        movl    %esp, %ebp      # use current stack pointer as base pointer
                                #  for addNumbers
        subl    $4, %esp        # reserve one word for z

        movl    12(%ebp), %eax  # copy y to %eax (50)
        addl    8(%ebp), %eax   # add x to %eax (50+30=80)
        movl    %eax, -4(%ebp)  # copy %eax to z
        movl    -4(%ebp), %eax  # copy z to %eax (redundant, z didn't need to
                                #                 be introduced at all, but
                                #                 who cares :)

        leave                   # restore caller stack frame
        ret                     # return to caller
        ...
main:
        ...
        subl    $8, %esp        # reserve space in stack for 2 arguments
        movl    $50, 4(%esp)    # push 50 to one below top of stack (y)
        movl    $30, (%esp)     # push 30 to top of stack (x)
        call    addNumbers      # call function
        ...

addNumbers memory layout after subl $4, %esp

z                %ebp - 4 = %esp
old base pointer %ebp
return address   %ebp + 4
x                %ebp + 8
y                %ebp + 12