为什么 %rbp 没有指向任何东西?

Why does %rbp point to nothing?

已知%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    [=10=]x1,%eax
   0x00000000004000b9 <+9>: mov    [=10=]x1,%edi
   0x00000000004000be <+14>:    movabs [=10=]x6000ec,%rsi
   0x00000000004000c8 <+24>:    mov    [=10=]xd,%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

为什么它 "saving"(推)%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      |====================|

因此,函数的样板 prologue 代码是:

push   %rbp
mov    %rsp, %rbp

第一条指令通过将 RBP 的原始值压入堆栈来保存它,然后第二条指令将 RBP 设置为 RSP 的原始值。在此之后,堆栈看起来与上面描述的完全一样,采用漂亮的 ASCII 艺术。

函数然后执行它的操作,执行它想执行的任何代码。正如图中所建议的,它可以通过使用 RBPpositive 偏移来访问它在堆栈上传递的任何参数(RBP+x),并且它可以通过使用 RBPnegative 偏移来访问它在堆栈上分配 space 的任何局部变量(RBP-x)。如果您了解堆栈在内存中向下 增长(地址变小),那么这种偏移方案就有意义了。

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

leaveq

或者,等价地:

mov %rbp, %rsp
pop %rbp

第一条指令将 RSP 设置为 RBP 的值(整个函数代码中使用的工作值),第二条指令将 "original/saved RBP" 弹出堆栈,进入RBP。这恰恰是我们在上面看到的序言代码中所做的相反,这并非巧合。

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

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

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

但是,再说一遍,确切的值对您来说不重要。理解上面调用栈的示意图是关键。假设帧指针没有被删除,编译器不知道什么时候生成序言和结尾代码 什么RBP 将在入口时有,因为它不知道该函数最终将在调用堆栈中的哪个位置被调用。