栈帧与函数调用
1.什么是栈帧
在理解栈帧之前,我们需要理解什么是栈。
栈在数据结构中是一种运算受限的线性表,即我们只能对表尾进行操作,称之为栈顶,相对的,另一端称为栈底。它按照先进后出的原则存储数据,先进入的数据保存在栈底,后来的数据保存在栈顶。
在计算机系统当中,栈是拥有在数据结构当中所有属性的一块动态内存区域,用来存储函数内部的局部变量和返回地址。
而栈帧,就是在程序运行的过程当中,保存函数调用时所需要维护的信息。
栈帧
简言之,栈帧是利用EBP寄存器来访问函数内部局部变量、参数、函数的返回地址等。
栈帧结构
PUSH EBP ;函数开始(使用EBP先把已有值保存在栈中) MOV EBP ESP ;保存当前ESP值到EBP中 ;函数体 *** ;无论ESP值如何变化,EBP值不改变,可以安全访 问函数内的局部变量,参数 MOV ESP EBP ;将函数的起始地址(返回地址)返回到ESP中 POP EBP ;弹出保存在栈中的EBP值 RETN ;函数终止
利用实例来理解栈帧(逆向工程核心原理:栈帧章)
#include<stdio.h> long add(long a , long b) { long x = a , y = b; return (x+y); } int main() { long a = 1 , b = 2 ; printf(" %d \n" , add( a , b )) ; return 0; }
汇编语言如下所示
①我们先从main()函数来进行程序分析
我们需要密切关注栈的变化
当前ESP、EBP指针如图所示(如下);
栈顶指针保存19FF2C保存着main()函数执行完后的返回地址(如下);
这条指令(如下)把EBP(栈帧指针)保存在栈中(main()函数执行完毕,返回之前,该值会再次恢复)
main()函数的esp的值被保存在ebp当中(下图),当做开辟新栈之后的基址(同时作为执行完add()函数的返回地址)。从这条命令开始,ebp与esp持有相同的值,main()函数的栈帧就生成好了。
栈内ebp的指针保存着main()函数开始执行时的初始值
②设置变量
sub是减法指令,esp-8意思是在栈内开辟一个八字节的空间(long 型的a,b一个占四字节)
把ebp-4与ebp-8处各有一个四字节的内存空间,用来保存数据1和2。
执行完后栈内空间如下所示
③调用add()函数
以上五行汇编描述了调用main()函数的整个过程
函数add()接收a、b两个长整型,所以调用前先把两个参数压入栈,如下图所示
返回地址
执行call命令进入函数之前,需要把函数的返回地址压入栈。在地址40103C中执行了call,它的下一条指令的地址是401041,即执行完add()函数后程序的执行流接着执行地址401041的命令,此处即为返回地址。
执行完call命令后,栈内如图所示
④进入add()函数
生成add()函数的栈帧
栈内情况如图所示:
⑤设置add()函数的局部变量
[ebp+8]在栈内表示a的值,[ebp+c]表示b的值,[ebp-8]与[ebp-4]指向add()函数的两个参数x,y
⑥add运算
运算完成后值被储存在eax当中
⑦删除add()函数的栈帧&函数的返回值
在返回值之前,需要先删除栈帧。
mov指令用于将储存在ebp当中的main()函数的esp的值恢复到esp当中
pop指令将add()函数开始执行时储存的ebp的值弹出
这两条指令完成后,栈内情况如下
ebp指向main()函数开始执行时的初始值
esp保存函数执行完毕后的返回地址401041
⑧从栈中删除add()函数的参数
retn执行后,程序重新运行到main()函数内执行add esp+8
这条命令的作用是删除参数a,b
⑨printf()与return 0;
略
⑩删除main()函数的栈帧
执行完后指针与栈内空间如下,main函数栈帧被删除
retn指令执行后,返回到主函数执行完毕后的状态
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~