函数栈帧的创建和销毁
1.什么是函数栈帧?
C语言程序是以函数为基本单位的,会把一个独立的功能抽象出来,编写为单独的函数。
既然是函数,就需要考虑到如何调用?返回值又如何对待?参数如何传递?等等一系列的问题都和函数栈帧有关。
函数栈帧(stack frame)就是函数在调用过程中在程序的调用栈 (call stack)所开辟的空间,这些空间是用来存放: 1. 函数参数和函数返回值 2. 临时变量(保存函数的非静态的局部变量以及编译器自动生产的其他 临时变量 3.保存上下文信息(包括在函数调用前后需要保存不变的寄存器)
2.函数栈帧能解决什么问题?
理解函数栈帧的创建和销毁之后,以下问题就能够很好地理解
1.局部变量是如何创建的?
2.为什么局部变量不初始化内容是随机的?
3.函数调用时参数是如何传递的?传参的顺序是怎么样的?
4.函数的形参和实参分别是如何实例化(创建变量)的?
5.函数的返回值是如何返回到 main函数中的?
3.解析函数栈帧的创建和销毁
3.1栈的概念
在经典的计算机科学中,栈(stack)被定义为一种特殊的容器,用户可以将数据压入栈中(入栈, push),也可以将已经压入栈中的数据弹出(出栈, pop),但是必须遵守一条规则:先入栈的数据后出栈。
在计算机系统中,栈是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出,压栈操作使得栈增大,弹出操作使得栈减小。
在经典操作系统中,栈总是向下增长(由低地址向高地址)的。
在常见的x86或者x64环境下,栈顶由 esp寄存器进行定位。
3.2掌握相关寄存器和汇编指令
相关寄存器
eax:通用寄存器,保留临时数据,常用于返回值 ebx:通用寄存器,保留临时数 ebp:栈底寄存器 esp:栈顶寄存器 eip:指令寄存器,保存当前指令的下一条指令的地址
汇编指令
mov:数据转移指令 push:数据入栈,同时esp栈顶寄存器也要发生变化 pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生变化 sub:减法命令 add:加法命令 call:函数调用 1.压入返回地址;2.转入目标函数 jump:通过修改eip,转入目标函数,进行调用 ret:恢复返回地址,压入eip,类似pop eip命令
3.3解析函数栈帧的创建和销毁
3.3.1前言
首先需要一些预备知识才能帮助我们理解函数栈帧的创建和销毁
1.每一次的函数调用,都要为本次函数调用开辟空间,就是开辟函数栈帧的空间
2.这块空间的维护是使用了2个寄存器: esp和 ebp, ebp记录的是栈底的地址, esp记录的是栈顶的地址。
3.函数栈帧的创建和销毁过程,在不同的编译器上实现的方法大同小异。
3.3.2函数的调用堆栈
#include<stdio.h> int Add(int x, int y) { int z = 0; z = x + y; return z; } int main() { int a = 3; int b = 2; int ret = 0; ret = Add(a, b); printf("%d\n", ret); return 0; }
在vs2022编译器下,这段代码,调试进入Add函数后,就可以观察函数的调用堆栈,如下图
函数调用堆栈是反应函数调用逻辑的,可以清楚地观察到,先是调用mian函数,然后通过mian函数来调用Add函数。
接下来就从main函数的栈帧创建开始分析
3.3.3转到反汇编
调试到main函数开始执行的第一行,右击鼠标转到反汇编。
int main() { //函数栈帧的创建 009118B0 push ebp 009118B1 mov ebp,esp 009118B3 sub esp,0E4h 009118B9 push ebx 009118BA push esi 009118BB push edi 009118BC lea edi,[ebp-24h] 009118BF mov ecx,9 009118C4 mov eax,0CCCCCCCCh 009118C9 rep stos dword ptr es:[edi] 009118CB mov ecx,offset _352C9E5E_test@c (091C008h) 009118D0 call @__CheckForDebuggerJustMyCode@4 (091131Bh) //main函数中的核心代码 int a = 3; 00EE18D5 mov dword ptr [ebp-8],3 int b = 2; 00EE18DC mov dword ptr [ebp-14h],2 int ret = 0; 00EE18E3 mov dword ptr [ebp-20h],0 ret = Add(a, b); 00EE18EA mov eax,dword ptr [ebp-14h] 00EE18ED push eax 00EE18EE mov ecx,dword ptr [ebp-8] 00EE18F1 push ecx 00EE18F2 call 00EE13B6 00EE18F7 add esp,8 00EE18FA mov dword ptr [ebp-20h],eax printf("%d\n", ret); 009118FD mov eax,dword ptr [ebp-20h] 00911900 push eax 00911901 push offset string "%d\n" (0917B30h) 00911906 call _printf (09110D2h) 0091190B add esp,8 return 0; 0091190E xor eax,eax } 00911910 pop edi 00911911 pop esi 00911912 pop ebx 00911913 add esp,0E4h 00911919 cmp ebp,esp 0091191B call __RTC_CheckEsp (0911244h) 00911920 mov esp,ebp 00911922 pop ebp 00911923 ret