一 函数栈帧的创建及销毁
1 什么是函数栈帧?
1 什么是函数?
函数就是对于一个独立功能的抽象。
2 什么是函数栈帧?
函数栈帧是函数调用时在栈区中临时开辟的一块内存空间
3 这些空间的作用是什么?
存放函数参数和函数返回值
存放临时变量
保存上下文信息(寄存器)
2 理解函数栈帧能够解决什么问题呢?
1 局部变量是如何创建的?
2 为什么局部变量不初始化内容是随机的?
3 函数调用时参数时如何传递的?传参的顺序是怎样的?
4 函数的形参和实参分别是怎样实例化的?
5 函数的返回值是如何带会的?
这五个问题都会在文章的最后意义解答
3 函数栈帧的创建和销毁解析
1 什么是栈区?
就是那些由编译器在需要的时候分配,在不需要的时候自动清除变量的存储区
2 什么是压栈?什么是出栈?
压栈:向栈区中添加一个元素
出栈:从栈区中删除一个元素
3 栈区原则 先进后出
二 认识相关寄存器和汇编指令
1 预备知识
1 寄存器的概念:寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果
重点是 1 在cpu中
2 暂时存放参与运算的数据和运算结果
2 相关寄存器
eax:通用寄存器,保留临时数据,常用于返回值
ebx:通用寄存器,保留临时数据
ebp:栈底寄存器
esp:栈顶寄存器
eip:指令寄存器,保存当前指令的下一条指令的地址
这里我们暂时只需要记住ebp栈底寄存器 和esp栈顶寄存器就好
3 相关汇编命令
mov:数据转移指令
push:数据入栈,同时esp栈顶寄存器也要发生改变
pop:数据弹出至指定位置,同时esp栈顶寄存器也要发生改变
sub:减法命令
add:加法命令
call:函数调用,1. 压入返回地址 2. 转入目标函数
jump:通过修改eip,转入目标函数,进行调用
ret:恢复返回地址,压入eip,类似pop eip命令
4 预备知识
- 每一次函数调用,都要为本次函数调用开辟空间,就是函数栈帧的空间。
- 这块空间的维护是使用了2个寄存器: esp和 ebp ebp记录的是栈底的地址esp记录的是栈顶的地址。
如下图所示:
函数调用堆栈演示代码:
#include <stdio.h> int Add(int x, int y) { int z = 0; z = x + y; return z; } int main() { int a = 3; int b = 5; int ret = 0; ret = Add(a, b); printf("%d\n", ret); return 0; }
2 准备环境
这段代码,如果我们在VS2019编译器上调试,调试进入Add函数后,我们就可以观察到函数的调用堆栈 (右击勾选【显示外部代码】),如下图:
函数调用堆栈是反馈函数调用逻辑的,那我们可以清晰的观察到, main 函数调用之前,是由 invoke_main 函数来调用main函数。
在 invoke_main 函数之前的函数调用我们就暂时不考虑了。
那我们可以确定, invoke_main 函数应该会有自己的栈帧, main 函数和 Add 函数也会维护自己的栈 帧,每个函数栈帧都有自己的 ebp和 esp来维护栈帧空间。
那接下来我们从main函数的栈帧创建开始讲解
为了让我们研究函数栈帧的过程足够清晰,不要太多干扰,我们可以关闭下面的选项,让汇编代码中排 除一些编译器附加的代码:
转到反汇编
int main() { //函数栈帧的创建 00BE1820 push ebp 00BE1821 mov ebp,esp 00BE1823 sub esp,0E4h 00BE1829 push ebx 00BE182A push esi 00BE182B push edi 00BE182C lea edi,[ebp-24h] 00BE182F mov ecx,9 00BE1834 mov eax,0CCCCCCCCh 00BE1839 rep stos dword ptr es:[edi] //main函数中的核心代码 int a = 3; 00BE183B mov dword ptr [ebp-8],3 int b = 5; 00BE1842 mov dword ptr [ebp-14h],5 int ret = 0; 00BE1849 mov dword ptr [ebp-20h],0 ret = Add(a, b); 00BE1850 mov eax,dword ptr [ebp-14h] 00BE1853 push eax 00BE1854 mov ecx,dword ptr [ebp-8] 00BE1857 push ecx 00BE1858 call 00BE10B4 00BE185D add esp,8 00BE1860 mov dword ptr [ebp-20h],eax printf("%d\n", ret); 00BE1863 mov eax,dword ptr [ebp-20h] 00BE1866 push eax 00BE1867 push 0BE7B30h 00BE186C call 00BE10D2 00BE1871 add esp,8 return 0; 00BE1874 xor eax,eax }