Bootstrap

简要说明———函数栈帧!

想要了解函数栈帧,那么我们首先要有寄存器的概念:

寄存器是集成在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 函数时的步骤相差不大,有兴趣的可以自己尝试一下!

;