Why does %rbp point to nothing?

扶醉桌前 提交于 2019-12-10 12:16:36

问题


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?


回答1:


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.



来源:https://stackoverflow.com/questions/44687662/why-does-rbp-point-to-nothing

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!