演示代码:
#include<stdio.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);
return 0;
}
转到反汇编如下:
第一部分:
int main()
{
00AE1420 push ebp
00AE1421 mov ebp,esp
00AE1423 sub esp,0E4h
00AE1429 push ebx
00AE142A push esi
00AE142B push edi
00AE142C lea edi,[ebp-0E4h]
00AE1432 mov ecx,39h
00AE1437 mov eax,0CCCCCCCCh
00AE143C rep stos dword ptr es:[edi]
首先,main函数在mainCRTStartup()中调用的,所以系统先给mainCRTStartup()开辟一块空间,即开辟栈帧,如图:
接下来看我们的汇编代码
1.00AE1420 push ebp
push为压栈的意思,即把ebp的地址压入栈中,此时esp会向上移动指向新开辟的栈,如图:
2.00AE1421 mov ebp,esp
即把esp的值赋给ebp,此时ebp 和esp指向同一个位置,此时esp会移开,但是我们这里暂时不动如图:
3.00AE1423 sub esp,0E4h
sub为减的意思,即esp指向esp-0E4h的这个地方,即开辟了0E4h这块空间,并且esp指向它的栈顶,这也是为main函数开辟了空间
4. 00AE1429 push ebx
00AE142A push esi
00AE142B push edi
三个push压栈分别将ebx,esi,esi,分别按顺序压入栈顶,此时esp也会向上指向栈顶,如图
5.00AE142C lea edi,[ebp-0E4h]
这句话的意思是把[ebp-0E4h]的值放入edi中,这样edi便指向了[ebp-0E4h]的位置,如图:
6.00AE1432 mov ecx,39h
00AE1437 mov eax,0CCCCCCCCh
00AE143C rep stos dword ptr es:[edi]
首先看第一句话意思是把39h放到ecx中(ecx是寄存器),重复rep的前指令。
第二句话的意思是把0CCCCCCCCh放入eax中(eax也是寄存器)。
第三句话的意思是对edi所指向的位置开始向高地址进行拷贝,拷贝的内容为eax的内容,拷贝次数为39h次(即对ebp-0E4h这块区域进行了初始化)。如图
我们继续向下看
第二部分的代码
00AE143C rep stos dword ptr es:[edi]
int a = 10;
00AE143E mov dword ptr [ebp-8],0Ah
int b = 20;
00AE1445 mov dword ptr [ebp-14h],14h
int ret = 0;
00AE144C mov dword ptr [ebp-20h],0
ret = add(a, b);
00AE1453 mov eax,dword ptr [ebp-14h]
00AE1456 push eax
00AE1457 mov ecx,dword ptr [ebp-8]
00AE145A push ecx
00AE145B call 00AE10EB
00AE1460 add esp,8
00AE1463 mov dword ptr [ebp-20h],eax
return 0;
继续分析我们的代码
7. int a = 10;
00AE143E mov dword ptr [ebp-8],0Ah
int b = 20;
00AE1445 mov dword ptr [ebp-14h],14h
int ret = 0;
00AE144C mov dword ptr [ebp-20h],0
int a=10,mov dword ptr [ebp-8],0Ah(10),这句话的意思即把10放入ebp-8的位置;
以此类推,分别把20放入ebp-14h的位置,把0放入ebp-20h的地方,这里相当于创建好了变量a,b,ret并且分别赋上了相应的值。机器并不认识你的变量名,只认识地址
忘记说栈帧中从栈底到栈顶是高地址指向低地址。如图
8. ret = add(a, b);
00AE1453 mov eax,dword ptr [ebp-14h]
00AE1456 push eax
00AE1457 mov ecx,dword ptr [ebp-8]
00AE145A push ecx
在这里,mov eax,dword ptr [ebp-14h]
push eax,
的意思就是把ebp-14hd的值放入eax中,然后对eax进行压栈,刚才已经知道ebp-14h里面放的是20的值,所以在这里相当于函数的传参,并且把参数进行了压栈,
此时esp会向上移动并且指向ecx栈顶
所以如图所示
9.00AE145B call 00AE10EB
在这里call 00AE10EBd的意思是再一次进行压栈,将00AE10EB压进去,然后进入add()函数里面
接下来看我们的add函数部分代码
00AE13D0 push ebp
00AE13D1 mov ebp,esp
00AE13D3 sub esp,0CCh
00AE13D9 push ebx
00AE13DA push esi
00AE13DB push edi
00AE13DC lea edi,[ebp+FFFFFF34h]
00AE13E2 mov ecx,33h
00AE13E7 mov eax,0CCCCCCCCh
00AE13EC rep stos dword ptr es:[edi]
int z = 0;
00AE13EE mov dword ptr [ebp-8],0
z = x + y;
00AE13F5 mov eax,dword ptr [ebp+8]
00AE13F8 add eax,dword ptr [ebp+0Ch]
00AE13FB mov dword ptr [ebp-8],eax
return z;
00AE13FE mov eax,dword ptr [ebp-8]
}
00AE1401 pop edi
00AE1402 pop esi
00AE1403 pop ebx
00AE1404 mov esp,ebp
00AE1406 pop ebp
00AE1407 ret
首先我们可以看到
10.00AE13D0 push ebp
00AE13D1 mov ebp,esp
00AE13D3 sub esp,0CCh
00AE13D9 push ebx
00AE13DA push esi
00AE13DB push edi
00AE13DC lea edi,[ebp+FFFFFF34h]
00AE13E2 mov ecx,33h
00AE13E7 mov eax,0CCCCCCCCh
00AE13EC rep stos dword ptr es:[edi]
这段代码和main函数开始的代码一样,都是先将ebp压栈,这里的ebp是main函数的ebp,然后开辟空间,然后再进行三次压栈,然后初始化
当然esp和ebp也会改变他们的位置,此时相当于开辟了add函数的栈帧,如图
11. int z = 0;
00AE13EE mov dword ptr [ebp-8],0
这句话的意思是把0放到ebp-8的位置,即创建变量z并且赋值为0
12. z = x + y;
00AE13F5 mov eax,dword ptr [ebp+8]
00AE13F8 add eax,dword ptr [ebp+0Ch]
00AE13FB mov dword ptr [ebp-8],eax
在这里相当于把ebp+8放到了eax中,即将10的值放入eax中,然后再将20(ebp+0Ch)的值和eax中的值(10)进行相加,所以现在eax中的值为30
然后再将eax(30)的值放入ebp-8(即变量z)中
如图
13. return z;
00AE13FE mov eax,dword ptr [ebp-8]
在这里把ebp-8的值放回到寄存器eax中返回,因为ebp-8为函数临时开辟的变量空间等函数执行完会销毁,这样的话就不能将结果返回去,所以放在寄存器中返回,
14.00AE1401 pop edi
00AE1402 pop esi
00AE1403 pop ebx
00AE1404 mov esp,ebp
00AE1406 pop ebp
00AE1407 ret
接来下执行pop出栈操作,edi,esi,ebx分别从上向下出栈,体现了栈的特点:先进后出,后进先出。然后将ebp的值赋值给esp,esp向下移动指向ebp的位置,之后pop ebp,这个时候ebp返回继续维护main函数的栈帧,在这个时候上面的空间不属于你了,但依然存在。
15.00AE1407 ret
在这里,执行ret指令后,会把之前push 的地址(00AE10EB)弹出去,这个时候就从Add()函数返回main()函数了。这就是为什么当初要push这个地址了,这样call指令就完成了。程序走完自动会到当初call指令的下一条指令
16.00AE1460 add esp,8
00AE1463 mov dword ptr [ebp-20h],eax
给esp+8意思即把esp向下移,即把形参也弹出去了,形参被销毁,然后把eax(里面存的是30)放到ebp-20h(ret)中
到这里Add函数栈帧的调用和销毁已经完成。接下来就是对main()函数的返回和栈帧的销毁和Add()函数一样,就不继续做讲解了。
最后附上整个流程图
上面的整个流程就是函数栈帧的创建和销毁。
如果哪里有错误的地方欢迎大家积极指出!!!