为什么%rbp指向什么?

ala*_*rda 0 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
Run Code Online (Sandbox Code Playgroud)

如果它什么都不指向,为什么为什么要“保存”(推入)%rbp?

Cod*_*ray 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      |====================|
Run Code Online (Sandbox Code Playgroud)

因此,该功能的样板序代码为:

push   %rbp
mov    %rsp, %rbp
Run Code Online (Sandbox Code Playgroud)

第一条指令通过将其原始值保存RBP到堆栈中来保存,然后第二条指令设置RBP为的原始值RSP。在此之后,堆栈看起来就像上面所描述的那样,采用了漂亮的ASCII艺术。

然后,该函数执行其任务,执行它想要执行的任何代码。如该图中所建议,它可以通过使用访问它是在栈上传递任何参数正从偏移RBP(即,RBP+x),它可以通过使用访问它已经为在栈上分配空间中的任何局部变量负从偏移RBP(即,RBP-x)。如果您了解堆栈在内存中向下增长(地址变小),则此偏移方案很有意义。

最后,样板结尾码结束的功能是:

leaveq
Run Code Online (Sandbox Code Playgroud)

或等效地:

mov %rbp, %rsp
pop %rbp
Run Code Online (Sandbox Code Playgroud)

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

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

您看到所有这些框架指针簿记的原因是因为您正在分析未优化的代码,因此从不遗漏框架指针,因为将其放在一起通常会使调试更加容易(并且执行速度并不是很重要的问题)。

RBP进入函数时之所以为0 的原因似乎是GDB的特性,而不是您真正需要关注的事情。正如Shift_Left在注释中指出的那样,Linux下的GDB在RSP将控制权移交给应用程序之前将所有寄存器(除外)预初始化为0。如果您已经在调试器之外运行了该程序,并且只是将初始值打印RBP到stdout,您会发现它将为非零值。

但是,再次,确切的值对您来说并不重要。了解上面的调用堆栈的示意图是关键。假定没有删除帧指针,编译器不知道何时生成序言和结语代码在输入时将具有什么值RBP,因为它不知道函数在调用堆栈中的最后位置。