为什么%rbp指向什么?

时间:2017-06-21 23:17:04

标签: linux assembly gdb x86-64 stack-pointer

众所周知,%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

为什么它会拯救&#34; (推)%rbp 如果它没有指向什么?

1 个答案:

答案 0 :(得分:5)

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设置为原始值RBP。在此之后,在美丽的ASCII艺术中,堆栈看起来与上面描述的完全相同。

然后该函数执行它的任务,执行它想要执行的任何代码。如图所示,它可以使用来自RBP+x偏移来访问它在堆栈上传递的任何参数(RBP) ,它可以使用来自RBP-x ie leaveq )的否定偏移量来访问已在堆栈上分配空间的任何局部变量。如果您了解堆栈在内存中向下向下(地址变小),则此偏移方案是有意义的。

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

mov %rbp, %rsp
pop %rbp

或等同于:

RSP

第一条指令将RBP设置为RBP的值(整个函数代码中使用的工作值),第二条指令弹出&#34;原始/保存的RBP& #34;离开堆栈,进入RBP。这恰恰是我们在上面看到的序言代码中完成的相反并非巧合。

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

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

进入您的函数时RSP为0的原因似乎是a peculiarity of GDB,而不是您真正需要关注的事情。正如Shift_Left在注释中所述,Linux中的GDB在将控制权交给应用程序之前将所有寄存器(RBP除外)预先初始化为0。如果你在调试器之外运行了这个程序,只是将RBP的初始值打印到stdout,你就会发现它不是零。

但是,再一次,准确的价值对你来说并不重要。理解上面调用堆栈的示意图是关键。假设帧指针没有被删除,编译器不知道它何时生成序言和结尾代码 值{{1}}在进入时会有什么,因为它不知道在哪里在调用堆栈上,函数最终会被调用。