想要了解函数栈帧,那么我们首先要有寄存器的概念:
寄存器是集成在CPU上的,同时它也是独立的,一直存在。
函数栈帧创建上就会使用两个重要的寄存器,和一些其它的寄存器:
ebp :栈底指针
esp:栈顶指针
上面两个重要的寄存器,都是用来存放地址,并对函数栈帧进行维护。
其它的寄存器:eax, ebx, ecx, edx等,这些是汇编语言中CPU上的通用寄存器名称,是32位的寄存器。当然它们也都有自己独立的作用,有兴趣的可以下去了解。
我们都知道在调用函数时(main函数也是被调用的),计算机就要为函数开辟一块空间,而这块空间是在栈上开辟的。(栈的使用规则:先使用高地址,后使用低地址。)
我们把为这个函数(比如main函数)开辟的空间叫做——该函数的函数栈帧。
下面的代码可以简单的看到函数栈帧创建的过程:
#include<stdio.h>
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int c = 0;
c = Add(a, b);
printf("%d\n", c);
return 0;
}
当上面这个函数运行时,首先要为 main 函数在栈上开辟一块空间,然后使 esp 和 ebp 这两个寄存器分别指向这块空间的顶端和底端,为 main 函数的函数栈帧进行维护。
怎么样为 main 函数创建函数栈帧 ,又是怎样为后面的 Add 函数创建函数栈帧的呢?
往下走!!!
打开我们的编译器(这里使用的是vs2013,尽量使用低版本的编译器,方便观察函数栈帧创建的过程),进入调试,并打开这段 c 语言的汇编代码:
这时,我们可以观察到,上面在 main 函数里面的汇编代码,前面有下图这样一段代码:
这是由于 main 函数也是由别人调用的,在调用 main 函数之前,就已经为调用 main 函数的那个函数开辟了空间,并且这时 main 函数的空间还没有开辟出来,esp 和 ebp 更没有对它进行维护。
如下图:
接着 main 函数前端的那段代码开始执行。
push —— 压栈操作。
push ebp —— 把 ebp 放到现在维护空间的栈顶上面。(这时,esp指针就会向上移动)
mov ebp,esp —— 把 esp 的值给到 ebp 。(就是使 ebp 和 esp 同时指向了现在的栈顶)
sub esp,0E4h —— 使 esp 减去 0E4h(16进制数,使栈顶指针往上移动 0E4h)
这时,esp 和 ebp 维护的空间就变了,变成了 main 函数的函数栈帧(为main函数开辟的空间)
然后这三条指令:
就是 main 函数的栈顶,压上三个通用寄存器。
lea edi,[ebp+FFFFFF1Ch] —— 这条指令就等同于 :lea edi,[ebp - 0E4h](在反汇编中右击,把“显示符号”勾上)
把 ebp 指向的地址减去0E4h(就是把压三个通用寄存器之前 esp-0E4h 赋值给edi)
如图:
上面这条指令与下面的三条指令:
意思:把从 edi 开始,往下 39h 次的 dword(4)个字节的内容更改为 0CCCCCCCCh(就是把main函数的空间里面都存放 0CCCCCCCCh)
接下来就要正式执行 main 函数里面的内容了!!!
1.这段代码就是在给变量a , b, c开辟空间,并初始化赋值。(看到这儿,我们能看到在创建变量时不初始化,那么里面放的数据完全就是电脑随机的,这是个很可怕的现象!)
把 0Ah(10)赋值到 ebp-8 这块空间,把 14h(20)赋值到 ebp-14h 这块空间,把 0 赋值到 ebp-20h 这块空间。
2.接下来就是给 Add 函数传参,然后给它创建函数栈帧了
先把 ebp - 14h 这块空间的值赋值到 eax 中(就是b的值20)
在对 eax 压栈,把 eax 放到栈顶上。
同理:把 ebp - 8 这块空间的值赋值到 ecx 中(a的值10)
在对 ecx 压栈,把 ecx 放到栈顶上。
这里注意,传参是从后往前传(先传b,后传a)
3.执行到这儿,再往下就是 call (调用函数),正式进入 Add 函数。
4.按一下 F11 ,就会跳到:
这时我们会发现 main 函数的栈顶上压了一个 00671450(每个人每次运行都可能不一样),而这个地址,恰巧就是 call 指令下面那条指令的地址,这是为了从 Add 函数出来时,好跳回这里。
5.再按一下 F11 就会出现以下熟悉的画面:
与创建 main 函数的函数栈帧的步骤大同小异,先准备工作,然后正式执行。
6.接着就出现在了这里:
下面可以看到 eax 确实把返回值带回了变量 c 中。
后面的指令与在释放 Add 函数时的步骤相差不大,有兴趣的可以自己尝试一下!