跳转至

调用规范

What happened in stack when a function is called?

#include <stdio.h>

int multiply(int a, int b) {
    int multiplication = a * b;
    return multiplication;
}

int main() {
    int x = 5;
    int y = 10;
    int result = multiply(x, y);
    printf("The result is: %d\n", result);
    return 0;
}

compiled with:

$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
$ gcc -m32 -g hello.c

Given that the ISA is 32bit x86:

  1. CALLER push the arguments into stack, from right to left.
  2. CALLER call the target function, the %eip is pushed into stack.
  3. CALLEE push %ebp into stack.
  4. CALLEE sub %esp to reserve space for local variables and temporary variables.
  5. CALLEE leave and ret,
    1. leave -> mov %ebp,%esp; pop %ebp;. The %ebp and %esp of CALLER is restored.
    2. ret -> pop %eip. The %eip saved is restored.
  6. CALLEE add %esp to clear the space for argument passing.
int multiply(int a, int b) {
    119d:       55                      push   %ebp
    119e:       89 e5                   mov    %esp,%ebp
    11a0:       83 ec 10                sub    $0x10,%esp
    11a3:       e8 74 00 00 00          call   121c <__x86.get_pc_thunk.ax>
    11a8:       05 30 2e 00 00          add    $0x2e30,%eax
    ...
    11ba:       c9                      leave
    11bb:       c3                      ret
}

int result = multiply(x, y);
    11e7:       ff 75 f0                push   -0x10(%ebp)
    11ea:       ff 75 ec                push   -0x14(%ebp)
    11ed:       e8 ab ff ff ff          call   119d <multiply>
    11f2:       83 c4 08                add    $0x8,%esp
    11f5:       89 45 f4                mov    %eax,-0xc(%ebp)

We notice that there is a call to __x86.get_pc_thunk.ax. The function is to transfer the contents of the %eip register to the %eax register. It is equivalent to mov %eip, %eax.
This function is used in PIC (position independent code) on x86. It loads the location of %eip into the %eax register, thereby enabling access to the internal data of the module (such as global variables). The reason is that there is no instruction in the x86 instruction set to directly read %eip.

What information does a function stack frame generally contain ?

  • Return Address: The call instruction saves. This is the memory address of the instruction in the calling code where the program should resume execution after the function completes. When the function finishes, the CPU uses this address to transfer control back to the caller.
  • Parameters and Local Variables: The stack frame allocates space for the function's parameters (values passed from the CALLER) and local variables declared within the function (values created in CALLEE). These values are stored on the stack frame and can be accessed and modified by the function during its execution.
  • Base Pointer: The base pointer (%ebp on x86 architectures) is a pointer that references the current stack frame's base address. It is useful for accessing local variables and function parameters through offsets from the base pointer.
  • Function State Information: (Not shown in the example code)Some additional information related to the function's state may be stored on the stack frame, such as callee-saved registers (registers whose values must be preserved across function calls) and other bookkeeping data.

C语言提供了稳定的ABI。C语言函数调用规范叫做cdecl,是被gcc, clang编译器默认被使用的。很多其他语言也依赖C语言的ABI和C编写的库进行交互。
还有其他的调用规范,如Pascal Call(stdcall), fastcall, linux system call...