您的位置:首页 > 其它

从简单的Add函数分析函数调用及函数栈帧过程

2017-05-23 12:54 253 查看
1.函数的调用过程即函数栈帧,是编译器要用来实现函数调用的一个过程。栈帧(stack frame),机器用栈来传递过程参数,存储返回信息,保存寄存器用于以后恢复,以及本地存储。为单个过程(函数调用)分配的那部分栈称为栈帧。栈帧其实是两个指针寄存器,寄存器%ebp为帧指针,而寄存器%esp为栈指针,当程序运行时,栈指针可以移动(大多数的信息的访问都是通过帧指针的)。总之简单一句话,栈帧的主要作用是用来控制和保存一个过程的所有信息的。下面我们就用一个简单的Add()函数分析函数栈帧的过程。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int Add(int x, int y)
{
 int ret = 0;
 ret = x + y;
 return ret;
}
int main()
{
 int a = 10;
 int b = 20;
 int ret=Add(a, b);
 getchar();
 return 0;
}
2此时我们转到反汇编,查看整个函数的调用过程。
int main()
{
011616D0  push        ebp 
011616D1  mov         ebp,esp 
011616D3  sub         esp,0D8h 
011616D9  push        ebx 
011616DA  push        esi 
011616DB  push        edi 
011616DC  lea         edi,[ebp-0D8h] 
011616E2  mov         ecx,36h 
011616E7  mov         eax,0CCCCCCCCh 
011616EC  rep stos    dword ptr es:[edi] 
 int a = 10;
011616EE  mov         dword ptr [a],0Ah
 int b = 20;
011616F5  mov         dword ptr [b],14h 
 int ret=Add(a, b);
011616FC  mov         eax,dword ptr [b] 
011616FF  push        eax 
01161700  mov         ecx,dword ptr [a] 
0116
4000
1703  push        ecx 
01161704  call        _Add (011610F5h) 
01161709  add         esp,8 
0116170C  mov         dword ptr [ret],eax 
 getchar();
0116170F  mov         esi,esp 
01161711  call        dword ptr [__imp__getchar (01169160h)] 
01161717  cmp         esi,esp 
01161719  call        __RTC_CheckEsp (01161113h) 
 return 0;
下面我们来逐条分析过程。
1.压栈,我们知道main函数在mainCRTstartup中调用,此时先为mainCRTstartup开辟栈空间。
2.ebp和esp是维护这块栈空间的两个寄存器指针,esp指向栈顶,ebp指向栈底,move指令就是把esp的内容传给ebp,此时ebp中就存的是esp里面的内容,所以此时ebp和esp同时指向栈顶。
3.esp减去0D8h大小的值,为main函数开辟栈空间。
4.push ebx ,push esi, push edi三个push指令进行了三次压栈,开辟了三个地址,其中存放来了 ebx esi edi。同时esp向上走了三个地址。
5.lea 加载有效地址,把ebp-0D8h这个地址放在edi这个寄存器中;为后续ret的执行做准备;
6.将39h这个值放入到ecx当中
7.初始化,将0cccccccch存放到eax这个寄存器中;
8.rep是重复的意思,doubleword.就是按双字节向下拷贝36h次0cccccccccch
9.将0Ah放入到ebp-8这个地址当中。
10.将14h拷贝到ebp-14h当中,这就是局部变量的创建过程,如果不初始化里面就是0ccccccccch,也就是随机值的产生。
11.为调用add()函数做准备;把ebp-14h的内容也就是b的值20双字拷贝到寄存器eax里面;push eax,然后进行压栈,esp指向存放寄存eax的空间地址;
12.把ebp-8的内容也就是a的值10双字拷贝到ecx里面,然后进行压栈;
13.call指令,调用函数,在执行call指令同时,在栈顶ecx又开辟了一开新空间,用于存放call指令下一条指令的地址;这个地址的作用是call指令调用add()函数结束时jump指令能够找到call指令下一条指令的地址,从而回到main函数中;这个很重要,不然函数就不知回到哪里了。
14.:add()函数调用的第一步  push ebp 就是 压栈 ,注意这个时候压的是main函数的ebp的地址;
15.这个时候add()函数空间的开辟和初始化和main函数的步骤一样;
16.把0双字拷贝到 ebp-8这个空间里面去(创建临时变量ret 且初始化为0);
17.ebp+8 就是ecx中保存的a的值;这一步就是把a的值拷贝到eax这个寄存器中间去;
18.ebp+0ch也就是b的值,这步骤就是把b的值加上a的值加起来放在eax里面;
19.再把eax里面的值拷给ebp-8也就是ret里面,ret的值变为30 ,再将ret的值放回到eax中,这表示一个返回机制,ret的值将由eax带回到main函数中,不然add()函数销毁了,这个值就不存在了;
20.接着就开始执行三次pop指令,pop是弹出的意思,将edi,esi,ebx这三块空间弹出,不并不是代表着三块空间不存在了,只是他们不属于add()函数了;
21.将ebp给esp,意味着esp回到ebp所指向的地址,刚刚创建的add函数的空间瞬间被销毁;
22.pop ebp 这里弹出的ebp是main函数的ebp,所以ebp栈底指针回到main函数ebp的位置:;
23.ret是指重复的意思,继续执行pop指令,pop这次弹出的是call指令,call值里面存放的是call指令的下一条指令的地址;所以ret执行按完成后,程序回到call指令下一条指令的地址;
24.call指令的下一条指令就是esp+8;把esp向下挪动8个字节,所以形参实例化的两个空间ecx,eax被销毁;10和20 倍弹出去;
25。把eax带回来的值付给ebp-20;也就是ret所在的空间,此时ret复制为30,到此add该函数的调用完全结束,下面是system的调用,我们就不看了;
26.xor是异或的意思,把两个相同的eax异或,是归零的操作,因为此时寄存器已经没有了用处,需要归零把它还给硬盘
27.main函数栈桢的销毁过程和add函数差不多;直到esp和ebp回到-mainCRTStartup中相应的位置,整个程序调用结束;
   
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: