天天看点

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

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

注:使用的工具是VS2017

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

#include <stdio.h>

    int Add(int x, int y)
    {
        int z = ;
        z = x + y;
        return z;
    }

    int main()
    {
        int a = ;
        int b = ;
        int ret = Add(a, b);
        printf("%d\n", ret);
        return ;
    }
           

函数在调用的过程中,是需要为函数来开辟栈空间,用于本次函数的调用过程中临时变量的保存、现场保护,这块空间就被称为函数栈帧。

首先我们来研究一下main函数的汇编代码:

int main()
    {
      push        ebp  
      mov         ebp,esp  
      sub         esp,h  
      push        ebx  
    A  push        esi  
    B  push        edi  
    C  lea         edi,[ebp-h]  
      mov         ecx,h  
      mov         eax,CCCCCCCCh  
    C  rep stos    dword ptr es:[edi]  
        int a = ;
    E  mov         dword ptr [a],Ah  
        int b = ;
      mov         dword ptr [b],h  
        int ret = Add(a, b);
    C  mov         eax,dword ptr [b]  
    F  push        eax  
      mov         ecx,dword ptr [a]  
      push        ecx  
      call        _Add (Eh)  
      add         esp,  
           

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

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

开始调用main函数

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

81  push        ebp  
           

push 表示压栈,从栈顶压入。

深度剖析函数的调用过程(栈帧)
mov         ebp,esp  
           

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

深度剖析函数的调用过程(栈帧)
sub         esp,h  
           

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

深度剖析函数的调用过程(栈帧)
819  push        ebx  
    81A  push        esi  
    81B  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 = ;
    E  mov         dword ptr [a],Ah  
           

表示将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);
    C  mov         eax,dword ptr [b]  
    F  push        eax  
      mov         ecx,dword ptr [a]  
      push        ecx  
      call        _Add (Eh)  
      add         esp,  
    C  mov         dword ptr [ret],eax  
           
C  mov         eax,dword ptr [b]  
    F  push        eax  
      mov         ecx,dword ptr [a]  
      push        ecx   
           

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

深度剖析函数的调用过程(栈帧)
int Add(int x, int y)
    {
      push        ebp  
      mov         ebp,esp  
      sub         esp,CCh  
      push        ebx  
    A  push        esi  
    B  push        edi  
    C  lea         edi,[ebp-CCh]  
      mov         ecx,h  
      mov         eax,CCCCCCCCh  
    C  rep stos    dword ptr es:[edi]  
        int z = ;
    E  mov         dword ptr [z],  
        z = x + y;
      mov         eax,dword ptr [x]  
      add         eax,dword ptr [y]  
    B  mov         dword ptr [z],eax  
        return z;
    E  mov         eax,dword ptr [z]  
    }
      pop         edi  
      pop         esi  
      pop         ebx  
      mov         esp,ebp  
      pop         ebp  
      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中

pop         edi  
      pop         esi  
      pop         ebx  
      mov         esp,ebp  
      pop         ebp  
      ret  
           

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

接下来就会从call指令处继续向下执行

add         esp,  
    C  mov         dword ptr [ret],eax
           

将 esp 向下移,将形参销毁,把 eax放入ret( ebp-20h) 中。

下面就是对main栈帧的销毁,和Add栈帧的销毁一样。

继续阅读