#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清零