深度剖析函数的调用过程(栈帧)

当我们在学习C语言的时候,写代码一定会用到函数(main函数),但函数在使用过程中是如何调用的,当我们从汇编的角度来剖析函数的调用,会让我们对函数的认识刚深一层。

注:使用的工具是VS2017

下面我将通过一段代码来剖析函数是如何调用的

    #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 = Add(a, b);
        printf("%d\n", ret);
        return 0;
    }

函数在调用的过程中,是需要为函数来开辟栈空间,用于本次函数的调用过程中临时变量的保存、现场保护,这块空间就被称为函数栈帧
首先我们来研究一下main函数的汇编代码:

    int main()
    {
    01061810  push        ebp  
    01061811  mov         ebp,esp  
    01061813  sub         esp,0E4h  
    01061819  push        ebx  
    0106181A  push        esi  
    0106181B  push        edi  
    0106181C  lea         edi,[ebp-0E4h]  
    01061822  mov         ecx,39h  
    01061827  mov         eax,0CCCCCCCCh  
    0106182C  rep stos    dword ptr es:[edi]  
        int a = 10;
    0106182E  mov         dword ptr [a],0Ah  
        int b = 20;
    01061835  mov         dword ptr [b],14h  
        int ret = Add(a, b);
    0106183C  mov         eax,dword ptr [b]  
    0106183F  push        eax  
    01061840  mov         ecx,dword ptr [a]  
    01061843  push        ecx  
    01061844  call        _Add (0106110Eh)  
    01061849  add         esp,8  

在还没有调用main函数之前,当_tmianCRTStartup在还没有开始调用main函数之前, esp edp 一起维护同一块空间

这里写图片描述

开始调用main函数
刚开始的三行代码是为了main函数俩开辟栈帧空间的,其中 esp 存放指向函数栈帧栈顶的地址, ebp 存放指向函数栈帧栈底的地址。

    01061810  push        ebp  

push 表示压栈,从栈顶压入。
这里写图片描述

    01061811  mov         ebp,esp  

mov 表示移动,表示的就是将 esp 移动到 ebp 的位置

这里写图片描述

    01061813  sub         esp,0E4h  

sub 表示将esp减去 0E4h(0x000000e4),将 esp 上移,因为在计算机中上面为低地址,下面为高地址。
这里写图片描述

    01061819  push        ebx  
    0106181A  push        esi  
    0106181B  push        edi 

这三行 push 就是将 ebx esi edi 先后压入栈顶,并且 esp 将会上移
这里写图片描述

    0106181C  lea         edi,[ebp-0E4h]  
    01061822  mov         ecx,39h  
    01061827  mov         eax,0CCCCCCCCh  
    0106182C  rep stos    dword ptr es:[edi]  

这四条语句就是,将 eax 的内容重复 ecx 次,重复是从 edi 所指向的地址开始。
这里写图片描述

        int a = 10;
    0106182E  mov         dword ptr [a],0Ah  

表示将10(0Ah)放入a中,放在 ebp - 8 的位置
这里写图片描述

        int b = 20;
    01061835  mov         dword ptr [b],14h 

表示将20(14h)放入b中,放在 ebp - 20 的位置
这里写图片描述
这里写图片描述

下面开始调用Add函数

        int ret = Add(a, b);
    0106183C  mov         eax,dword ptr [b]  
    0106183F  push        eax  
    01061840  mov         ecx,dword ptr [a]  
    01061843  push        ecx  
    01061844  call        _Add (0106110Eh)  
    01061849  add         esp,8  
    0106184C  mov         dword ptr [ret],eax  
    0106183C  mov         eax,dword ptr [b]  
    0106183F  push        eax  
    01061840  mov         ecx,dword ptr [a]  
    01061843  push        ecx   

上述四行代码就是先将b压栈,然后再将a压栈,实际上a,b就是传给Add函数的形参
这里写图片描述

    int Add(int x, int y)
    {
    01061700  push        ebp  
    01061701  mov         ebp,esp  
    01061703  sub         esp,0CCh  
    01061709  push        ebx  
    0106170A  push        esi  
    0106170B  push        edi  
    0106170C  lea         edi,[ebp-0CCh]  
    01061712  mov         ecx,33h  
    01061717  mov         eax,0CCCCCCCCh  
    0106171C  rep stos    dword ptr es:[edi]  
        int z = 0;
    0106171E  mov         dword ptr [z],0  
        z = x + y;
    01061725  mov         eax,dword ptr [x]  
    01061728  add         eax,dword ptr [y]  
    0106172B  mov         dword ptr [z],eax  
        return z;
    0106172E  mov         eax,dword ptr [z]  
    }
    01061731  pop         edi  
    01061732  pop         esi  
    01061733  pop         ebx  
    01061734  mov         esp,ebp  
    01061736  pop         ebp  
    01061737  ret  

前面创建栈帧的过程和main函数一样,其中第一步是先将main函数的 ebp 压栈,目的是为了在返回时能够返回到main函数的栈底
这里写图片描述

    01061725  mov         eax,dword ptr [x]  
    01061728  add         eax,dword ptr [y]  
    0106172B  mov         dword ptr [z],eax  
            return z;
    0106172E  mov         eax,dword ptr [z]  

将两数之和放入z中,并最后将z的值放在寄存器eax中

    01061731  pop         edi  
    01061732  pop         esi  
    01061733  pop         ebx  
    01061734  mov         esp,ebp  
    01061736  pop         ebp  
    01061737  ret  

接下来将会执行 pop 操作,也就是出栈,按照顺序依次将 edi esi ebx 出栈。然后将 ebp 赋值给 esp ebp 出栈。注: ret 指令会使得出栈一次,并将出栈的内容当做地址,将程序跳转到该地址处。将Add栈帧销毁。
接下来就会从call指令处继续向下执行

    01061849  add         esp,8  
    0106184C  mov         dword ptr [ret],eax

esp 向下移,将形参销毁,把 eax放入ret( ebp-20h) 中。
下面就是对main栈帧的销毁,和Add栈帧的销毁一样。

猜你喜欢

转载自blog.csdn.net/aixiaodeshushu/article/details/81202685
今日推荐