函数的栈帧创建与销毁

时间:2022-02-06 21:20:44
 
#include<stdio.h>
#include<stdlib.h>
int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}
int main()
{
	int a = 1;
	int b = 2;
	int ret = 0;
	ret = Add(a, b);
	system("pause");
	return 0;
}

接下来按F10,并转到反汇编

int main()
{
00F21410  push        ebp  
00F21411  mov         ebp,esp  
00F21413  sub         esp,0E4h  
00F21419  push        ebx  
00F2141A  push        esi  
00F2141B  push        edi  
00F2141C  lea         edi,[ebp-0E4h]  
00F21422  mov         ecx,39h  
00F21427  mov         eax, 
00F2142C  rep stos    dword ptr es:[edi]  

esp:esp寄存器里面存储的是调用函数之后栈顶地址。始终指向栈顶

ebp:ebp寄存器里面存储的是调用函数之后栈底地址。始终指向栈底

push:压栈操作,把一个32位的操作数压栈,会使得esp被减4(字节),并且esp是指向栈顶的,且顶部地址逐渐减小,也就是说压栈越多,esp越小。

pop:将栈顶的元素弹出赋给其后参数

mov:数据传送,把第二个参数(源操作数)拷贝到第一个参数(目的操作数)一份。

lea:将第二个参数地址放到第一个参数(寄存器)中。

rep stos:rep指令的目的是重复上面的指令,ecx的值表示重复的次数,eax的值表示重复的内容,重复是从edi所指向地址开始,通俗的说就是把一块空间初始化为随机值。

接下来我们来用图片和文字解释

1.调用main函数之前

函数的栈帧创建与销毁

2.开始调用main函数;执行上图第一条指令

00F21411  mov         ebp,esp

//压栈,把ebp放入栈顶,而esp始终指向栈顶

函数的栈帧创建与销毁

3.
00F21411  mov         ebp,esp

//将esp值传给ebp,也就是让esp,ebp移在一起

函数的栈帧创建与销毁

4.
00F21413  sub         esp,0E4h

//sub为减的意思,即将esp-0E4h赋给esp,且函数调用分配由高地址向低地址增长,因此esp向上移动,即开辟了新空间,也就是为main函数开辟空间

函数的栈帧创建与销毁

5.
00F21419  push        ebx  
00F2141A  push        esi  
00F2141B  push        edi

//三个push压榨分别将ebx,esi,edi按顺序压入栈顶,而esp也会指向栈顶

函数的栈帧创建与销毁

6.

00F2141C  lea         edi,[ebp-0E4h]

//将ebp-0E4h的地址放入dei中,也就是edi指向ebp-0E4h

 函数的栈帧创建与销毁

6.
00F21422  mov         ecx,39h  
00F21427  mov         eax,0CCCCCCCCh  
00F2142C  rep stos    dword ptr es:[edi]
//把39h放到ecx中
//把0cccccccch放到eax中

//从edi所指向的地址开始向高地址进行拷贝,拷贝的次数为ecx内容,拷贝的内容为eax内容

函数的栈帧创建与销毁

7.

int a = 1;
0016142E  mov         dword ptr [ebp-8],1  
	int b = 2;
00161435  mov         dword ptr [ebp-14h],2  
	int ret = 0;
0016143C  mov         dword ptr [ebp-20h],0 
//把1放到epb-8的位置

//把2放到epb-14h的位置
//把0放到epb-20h的位置

函数的栈帧创建与销毁

8.

ret = Add(a, b);
00161443  mov         eax,dword ptr [ebp-14h]  
00161446  push        eax  
00161447  mov         ecx,dword ptr [ebp-8]  
0016144A  push        ecx  
0016144B  call        001610E6  
00161450  add         esp,8  
00161453  mov         dword ptr [ebp-20h],eax

//把ebp-14h的值2放入eax中,然后对eax压栈

//把ebp-8的值1放入ecx中,然后对ecx压栈

//call作用:将下一条指令地址压栈,然后进入add函数里面

函数的栈帧创建与销毁

接下来进入add函数

int Add(int x, int y)
{
001813C0 55                   push        ebp  
001813C1 8B EC                mov         ebp,esp  
001813C3 81 EC CC 00 00 00    sub         esp,0CCh  
001813C9 53                   push        ebx  
001813CA 56                   push        esi  
001813CB 57                   push        edi  
001813CC 8D BD 34 FF FF FF    lea         edi,[ebp+FFFFFF34h]  
001813D2 B9 33 00 00 00       mov         ecx,33h  
001813D7 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
001813DC F3 AB                rep stos    dword ptr es:[edi]  
	int z = 0;
001813DE C7 45 F8 00 00 00 00 mov         dword ptr [ebp-8],0  
	z = x + y;
001813E5 8B 45 08             mov         eax,dword ptr [ebp+8]  
001813E8 03 45 0C             add         eax,dword ptr [ebp+0Ch]  
001813EB 89 45 F8             mov         dword ptr [ebp-8],eax  
	return z;
001813EE 8B 45 F8             mov         eax,dword ptr [ebp-8]  
}
001813F1 5F                   pop         edi  
}
001813F2 5E                   pop         esi  
001813F3 5B                   pop         ebx  
001813F4 8B E5                mov         esp,ebp  
001813F6 5D                   pop         ebp  
001813F7 C3                   ret
//先把main函数ebp压栈, 目的是当返回时能找到main函数栈底

