1

%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到堆栈?

4

1 回答 1

16

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 艺术中。

然后该函数做它的事情,执行它想要执行的任何代码。如图所示,它可以使用( ie , ) 的偏移量访问它在堆栈上传递的任何参数,并且它可以使用( ie )的偏移量访问它在堆栈上分配空间的任何局部变量)。如果您了解堆栈在内存中向下增长(地址变小),那么这种偏移方案是有意义的。RBPRBP+xRBPRBP-x

最后,结束函数的样板尾声代码是:

leaveq

或者,等效地:

mov %rbp, %rsp
pop %rbp

第一条指令设置RSPRBP(整个函数代码中使用的工作值)的值,第二条指令将“原始/保存的 RBP”从堆栈中弹出,放入RBP. 这与我们在上面看到的序言代码中所做的完全相反,这并非巧合。

但请注意,这只是一个约定。除非 ABI 要求,否则编译器可以自由地RBP用作通用寄存器,与堆栈指针无关。这是有效的,因为编译器可以在编译时计算所需的偏移量RSP,这是一种常见的优化,称为“帧指针省略”(或“帧指针省略”)。这在 32 位模式中尤其常见,可用的通用寄存器的数量非常少,但您有时也会在 64 位代码中看到它。当编译器省略了帧指针时,它不需要序言和结尾代码来操作它,因此也可以省略。

您看到所有这些帧指针簿记的原因是因为您正在分析未优化的代码,其中帧指针永远不会被忽略,因为拥有它通常会使调试更容易(并且因为执行速度不是一个重要的问题)。

RBP进入你的函数时它为 0 的原因似乎是GDB 的一个特性,而不是你真正需要关心的事情。RSP正如评论中的 Shift_Left 所指出的,Linux 下的 GDB 在将控制权交给应用程序之前会将所有寄存器(除了 )预初始化为 0。如果你在调试器之外运行这个程序,并且简单地将初始值打印RBP到标准输出,你会看到它是非零的。

但是,同样,确切的值对你来说并不重要。理解上面调用栈的示意图是关键。假设没有删除帧指针,编译器不知道它何时生成序言和尾声代码在进入时会有什么RBP,因为它不知道函数最终会在调用堆栈的哪个位置被调用。

于 2017-06-22T08:07:08.613 回答