您的位置:首页 > 编程语言 > C语言/C++

浅谈函数的调用和栈帧的创建和销毁

2017-05-21 14:17 169 查看
演示代码:

#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()函数一样,就不继续做讲解了。
最后附上整个流程图



上面的整个流程就是函数栈帧的创建和销毁。
如果哪里有错误的地方欢迎大家积极指出!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息