//接下来到int z=0之前都和main函数相同,相当开辟了add函数栈帧

函数的栈帧创建与销毁

int z = 0;
001813DE C7 45 F8 00 00 00 00 mov         dword ptr [ebp-8],0  
	z = x + y;
001813E5 8B 45 08             mov         eax,dword ptr [ebp+8]  
001813E8 03 45 0C             add         eax,dword ptr [ebp+0Ch]  
001813EB 89 45 F8             mov         dword ptr [ebp-8] eax
//把0放到ebp-8的位置,也就是创建变量z并为其赋值

//把ebp+8放到eax,即把1,放入eax

//把ebp+0ch加到eax中,即把2加到eax中
//再把eax放到ebp-8的位置,即把两数之和放到z中

return z;
001813EE 8B 45 F8             mov         eax,dword ptr [ebp-8]
//把ebp-8的值放到寄存器eax中返回, 因为ebp-8为函数临时开辟的变量空间等函数执行完会销毁,因此放寄存器中返回
001813F1 5F                   pop         edi  
}
001813F2 5E                   pop         esi  
001813F3 5B                   pop         ebx  
001813F4 8B E5                mov         esp,ebp  
001813F6 5D                   pop         ebp  
001813F7 C3                   ret  
//接下来执行pop出栈操作,edi esi ebx依次从上向下出栈,栈的特点:先进后出,后进先出

//将ebp值赋给esp,也就是esp向下移动指向ebp位置,此时add开辟的栈空间已经销毁

//pop将栈顶的元素弹出放到ebp中,也就是说将main函数的ebp放入ebp中,即ebp现在指向main函数ebp

//在执行ret后,会把之前push的地址弹出去,这时就要返回main函数,这也就是为什么之前要push这个地址,这样call指令就完成了
函数的栈帧创建与销毁

接下来从那个call指令继续执行

9.

00161450  add         esp,8  
00161453  mov         dword ptr [ebp-20h],eax
//把esp+8,即esp向下移,把形参销毁

//把eax(30)放到ebp-20h(ret)中

函数的栈帧创建与销毁

接下来就是对main函数栈帧的销毁,方法同上不在多说

要点提醒:注意call语句push的是下一条指令的地址,为了函数返回时知道从哪儿接着执行

                    将main函数的ebp压栈,也是为了返回时找到main函数栈底