日期:2014-05-16 浏览次数:20681 次
看一个例子:
void FuncA() { } void FuncB() { FuncA(); } int main() { FuncB(); return 0; }
用下面命令编译出它发布版本:
[buckxu@xuzhina 1]$ g++ -o xuzhina_dump_c3_s1_relxuzhina_dump_c3_s1.cpp
在讨论它们的栈之前,先分析一下main,FuncB,FuncA这三个函数的汇编:
(gdb) disassemble FuncA Dump of assembler code for function_Z5FuncAv: 0x08048470 <+0>: push %ebp 0x08048471 <+1>: mov %esp,%ebp 0x08048473 <+3>: pop %ebp 0x08048474 <+4>: ret End of assembler dump.
(gdb) disassemble FuncB Dump of assembler code for function_Z5FuncBv: 0x08048475 <+0>: push %ebp 0x08048476 <+1>: mov %esp,%ebp 0x08048478 <+3>: call 0x8048470 <_Z5FuncAv> 0x0804847d <+8>: pop %ebp 0x0804847e <+9>: ret End of assembler dump.
(gdb) disassemble main Dump of assembler code for function main: 0x0804847f <+0>: push %ebp 0x08048480 <+1>: mov %esp,%ebp 0x08048482 <+3>: call 0x8048475 <_Z5FuncBv> 0x08048487 <+8>: mov $0x0,%eax 0x0804848c <+13>: pop %ebp 0x0804848d <+14>: ret End of assembler dump.
从它们的汇编,都可以看到在这三个函数的开头,都有这样的指令:
push %ebp mov %esp,%ebp
而在它们的结尾则有这样的指令:
pop %ebp ret
在没有使用gcc的-fomit-frame-pointer选项来编译的函数一般都会有这样的开头和结尾。这几行指令可以看作是函数的开头和结尾的特征。像FuncA这样空叶子函数,一般就是由这两个特征拼起来的。
在x86里,ebp存放着函数桢指针,而esp则指向当前栈顶位置,而eip则是要执行的下一条指令地址。
所以,函数开头的两条指令的含义如下
push %ebp // esp = esp – 4,把ebp的值放入到esp指向的地址 mov %esp,%ebp // 把esp的值放到ebp里。即ebp = esp
函数结尾两条指令的含义如下
pop %ebp // 把esp指向地址的内容放到ebp, esp = esp+4 ret //把ebp指向地址下一个单元的内容放到eip,esp = esp + 4
下面验证一下上面的内容。在main函数的开头指令地址0x0804847f打断点
(gdb) tbreak *0x0804847f Temporary breakpoint 1 at 0x804847f
(gdb) r Starting program:/home/buckxu/work/3/1/xuzhina_dump_c3_s1_rel Temporary breakpoint 1, 0x0804847f in main() (gdb) i r ebp esp ebp 0x0 0x0 esp 0xbffff4dc 0xbffff4dc (gdb) x /4x $esp 0xbffff4dc: 0x4a8bf635 0x00000001 0xbffff574 0xbffff57c (gdb) ni 0x08048480 in main () (gdb) i r ebp esp ebp 0x0 0x0 esp 0xbffff4d8 0xbffff4d8 (gdb) x /4x $esp 0xbffff4d8: 0x00000000 0x4a8bf635 0x00000001 0xbffff574
push %ebp
之后,esp的值由0xbffff4dc变为0xbffff4d8,而它所指向的单元刚好是ebp的值0。这一操作实质是把旧的函数桢指针保存到栈里。
再继续执行下去
(gdb) ni 0x08048482 in main () (gdb) i r ebp esp ebp 0xbffff4d8 0xbffff4d8 esp 0xbffff4d8 0xbffff4d8 (gdb) x /4x $esp 0xbffff4d8: 0x00000000 0x4a8bf635 0x00000001 0xbffff574
mov %esp,%ebp
确实是把esp的值赋给了ebp,这一操作实质是设置函数桢指针,新的函数桢指针所指向的地址刚好放着旧的函数桢指针。
那为什么要设置函数桢呢?只是考察了main函数,并不一定能够找到规律,继续执行FuncB,FuncA,看一下能不能找到规律。
(gdb) si 0x08048475 in FuncB() () (gdb) i r esp ebp esp 0xbffff4d4 0xbffff4d4 ebp 0xbffff4d8 0xbffff4d8 (gdb) x /4x $esp 0xbffff4d4: