为什么%rbp指向什么? [英] Why does %rbp point to nothing?

查看:727
本文介绍了为什么%rbp指向什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

众所周知,%rsp 指向堆栈框架的顶部,%rbp 指向堆栈框架的底部。然后我无法理解为什么%rbp是0x0 在这段代码中:

 (gdb )x / 4xg $ rsp 
0x7fffffffe170:0x00000000004000dc 0x0000000000000010
0x7fffffffe180:0x0000000000000001 0x00007fffffffe487
(gdb)disas HelloWorldProc
函数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>:系统调用
0x00000000004000cf< + 31>:leaveq
0x00000000004000d0< + 32> ;: retq
汇编器转储结束。
(gdb)x / xg $ rbp
0x0:无法访问地址0x0处的内存

为什么如果它指向什么都保存(推送)%rbp 到堆栈?

解决方案

RBP 是一个通用寄存器,因此它可以包含您(或您的编译器)希望它包含的任何值。按照惯例 是用 RBP 指向过程框架。按照这个惯例,堆栈看起来像这样:

 低| ============= ======= | 
地址|未使用的空间|
| |
| ==================== | ←RSP点这里
↑|函数的|
↑|局部变量|
↑| | ↑RBP - x
方向| -------------------- | ←RBP指向这里
的堆栈|原始/保存的RBP | ↓RBP + x
增长| -------------------- |
↑|返回指针|
↑| -------------------- |
↑|函数的|
|参数|
| |
| ==================== |
|父| |
|函数的数据|
| ==================== |
|祖父母|
高| |函数的数据|
地址| ==================== |

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

  push%rbp 
mov%rsp,%rbp

第一条指令将原始值 RBP 压入堆栈,然后第二条指令将 RBP 改为原始值 RSP 。在这之后,这个堆栈看起来就像上面描述的一样,在美丽的ASCII艺术中。



然后函数执行它的任务,执行任何想要执行的代码。如图所示,它可以通过使用来自 RBP )的 em>, RBP + x ),并且它可以通过使用来自<$ em>的负偏移量来访问它为堆栈分配空间的任何局部变量c $ c> RBP ( ie RBP-x )。如果你知道堆栈在内存中向下扩展(地址变小),那么这个抵消方案是合理的。

最后,样板文件<

  leaveq 
结束函数的代码如下:

或等价地:

  mov%rbp, %rsp 
pop%rbp

第一条指令设置 RSP 改为 RBP (在整个函数代码中使用的工作值)的值,第二条指令将原始/保存的RBP弹出堆栈,转换为 RBP 。这不是巧合,这正是我们在上面看到的序言代码中完成的

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



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

进入你的函数时, RBP 为0的原因似乎是 GDB的特性,而不是你真的需要关注的东西。由于注释中的Shift_Left注释,Linux下的GDB在将控制交给应用程序之前,将所有寄存器(除 RSP )之外的所有寄存器预初始化为0。如果你已经在调试器外部运行这个程序,并且简单地将 RBP 的初始值打印到stdout,你会发现它不是零。



但是,确切的价值对你来说应该不重要。了解上述调用堆栈的示意图是关键。假设帧指针没有被消除,编译器不知道什么时候它会生成序言和尾部代码 RBP 在输入时将具有什么值,因为它不知道调用堆栈上的哪个位置,函数最终会被调用。


It is known that %rsp points to the top of the stack frame and %rbp points to the base of the stack frame. Then I can't understand why %rbp is 0x0 in this piece of code:

(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

And why is it "saving" (pushing) %rbp to the stack if it points to nothing?

解决方案

RBP is a general-purpose register, so it can contain any value that you (or your compiler) wants it to contain. It is only by convention that RBP is used to point to the procedure frame. According to this convention, the stack looks like this:

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      |====================|

As such, the boilerplate prologue code for a function is:

push   %rbp
mov    %rsp, %rbp

This first instruction saves the original value of RBP by pushing it onto the stack, and then the second instruction sets RBP to the original value of RSP. After this, the stack looks exactly like the one depicted above, in the beautiful ASCII art.

The function then does its thing, executing whatever code it wants to execute. As suggested in the drawing, it can access any parameters it was passed on the stack by using positive offsets from RBP (i.e., RBP+x), and it can access any local variables it has allocated space for on the stack by using negative offsets from RBP (i.e., RBP-x). If you understand that the stack grows downward in memory (addresses get smaller), then this offsetting scheme makes sense.

Finally, the boilerplate epilogue code to end a function is:

leaveq

or, equivalently:

mov %rbp, %rsp
pop %rbp

This first instruction sets RSP to the value of RBP (the working value used throughout the function's code), and the second instruction pops the "original/saved RBP" off the stack, into RBP. It is no coincidence that this is precisely the opposite of what was done in the prologue code we looked at above.

Note, though, that this is merely a convention. Unless required by the ABI, the compiler is free to use RBP as a general-purpose register, with no relation to the stack pointer. This works because the compiler can just calculate the required offsets from RSP at compile time, and it is a common optimization, known as "frame pointer elision" (or "frame pointer omission"). It is especially common in 32-bit mode, where the number of available general-purpose registers is extremely small, but you'll sometimes see it in 64-bit code, too. When the compiler has elided the frame pointer, it doesn't need the prologue and epilogue code to manipulate it, so this can be omitted, too.

The reason you see all of this frame-pointer book-keeping is because you're analyzing unoptimized code, where the frame pointer is never elided because having it around often makes debugging easier (and since execution speed is not a significant concern).

The reason why it RBP is 0 upon entry to your function appears to be a peculiarity of GDB, and not something that you really need to concern yourself with. As Shift_Left notes in the comments, GDB under Linux pre-initializes all registers (except RSP) to 0 before handing off control to an application. If you had run this program outside of the debugger, and simply printed the initial value of RBP to stdout, you'd see that it would be non-zero.

But, again, the exact value shouldn't matter to you. Understanding the schematic drawing of the call stack above is the key. Assuming that frame pointers have not been elided, the compiler has no idea when it generates the prologue and epilogue code what value RBP will have upon entry, because it doesn't know where on the call stack the function will end up being called.

这篇关于为什么%rbp指向什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