%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返回。