为什么 %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 艺术。
函数然后执行它的操作,执行它想执行的任何代码。正如图中所建议的,它可以通过使用 RBP
的 positive 偏移来访问它在堆栈上传递的任何参数(即, RBP+x
),并且它可以通过使用 RBP
的 negative 偏移来访问它在堆栈上分配 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
将在入口时有,因为它不知道该函数最终将在调用堆栈中的哪个位置被调用。
已知%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 艺术。
函数然后执行它的操作,执行它想执行的任何代码。正如图中所建议的,它可以通过使用 RBP
的 positive 偏移来访问它在堆栈上传递的任何参数(即, RBP+x
),并且它可以通过使用 RBP
的 negative 偏移来访问它在堆栈上分配 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 的原因似乎是 RSP
除外)预初始化为 0。如果你在调试器之外有 运行 这个程序,并且简单地将 RBP
的初始值打印到标准输出,你会看到它是非零的。
但是,再说一遍,确切的值对您来说不重要。理解上面调用栈的示意图是关键。假设帧指针没有被删除,编译器不知道什么时候生成序言和结尾代码 什么 值 RBP
将在入口时有,因为它不知道该函数最终将在调用堆栈中的哪个位置被调用。