天天看点

栈帧结构

栈帧结构

%esp寄存器存储栈顶地址

%ebp寄存器存储各帧首地址

在x86中%ebp变成了%rbp,%esp变成了%rsp

int son_add(int a, int b)
{
        return a+b;
}

int father()
{
        int a = ;
        int b = ;
        int sum = ;
        sum = son_add(a, b);

        return sum;
}
           

以这两个函数举例,反汇编代码如下

<son_add>:
   :                         push   %ebp
   :    e5                 mov    %esp,%ebp
   :   b  0c            mov    (%ebp),%eax
   :     08            add    (%ebp),%eax
   :   c9                      leave  
   a:   c3                      ret    
   b:                         nop    

c <father>:
   c:                         push   %ebp
   d:    e5                 mov    %esp,%ebp
   f:   a                   push   $0x9
  :   a                 push   $0x8
  :   e8 fc ff ff ff        call    <father+>
  :   a                     pop    %edx
  :                        pop    %ecx
  a:   c9                     leave  
  b:   c3                     ret    
           

一开始在未调用son时%ebp这个寄存器所存储的是father的帧首地址。当father调用son函数时首先执行push %ebp,就将%ebp寄存器里所存储的father的帧首地址压入栈中。此时被调用者son的帧首地址就存储着调用者father的帧首地址,而mov %esp %ebp就是将%esp中存储的son的帧首地址赋给%ebp寄存器。此时%ebp寄存器中所存储的就是son的帧首地址了,之后也不再修改。

接下来的两句很有意思,mov 0xc(%ebp),%eax是对帧指针里的地址先增加0xC再取里面的值,增加12是啥意思?12刚好是4的倍数,也就是向上移动三个栈存储单元。根据栈结构图发现,%ebp作为帧首,向上移动一个单元是“返回地址”;向上移动两个单元是参数1,向上移动三个单元当然是参数2!也就是我们传给son_add的第二个参数9。因此这条汇编的意思是把9赋值给%eax寄存器。依次类推是不是还应该把参数1的这个8赋给另一个寄存器呢?编译器可没这么傻,你son_add不就是想做个加法么?直接add 0x8(%ebp),%eax,让%ebp寻找到参数1的地址位置,读取出8,然后直接和%eax的值相加,搞定!

好了,这个时候%eax寄存器就是存有加法结果的寄存器了,计算完成子函数需要返回了,于是先后执行leave和ret,先看leave的等价汇编代码:

leave:movl %ebp,%esp

pop %ebp

这步在理解上稍显困难,主要是对出入栈的操作理解。movl %ebp,%esp这条语句,其实目的就是破坏子函数son_add栈帧结构。想想看,直接修改栈指针%esp,让他指向son_add的帧首,然后执行pop %ebp,将帧首里的值赋给%ebp!回忆下帧首里存的是啥值啊?那当然是father帧首的地址值啊,这句目的就是让%ebp重新指回father栈帧的帧首!OK,son_add的帧首被弹出栈后,栈指针也不会再指向son_add帧首了,而是指向他的上面一个栈存储单元,那就是father帧的末尾:返回地址,leave的使命便完成。接下来就是ret,考虑到ret要完成函数调用的返回,还要维护栈帧的返回,我们可以猜测ret的等价汇编代码应该是:

ret:jmp (%esp)

add $4, %esp

因为此时%esp指向father帧的末尾,而该末尾里面又存储了father调用son_add函数后应该返回的地址(这里应该是18),因此就应该是将该地址取出,直接跳转到18,也就是call语句之后的语句,而子函数son_add的调用既然已经完成,根据个人的猜测,“返回地址”已失去意义,因此栈指针会加4返回。