✏️

Stack Frame

Tags

函数调用栈帧结构

notion image
当前栈帧从栈顶 到 栈底如下构成
  • 创建的参数表:要调用的函数建立的参数
  • 局部变量:即在函数内部声明的变量(如果有)
  • 保存的寄存器的上下文(如果有),当被调用函数返回之前,为调用者函数恢复原来的寄存器中的数据,如果当前架构有足够的空闲物理寄存器可供使用,一些寄存器信息可能不需要压入栈。
  • 旧的帧指针,即前一栈帧的ebp指针,它指向前一帧的栈底。
调用者函数的栈帧
  • 返回地址:caller内部执行到call指令时,会将call指令所在行的下一条指令的内存地址压入栈,当被调用者函数内部执行到ret指令后,从栈中弹出返回地址,以便调用者函数回到该地址对应的指令继续执行余下的指令。
 

参数入栈

取决于调用规定,一般都是从右往左
以C标准约定(Windows API)的 __cdecl 为例
// 函数调用:foo(1, 2) 的汇编大致为: push 2 // 先压第二个参数 push 1 // 再压第一个参数 call foo add esp, 8 // 调用者平衡栈
 

函数返回值传递方式

根据返回值的大小变化(此处不考虑返回引用的情况)
在x86环境下:
  • 基本类型(int/指针等,<=4字节):通过EAX寄存器返回
    • 为什么是4字节?x86环境的EAX寄存器只能装4字节数据
  • 较大类型(如结构体,>4字节但不太大):
    • caller预先在上分配一块存储返回值的空间
    • 将这块空间的地址隐式作为第一个参数传递到callee的栈帧
    • 被调函数将数据拷贝到该地址,最终EAX寄存器返回这个地址
// 返回结构体的实际处理(编译器隐式转换) struct Point { int x,y; }; Point foo() { ... } // 实际相当于: void foo(Point* hidden_return_val) { ... }
  • 超大类型:可能通过内存堆分配或特殊ABI处理(略)
 
Reference: