函数的栈帧创建与销毁
#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始终指向栈顶
00F21411 mov ebp,esp
//将esp值传给ebp,也就是让esp,ebp移在一起
00F21413 sub esp,0E4h
//sub为减的意思,即将esp-0E4h赋给esp,且函数调用分配由高地址向低地址增长,因此esp向上移动,即开辟了新空间,也就是为main函数开辟空间
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函数栈底
上一篇: 自主实现简单的shell
下一篇: 递归函数