函数栈帧的创建和销毁

 

目录

寄存器介绍

栈帧的创建

          初始化 

函数临时变量的创建

函数的调用 

形参的传递 

返回值 

 函数栈帧的销毁


本篇文章采用vs2022,在x86环境下运行。

寄存器介绍:

首先介绍一下寄存器:eax,ebx,ecx,edx,esp,ebp。

其中esp成为栈顶指针,ebp为栈底指针,他们共同维护一块栈帧空间

而eax,ebx,ecx,edx则是保存变量

 下面我们先讲下大概:

首先我们要知道每个函数再被调用时都会在栈区开辟一段空间。

查看调用堆栈,我们发现,main函数也被其他函数调用。 

下面具体展示一下main函数的栈帧时如何创建的。

main函数在被调用前,调用main函数的exeinvoke_main()函数栈帧已经创建完毕,由ebp,esp共同维护。

接着我们转到反汇编。

002618B0  push        ebp  
002618B1  mov         ebp,esp  
002618B3  sub         esp,0E4h  
002618B9  push        ebx  
002618BA  push        esi  
002618BB  push        edi  
002618BC  lea         edi,[ebp-24h]  
002618BF  mov         ecx,9  
002618C4  mov         eax,0CCCCCCCCh  
002618C9  rep stos    dword ptr es:[edi]  
002618CB  mov         ecx,26C008h  
002618D0  call        0026131B  
	int x = 10, y = 20;
002618D5  mov         dword ptr [ebp-8],0Ah  
002618DC  mov         dword ptr [ebp-14h],14h  
	int c = 0;
002618E3  mov         dword ptr [ebp-20h],0  
	 c = add(x, y);
002618EA  mov         eax,dword ptr [ebp-14h]  
002618ED  push        eax  
002618EE  mov         ecx,dword ptr [ebp-8]  
002618F1  push        ecx  
002618F2  call        00261023  
002618F7  add         esp,8  
002618FA  mov         dword ptr [ebp-20h],eax 

 栈帧的创建

001218B0  push        ebp  //插入ebp,压栈
001218B1  mov         ebp,esp  //把esp的值给ebp
001218B3  sub         esp,0E4h  //esp-oe4h

初始化 

001218BC  lea         edi,[ebp-24h]  //把[ebp-24h] 的值给edi
001218BF  mov         ecx,9  
001218C4  mov         eax,0CCCCCCCCh  
001218C9  rep stos    dword ptr es:[edi]  //从edi开始进行ecx次初始化值0CCCCCCCCh 

 

至此,函数栈帧创建并初始化完毕,这也就是为什么在某些情况下会打印出烫烫烫烫烫。 

函数临时变量的创建

int x = 10, y = 20;
001218D5  mov         dword ptr [ebp-8],0Ah  //把0ah值给ebp-8位置
001218DC  mov         dword ptr [ebp-14h],14h  //把14h值给ebp-14位置
    int c = 0;
002618E3  mov         dword ptr [ebp-20h],0  //把0给ebp-20h位置

至此,函数临时变量创建完毕。 

函数的调用 

 c = add(x, y);
002618EA  mov         eax,dword ptr [ebp-14h]  //把ebp-14h位置的值赋给eax,也就是y的值
002618ED  push        eax  //压栈
002618EE  mov         ecx,dword ptr [ebp-8]  //把ebp-8为位置的值给ecx,也就是x的值
002618F1  push        ecx  //压栈
002618F2  call        00261023  //call指令调用函数,会把call指令下一条指令的地址压栈
002618F7  add         esp,8  
002618FA  mov         dword ptr [ebp-20h],eax

形参的传递 

c = add(x, y);
002618EA  mov         eax,dword ptr [ebp-14h]  //把ebp-14h位置的值赋给eax,也就是y的值
002618ED  push        eax  //压栈
002618EE  mov         ecx,dword ptr [ebp-8]  //把ebp-8为位置的值给ecx,也就是x的值
002618F1  push        ecx  //压栈

 我们从中发现,形参的传递从右往左传递,形参与实参所处空间不同,所以形参的改变不能改变实参。

002618F2  call        00261023  //call指令调用函数,会把call指令下一条指令的地址压栈
002618F7  add         esp,8  
002618FA  mov         dword ptr [ebp-20h],eax

call 指令是要执行函数调用逻辑的,在执行call指令之前先会把call指令的下一条指令的地址进行压栈
操作,这个操作是为了解决当函数调用结束后要回到call指令的下一条指令的地方,继续往后执行。

 

下面进入add函数:

00261850  push        ebp  
00261851  mov         ebp,esp  
00261853  sub         esp,0CCh  
00261859  push        ebx  
0026185A  push        esi  
0026185B  push        edi  
0026185C  lea         edi,[ebp-0Ch]  
0026185F  mov         ecx,3  
00261864  mov         eax,0CCCCCCCCh  
00261869  rep stos    dword ptr es:[edi]  
0026186B  mov         ecx,26C008h  
00261870  call        0026131B  
	int z = 0;
00261875  mov         dword ptr [ebp-8],0  
	z = x + y;
0026187C  mov         eax,dword ptr [ebp+8]  
0026187F  add         eax,dword ptr [ebp+0Ch]  
00261882  mov         dword ptr [ebp-8],eax  
	return z;
00261885  mov         eax,dword ptr [ebp-8] 

 add函数栈帧的创建与main函数同理。

00261850  push        ebp  
00261851  mov         ebp,esp  
00261853  sub         esp,0CCh  
00261859  push        ebx  
0026185A  push        esi  
0026185B  push        edi  
0026185C  lea         edi,[ebp-0Ch]  
0026185F  mov         ecx,3  
00261864  mov         eax,0CCCCCCCCh  
00261869  rep stos    dword ptr es:[edi] 

下面进行运算:

int z = 0;
00261875  mov         dword ptr [ebp-8],0  
	z = x + y;
0026187C  mov         eax,dword ptr [ebp+8]  //把x‘放入eax
0026187F  add         eax,dword ptr [ebp+0Ch]  //把y’加入eax
00261882  mov         dword ptr [ebp-8],eax //把eax值放入ebp-8也就是z

 

我们知道函数内部临时变量z是要被销毁的,那么如何返回z呢? 

返回值 

return z;
00261885  mov         eax,dword ptr [ebp-8] //把z的值存入eax

 通过把z的值存入寄存器eax,来返回z的值。

至此,add函数调用完毕,下面分析如何销毁。

 函数栈帧的销毁

00261888  pop         edi  //弹出
00261889  pop         esi  //弹出
0026188A  pop         ebx  //弹出
0026188B  add         esp,0CCh  esp+occh
00261891  cmp         ebp,esp  
00261893  call        00261244  
00261898  mov         esp,ebp  //把ebp的值给esp
0026189A  pop         ebp  //弹出ebp
0026189B  ret  
00261888  pop         edi  //弹出
00261889  pop         esi  //弹出
0026188A  pop         ebx  //弹出
0026188B  add         esp,0CCh  esp+occh

 

00261898  mov         esp,ebp  
0026189A  pop         ebp  //把ebp弹出并将值放入ebp指针
0026189B  ret  //返回到保存的地址。也就是main函数

 

此时ebp指向main函数栈底esp指向栈顶,接着继续执行main函数。

002618F2  call        00261023  
002618F7  add         esp,8  
002618FA  mov         dword ptr [ebp-20h],eax  

寄存器eax中的值放入c,也就成功返回了值。 

至此,函数栈帧销毁的完整过程已经分析完了。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嚞譶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值