Fork me on Github Fork me on Gitee

C温故补缺(十五):栈帧

栈帧

概念

栈帧:也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构,每次函数的调用,都会在调用栈(call stack)上维护一个独立的栈帧(stack frame)

栈帧的内容

  • 函数的返回地址和参数

  • 临时变量:包括函数的非静态局部变量,以及编译器自动生成的其他临时变量

  • 函数调用上下文

  • 两个指针:ebp又称帧指针(frame pointer),指向当前栈帧的底部; esp,又称栈针织(stack pointer),始终指向栈顶

函数调用

函数调用过程中分:函数调用者(caller)和被调用的函数(callee)

调用者需要知道被调用函数的返回值

被调用函数需要知道传入的参数和返回的地址

步骤:

  1. 参数入栈:将参数按照调用约定依次压入系统栈

  2. 返回地址入栈:将当前代码区调用指令的下一条指令地址压入栈,供函数返回时继续执行,也就是保护现场和恢复现场

  3. 代码跳转:处理器将代码区跳转到被调用函数的入口处

  4. 栈帧调整:

  5. 将调用者的ebp压入栈,保存指向栈底ebp地址(用于恢复现场),此时esp指向新的栈顶位置

  6. 将当前栈帧切换到新栈帧(将esp值装入ebp,跟新栈底),此时ebp指向栈顶,

  7. 给新栈帧分配空间

函数返回

步骤:

  1. 保存被调用函数的返回值到eax寄存器

  2. 恢复esp同时收回局部变量空间

  3. 将上一个栈帧底部位置恢复到ebp

  4. 弹出当前元素栈顶元素,从栈中取到返回地址,并跳转到该位置,也就是再回到调用者,执行后续代码

举例说明

c代码:

int sum(int x,int y){
    int z=x+y;
    return z;
}
int main(){
    int a=1;
    int b=3;
    int c=sum(a,b);
}

汇编,且关闭编译器优化-O0

过程详解:

补充:整个过程中虽然看不到RIP,但它一直被使用,RIP每次都指向下一条将要运行的指令

每次取出一条指令时,RIP就会自动偏移,指向下一条指令,如图:

当发生函数调用时,也就是call时,callq 指令会自动将rip压入栈,并将rip指向被调用的函数,如

先RIP指向 callq f(),下一次执行就是调用函数f(),查看此时的rsp

接着执行该指令

ip跳到了f()内的第一条指令push %rbq,再查看rsp

rsp中存入了0x00401586,正是call的下一条指令的位置

且也可以查看变量在栈帧内的存储形式

将1赋值给变量b,即mov 1 -4(%bp),查看内存

就是在bp的偏移4字节处

再来看ret,ret是将之前存的RIP给出栈,经过sub分配空间然后再add释放空间,pop rbp后,rsp刚好在旧的rip处

此时执行ret,会自动执行pop rip,也就恢复了现场

也就是说:

call f的本质是:

push %rip
mov f,%rip

ret的本质是:

pop %rip
posted @   Tenerome  阅读(130)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示