函数栈帧的创建

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


猜你喜欢

转载自blog.csdn.net/sx2448826571/article/details/79412606
今日推荐