已知%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在进入您的函数时似乎是a peculiarity of GDB,而不是您真正需要关心的事情。正如shift_在注释中所说,linux下的gdb在将控制权交给应用程序之前,会将所有寄存器(除了RSP)预初始化为0。如果您在调试器之外运行这个程序,并简单地将RBP的初始值打印到stdout,您将看到它将是非零的。
但是,准确的价值对你来说不重要。理解上面调用堆栈的示意图是关键。假设帧指针没有被省略,编译器不知道何时生成序言和结尾代码,因为它不知道函数在调用堆栈上的何处被调用,所以在输入时RBP值会是什么。

关于linux - 为什么%rbp指向什么?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/44687662/

10-12 02:12