已知%rsp指向堆栈框架的顶部, %rbp指向堆栈框架的底部。 然后我不明白为什么%rbp在这段代码中是0x0 :
(gdb) x/4xg $rsp 0x7fffffffe170: 0x00000000004000dc 0x0000000000000010 0x7fffffffe180: 0x0000000000000001 0x00007fffffffe487 (gdb) disas HelloWorldProc Dump of assembler code for function HelloWorldProc: => 0x00000000004000b0 <+0>: push %rbp 0x00000000004000b1 <+1>: mov %rsp,%rbp 0x00000000004000b4 <+4>: mov $0x1,%eax 0x00000000004000b9 <+9>: mov $0x1,%edi 0x00000000004000be <+14>: movabs $0x6000ec,%rsi 0x00000000004000c8 <+24>: mov $0xd,%edx 0x00000000004000cd <+29>: syscall 0x00000000004000cf <+31>: leaveq 0x00000000004000d0 <+32>: retq End of assembler dump. (gdb) x/xg $rbp 0x0: Cannot access memory at address 0x0
为什么如果没有指向堆栈,将%rbp “保存”(推送)到堆栈?
RBP
是一个通用寄存器,所以它可以包含你(或你的编译器)希望包含的任何值。 RBP
只是用来指向程序框架。 根据这个惯例,堆栈看起来像这样:
Low |====================| addresses | Unused space | | | |====================| ← RSP points here ↑ | Function's | ↑ | local variables | ↑ | | ↑ RBP - x direction |--------------------| ← RBP points here of stack | Original/saved RBP | ↓ RBP + x growth |--------------------| ↑ | Return pointer | ↑ |--------------------| ↑ | Function's | | parameters | | | |====================| | Parent | | function's data | |====================| | Grandparent | High | function's data | addresses |====================|
因此,一个功能的样板序言代码是:
push %rbp mov %rsp, %rbp
第一条指令通过将RBP
的原始值压入堆栈来保存原始值,然后第二条指令将RBP
设置为RSP
的原始值。 在这之后,堆栈看起来就像上面描述的一样,在漂亮的ASCII艺术中。
该函数然后执行它的任务,执行任何想要执行的代码。 如图所示,通过使用来自RBP
( 即 RBP+x
)的正偏移量,它可以访问它在堆栈上传递的任何参数,并且可以通过使用负偏移量来访问已经在堆栈上分配空间的任何局部变量来自RBP
( 即 RBP-x
)。 如果你明白堆栈在内存中向下增长(地址变小),那么这个抵消方案是有意义的。
最后,样板结语代码结束功能是:
leaveq
或者等价地:
mov %rbp, %rsp pop %rbp
第一条指令将RSP
设置为RBP
(在整个函数代码中使用的工作值)的值,第二条指令将“原始/保存的RBP”从堆栈弹出到RBP
。 这不是巧合,这恰恰与我们在上面看到的序言代码所做的完全相反 。
但请注意,这只是一个惯例 。 除非ABI要求,编译器可以自由使用RBP
作为通用寄存器,与堆栈指针无关。 这是可行的,因为编译器可以在编译时从RSP
计算所需的偏移量,这是一种常见的优化,称为“帧指针省略”(或“帧指针省略”)。 在32位模式下,可用通用寄存器的数量非常少,但在64位代码中有时会看到。 当编译器忽略了帧指针时,它不需要序言和结尾代码来操作它,所以这也可以省略。
你看到所有这些帧指针簿记的原因是因为你正在分析未优化的代码,其中帧指针永远不会被消除,因为它周围往往使调试更容易(并且因为执行速度不是一个重要的问题)。
进入你的函数后RBP
为0的原因似乎是GDB的一个特点 ,而不是你真正需要关心的东西。 由于注释中的Shift_Left注释,Linux下的GDB将所有寄存器( RSP
除外)预先初始化为0,然后将控制权交给应用程序。 如果你已经在调试器之外运行这个程序,并且简单地将RBP
的初始值打印到stdout,你会发现它不是零。
但是,再次,确切的价值应该不重要给你。 了解上述调用堆栈的示意图是关键。 假设帧指针没有被消除,编译器不知道它什么时候生成序言和结尾代码, RBP
在进入时将具有什么价值,因为它不知道函数将在哪里调用函数。