解析函数的堆栈调用过程

关于函数的调用堆栈有如下几个问题:

  • 函数调用函数的栈桢开辟及回退过程是什么?

  • 主调函数在调用被调函数的中间过程做了什么?

  • 被调函数执行完成后是怎样回到主调函数的?

  • 在回到主调函数后是怎么知道要运行哪一条代码(指令)?

要解决这些问题,我们就要从汇编的角度切入。通过汇编代码能够使我们更加清晰地掌握函数的堆栈调用。
汇编分为两种形式inter x86 (从右向左看) 和 AT&T unix(从左向右看),我们要学习的主要是inter x86下的汇编代码。

例:

//main.c
int sum(int a, int b)
{
    int temp = 0;
    temp = a + b;
    return temp;
}
int main()
{ 
    int a = 10;
    int b = 20;
    int ret = 0;
    ret = sum(a, b);
    printf("ret = %d\n", ret);
    return 0;
}

函数在开辟栈桢空间的时候是通过两个指针即栈顶指针esp和栈底指针ebp来完成的。main.c的汇编代码如下:

1:    #include<stdio.h>
2:
3:    int sum(int a, int b)
4:    {                                           //开辟0x44大小的函数栈桢空间并循环赋值为0xcccccccc
0040D760   push        ebp
0040D761   mov         ebp,esp
0040D763   sub         esp,44h
0040D766   push        ebx                       //三个保存sum函数现场的寄存器ebx、 esi、edi
0040D767   push        esi
0040D768   push        edi
0040D769   lea         edi,[ebp-44h]
0040D76C   mov         ecx,11h
0040D771   mov         eax,0CCCCCCCCh
0040D776   rep stos    dword ptr [edi]
5:        int temp = 0;                           //局部变量申请空间并初始化
0040D778   mov         dword ptr [ebp-4],0
6:        temp = a + b;                           //将运算结果保存在局部变量中
0040D77F   mov         eax,dword ptr [ebp+8]
0040D782   add         eax,dword ptr [ebp+0Ch]
0040D785   mov         dword ptr [ebp-4],eax
7:        return temp;                           //返回局部变量
0040D788   mov         eax,dword ptr [ebp-4]     //通过寄存器eax返回
8:    }                                          //函数栈帧空间的回退
0040D78B   pop         edi
0040D78C   pop         esi
0040D78D   pop         ebx
0040D78E   mov         esp,ebp                   //释放sum函数栈桢空间 
0040D790   pop         ebp                       //将ebp重新指向main函数栈底,并使得esp向下偏移
0040D791   ret 


9:
10:   int main()
11:   {                                           //开辟0x4c大小的函数栈桢空间并循环赋值为0xcccccccc
00401050   push        ebp
00401051   mov         ebp,esp
00401053   sub         esp,4Ch
00401056   push        ebx                        //三个保存main函数现场的寄存器ebx、 esi、edi
00401057   push        esi
00401058   push        edi
00401059   lea         edi,[ebp-4Ch]
0040105C   mov         ecx,13h
00401061   mov         eax,0CCCCCCCCh
00401066   rep stos    dword ptr [edi]            //局部变量申请空间并初始化
12:       int a = 10;
00401068   mov         dword ptr [ebp-4],0Ah
13:       int b = 20;
0040106F   mov         dword ptr [ebp-8],14h
14:       int ret = 0;
00401076   mov         dword ptr [ebp-0Ch],0
15:       ret = sum(a, b);                         //调用sum函数
0040107D   mov         eax,dword ptr [ebp-8]       //压实参
00401080   push        eax
00401081   mov         ecx,dword ptr [ebp-4]
00401084   push        ecx
00401085   call        @ILT+0(_sum) (00401005)     //跳转到sum函数入口
0040108A   add         esp,8                       //清理实参空间
0040108D   mov         dword ptr [ebp-0Ch],eax
16:
17:       printf("ret = %d\n", ret);               //调用printf函数
00401090   mov         edx,dword ptr [ebp-0Ch]
00401093   push        edx
00401094   push        offset string "ret = %d\n" (0042201c)
00401099   call        printf (004010d0)
0040109E   add         esp,8
18:       return 0;
004010A1   xor         eax,eax
19:   }                                           //清理主函数栈桢空间
004010A3   pop         edi
004010A4   pop         esi
004010A5   pop         ebx
004010A6   add         esp,4Ch                    //释放main函数栈桢空间
004010A9   cmp         ebp,esp
004010AB   call        __chkesp (00401150)
004010B0   mov         esp,ebp
004010B2   pop         ebp
004010B3   ret

其开辟运行过程可用如下图来表示:
这里写图片描述

回退过程如下:
这里写图片描述

通过汇编代码,堆函数的堆栈调用做出了如下总结:

  • 栈通过两个指针栈顶指针和栈底指针来完成堆栈桢空间的开辟,另外栈底指针保存的是调用main函数的函数栈顶地址(可以自己去了解),栈顶指针通过偏移量来开辟栈桢空间的大小,开辟完成后通过edi和esi寄存器来对栈桢空间附初始值0xcccccccc,通过栈底指针偏移将局部变量压入栈桢空间。在遇到调用函数的地方首先通过寄存器将实参入栈,然后调用call指令,call指令在执行时做了两件事:首先,为保证函数调用完成后能够回到当前位置继续执行于是将下一行指令的地址压入栈顶;另外,通过地址偏移跳转到被调函数(sum)入口。在进入被调函数前将原来ebp入栈,然后将ebp移动到栈顶以同样的方式对被调(sum)函数开辟栈桢空间和赋初始值。为局部变量temp申请空间通够ebp的偏移取值和eax寄存器保存计算结果并赋值给temp完成函数功能。最后的结构也是有eax寄存器带出(这里我们就不分析printf函数的堆栈调用过程)。在完成函数功能后就是函数栈桢空间的回退,也是有ebp和esp两个指针来完成的,其中的pop操作为将esp向下偏移和对ebp重新赋值,值就为该空间中保存的地址,也就是重新将ebp指向该地址处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值