文章目录
函数栈帧的创建和销毁
在此之前先强调一句话:函数栈帧的创建和销毁的过程,在不同编译器下他的创建和销毁过程是略有差异的,具体差异取决于编译器。但大体是一致的。
寄存器
什么是寄存器?
寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果以及一些CPU运行需要的信息。
寄存器的名称和功能
名称 | 功能 |
---|---|
eax | 通常用来执行加法,函数调用的返回值一般也放在这里面 |
ebx | 数据存取 |
ecx | 通常用来作为计数器,用于循环操作,重复的字符存储操作或数字统计 |
edx | 读写I/O端口时,edx用来存放端口号 |
esp | 栈顶指针,指向栈的顶部 |
ebp | 栈底指针,指向栈的底部,通常用 ebp+偏移量 的形式来定位函数存放在栈中的局部变量 |
esi | 字符串操作时,用于存放数据源的地址 |
edi | 字符串操作时,用于存放目的地址的,和esi两个经常搭配一起使用,执行字符串的复制等操作 |
ebp和esp这两个寄存器存放的是地址,用来维护函数栈帧
每一个函数调用,都要在栈区创建一块空间
汇编指令
名称 | 功能 |
---|---|
push | 把字压入堆栈,给栈顶放一个元素。在32位平台上,esp 每次减少4字节。 |
mov | 将一个数据从源地址传送到目标地址,源操作地址的内容不变 |
sub | 减法 |
lea | “load effective address”的缩写,用来将一个内存地址直接赋给目的操作数。 |
pop | 出栈,从栈顶删除一个元素。在32位平台上,esp 每次增加4字节。 |
rep | 缩写 repeat, 重复前缀指令,能够引发其后字符串指令被重复。 |
cmp | 比较指令 |
jmp | 无条件跳转 |
add | 两个运算子相加,结果写入第一个运算子 |
ret | 终止当前函数的执行,将运行权交还给上层函数 |
stos | 串存储指令,英文缩写 store string。 |
call | 调用函数 |
数栈帧的创建销毁过程
为了更好理解函数栈帧的创建和销毁过程,下面简单举个例子:
#include <stdio.h>
int Add(int x,int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 3;
int b = 4;
int c = 0;
c = Add(a, b);
printf("%d", c);
return 0;
}
按下F10,在调试->窗口打开调用堆栈,这里比较推荐低版本的编译器,版本越低观察越清楚。
这儿我们发现mian函数被调用起来了
main函数是被谁调用了呢?
在VS2022中,main函数是被 scrt_common_main_seh 函数调用,而 scrt_common_main_seh 函数是被 invoke_main 函数调用。
![image-20230118174832360](https://i-blog.csdnimg.cn/blog_migrate/5367e46f0ad333b7d46a81d41c63b7c9.png)
为了更详细观察我们转到反汇编,按下F10,右击鼠标转到反汇编
![image-20230120100426826](https://i-blog.csdnimg.cn/blog_migrate/973f1263fa6bc61ed7408529bef29d96.png)
同时也打开监视窗口,在调试状态下执行以下操作
栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)。
main函数栈帧开辟
word,是2个字节,dword是双字的意思,也就是4字节
main函数栈帧开辟动态演示
最终开辟静态图
main函数中变量创建
在监视窗口查找以下信息:
根据上图,我们知道了0Ah,14h和0的位置,执行mov命令便会出现如下情况,仔细观察三个位置
在main函数创建变量最终效果图
main函数传参过程
由main函数中变量已知a,b
执行过程
main函数中的变量创建好且传参之后,调用Add 函数,调用之前要先给Add函数开辟栈帧,过程类似于给main函数创建栈帧。
Add函数栈帧开辟
Add函数栈帧开辟动态演示
最终开辟静态图
Add函数中变量创建及运行
在监视窗口查看以下信息:
Add函数销毁过程
返回函数栈帧
返回到call指令的下一条指令地址