函数栈帧创建和销毁

前言

本次实验室主要是在vs2013编译器下进行的。计算机内部存在着各种各样的寄存器,例如eax ebx ecx edx以及ebp esp,其中ebp和esp ,这两个寄存器存放的是函数开辟栈区域的地址,这两个地址是用来维护函数栈帧的。ebp指向的是函数栈帧的底部,esp指向的是栈帧的顶部。每一个函数调用,都要在栈区创建一份空间,然后esp和ebp就会维护这段空间的地址,直到这个函数调用完成开始返回,esp和ebp回到main函数。函数的入口是main函数,main函数也是被其他函数所调用的,调用关系如下:

函数栈帧的创建 

函数栈帧的创建主要是分为以下几个部分:main函数执行前的准备操作,执行main函数,调用函数并返回,执行main函数。首先需要知道这一系列都是栈空间上执行的,栈是从高地址往低地址增长的过程

main函数执行前的准备操作

函数代码如下:

int Add(int a, int b)
{
	int z = 0;
	z = a + b;
	return z;

}
int main() {
	int a = 10;
	int b = 20;
	int c = 0;
	c = Add(a, b);
	printf("%d\n", c);
	return 0;
}

第一阶段的汇编代码如下:

00D01960  push        ebp  
00D01961  mov         ebp,esp  
00D01963  sub         esp,0E4h  
00D01969  push        ebx  
00D0196A  push        esi  
00D0196B  push        edi  
00D0196C  lea         edi,[ebp-0E4h]  
00D0196F  mov         ecx,39h  
00D01974  mov         eax,0CCCCCCCCh  
00D01979  rep stos    dword ptr es:[edi]  
    14: 	int a = 10;

第一阶段主要是main函数栈帧的开辟,首先esp和ebp从_tmainCRTStartup的栈帧向下增长,记录下该栈帧ebp的值,此时在push的时候esp同样会向上挪动,紧接着将esp此时的值赋给ebp作为main函数的栈底,此时esp进行sub操作,在一个新的位置作为栈顶,此时esp到ebp之间的空间就是此时为mian函数开辟的空间,然后压入三个寄存器ebx,esi,edi,如下图所示:

紧接着lea(load effective address)edi,[ebp-0E4h],主要是为了把main函数栈帧的最开始的ebp的值放入edi寄存中,紧接着接下来的三句汇编指令主要是对这段main函数栈帧的空间进行初始化,全部初始化成0CCCCCCCCh。整个完整的过程如下图所示:

 调用函数

    14: 	int a = 10;
00D01985  mov         dword ptr [ebp-8],0Ah  
    15: 	int b = 20;
00D0198C  mov         dword ptr [ebp-14h],14h  
    16: 	int c = 0;
00D01993  mov         dword ptr [ebp-20h],0  
    17: 	c = Add(a, b);
00D0199A  mov         eax,dword ptr [ebp-14h]  
00D0199D  push        eax  
00D0199E  mov         ecx,dword ptr [ebp-8]  
00D019A1  push        ecx  
00D019A2  call        00D011C2  
00D019A7  add         esp,8  
00D019AA  mov         dword ptr [ebp-20h],eax 

首先对变量进行初始化,在ebp-8的位置,将值置为10,ebp-20的位置置为20,ebp-32的位置将值置为0,分别对应了a,b,c三个变量:

紧接着调用函数,对于形参而言,首先将变量从右向左进行拷贝,分别拷贝到eax和ecx寄存上,再将这两个寄存器压栈 。紧接着call函数,此时会进行地址的跳转,但是此时会将call语句的下一条语句的地址压入栈顶,为了返回的时候能够重新回到这里。

执行该函数的时候,跟main函数类似,首先需要为他开辟一段栈帧空间,并且进行初始化成CCCCCCCCh。

00D01780  push        ebp  
00D01781  mov         ebp,esp  
00D01783  sub         esp,0CCh  
00D01789  push        ebx  
00D0178A  push        esi  
00D0178B  push        edi  
00D0178C  lea         edi,[ebp-0CCh]  
00D0178F  mov         ecx,33h  
00D01794  mov         eax,0CCCCCCCCh  
00D01799  rep stos    dword ptr es:[edi] 

 在初始化完成之后,开始调用形参进行计算,将参数的值从eax和ecx中取出来,并且赋值给z,最后返回的时候将z的值拷贝给eax寄存器,至此函数调用结束。

     8: 	int z = 0;
00D017A5  mov         dword ptr [ebp-8],0  
     9: 	z = a + b;
00D017AC  mov         eax,dword ptr [ebp+8]  
00D017AF  add         eax,dword ptr [ebp+0Ch]  
00D017B2  mov         dword ptr [ebp-8],eax  
    10: 	return z;
00D017B5  mov         eax,dword ptr [ebp-8]  

 返回main函数

在执行完函数之后,紧接着开始返回。

00D017B8  pop         edi  
00D017B9  pop         esi  
00D017BA  pop         ebx  
00D017C8  mov         esp,ebp  
00D017CA  pop         ebp  
00D017CB  ret 




00D019A7  add         esp,8  
00D019AA  mov         dword ptr [ebp-20h],eax  

把空间回收,把ebp的值给esp,然后弹出栈顶元素并且赋值给ebp,也就是main函数的栈底元素的值,就回到了main函数的栈帧空间,此时esp指向的是调用函数语句的下一条语句的地址,ret指令就是执行栈顶元素的地址的那一条语句。紧接着add esp 8将esp指向了edi的位置,此时形参就被销毁。然后把eax的值交给变量c。

main函数栈帧销毁跟add函数的销毁一样。 

总结

局部变量的创建:为函数分配好栈帧空间,对栈帧空间进行初始化,然后给局部变量分配空间初始化,所以初始化局部变量的值是随机值,是栈帧初始化的时候的随机值。函数的传参通过还没调用函数的时候,从右向左进行压栈,压入到eax和ecx寄存器中,紧接着给函数建立栈帧,然后通过偏移量找到对应的参数,所以形参只是实参的一份临时拷贝。函数的返回是:在调用函数的时候,就已经把call指令的下一条指令的地址压入栈中,然后把调用该函数的上一个函数的栈帧的ebp值也压入栈帧中,等函数返回的时候,弹出ebp,就可以找到上一个函数的ebp,然后指针往下走,因为记住了call下一条指令的函数地址,所以返回到的时候,就可以接着call的下一条指令接着运行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值