#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
int Add(int x,int y){
int z = 0;
z = x + y;
return z;
}
int main(){
int a = 10;
int b = 20;
int ret = 0;
ret = Add(a, b);
printf("%d\n", ret);
system("pause");
return 0;
}
我们来用上面的一段代码来理解函数栈帧的创建--------在此之前呢, 我们来看一下main函数在调用之前的一些环境
栈空间的使用是从高地址向低地址使用的
esp表示一个指针:该指针永远指向栈空间的栈顶。
ebp表示一个指针:该指针永远指向栈空间的栈底。
在调用main函数之前,会先调用mainSRTStartup()函数,也就是说,是mainSRTStartup()函数调用了main函数
并且此时的栈空间的使用情况如下:
我们的反汇编代码如下:
上述可见在main函数执行的时候并没有直接创建变量:
push ebp
move ebp esp
sub esp 0E4h
01131419 push ebx
0113141A push esi
0113141B push edi
0113141C lea edi,[ebp-0E4h] -- 把ebp-0E4h地址放到edi中
01131422 mov ecx,39h --将39h(十六进制数字)放到ecx中
01131427 mov eax,0CCCCCCCCh
0113142C rep stos dword ptr es:[edi]
从edi这个地址处向上初始化,初始化的内容为0cccccccch
接下来开始执行main函数体
int a = 10;
0113142E mov dword ptr [a],0Ah
int b = 20;
01131435 mov dword ptr [b],14h
int ret = 0;
0113143C mov dword ptr [ret],0
ret = Add(a, b);
01131443 mov eax,dword ptr [b]
01131446 push eax
01131447 mov ecx,dword ptr [a]
0113144A push ecx
0113144B call _Add (011310E6h)
01131450 add esp,8
01131453 mov dword ptr [ret],eax
printf("%d\n", ret);
mov dword[ebp-4],10—>将10存入[ebp-4]所指向的内存空间内
mov dword[ebp-8],20—>将20存入[ebp-8]所指向的内存空间内
mov dword[ebp-12],0—>将0存入[ebp-12]所指向的内存空间内
•调用Add()函数
mov eax,dword ptr[ebp-8]—>将[ebp-8]中放的内容存入eax中(寄存器),此时[ebp-8]中的内容为20
push eax—>在栈顶开辟新的空间(4个字节)来存放20
mov ecx,dword ptr[ebp-4]—>将[ebp-4]中放的内容存入ecx中(寄存器),此时[ebp-4]中的内容为10
push ecx—>在栈顶开辟新的空间(4个字节)来存放10
call @ILT+5(_Add)(013B11EFh)—>函数调用
从013B1B04这个位置直接跳转到013B11EFh所指向的位置(在操作系统中,此过程被称为“现场保护”)此时main()函数并没有结束,只是停在了013B1B04这个位置,等待Add()函数执行完毕之后,main()函数会从013B1B04这个位置继续向下执行。此时在栈顶又会开辟新的空间(4字节),用来存放013B1B04这个地址。
相应的esp会向上走4个字节
jmp Add(013B11EFh)—>跳入Add()函数的内部
开始执行函数语句之前,Add()函数做了与main()函数一样的工作。
底部的ebp为main()函数的栈底地址,是因为当一个函数被调用完毕时必须回到上一个函数中去,所以就要保存上一个函数的栈底地址。
然后开始执行Add函数体语句
int Add(int x,int y){
003B13C6 add byte ptr [eax],al
003B13C8 add byte ptr [ebx+56h],dl
003B13CB push edi
003B13CC lea edi,[ebp-0CCh]
003B13D2 mov ecx,33h
003B13D7 mov eax,0CCCCCCCCh
003B13DC rep stos dword ptr es:[edi]
int z = 0;
003B13DE mov dword ptr [z],0
z = x + y;
003B13E5 mov eax,dword ptr [x]
003B13E8 add eax,dword ptr [y]
003B13EB mov dword ptr [z],eax
return z;
003B13EE mov eax,dword ptr [z]
}
mov dword ptr[ebp-4],0—>将0存入[ebp-4]的位置
mov eax,dword ptr[ebp+8]—>将 ebp+8中的内容存入eax(eax此时存储的是b的值20)中
add eax,dword ptr[ebp+0Ch]—> 将a与b的值相加并存放在eax中
mov dword ptr[ebp-4],eax—>将此刻eax的值(8)放到[ebp-4]的位置里去
mov eax,dword ptr[ebp-4]—>又把[ebp-4]中的值放到eax中去,因为当函数调用完成时,所使用的内存会返还给电脑,但是寄存器不会。
pop edi
pop esi
pop ebx
相应的esp下移
mov esp,ebp—>把ebp的值赋给给esp(令esp返回)。
pop ebp—>出栈(令ebp指向的位置回到上一个函数的栈底),此时esp也会因为ebp的出栈而返回四个字节。
即为图中紫色ebp所指向的位置。
函数执行到这一步时,图中紫色esp向上的空间(即为为Add()函数所开辟的空间)已经返还给了操作系统。
ret —>直接跳回call指令的下一条指令(是由紫色esp向下四个字节所保存的地址(013B1B04)找到call指令的下一条指令),此时esp返回四个字节,保存地址(013B1B04)的四个字节也被返还给操作系统。此时的esp和ebp所指向的位置如图所示:
add esp,8—>给esp加8,让esp返回8个字节
mov dword ptr[ret],eax—>将eax中的值(30)保存到ret所在的4个字节中去。
然后将eax清零