Linux 为什么%rbp没有指向任何东西?
众所周知,%rsp指向堆栈框架的顶部,%rbp指向堆栈框架的底部。然后我不明白为什么在这段代码中%rbp是0x0:Linux 为什么%rbp没有指向任何东西?,linux,assembly,gdb,x86-64,stack-pointer,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 H
(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
(gdb)x/4xg$rsp
0x7FFFFFE170:0x00000000004000dc 0x0000000000000010
0x7FFFFFE180:0x0000000000000001 0x00007FFFFFE487
(gdb)disas HelloWorldProc
函数HelloWorldProc的汇编程序代码转储:
=>0x00000000004000b0:推送%rbp
0x00000000004000b1:mov%rsp,%rbp
0x00000000004000b4:mov$0x1,%eax
0x00000000004000b9:mov$0x1,%edi
0x00000000004000be:movabs$0x6000ec,%rsi
0x00000000004000c8:mov$0xd,%edx
0x00000000004000cd:syscall
0x00000000004000cf:leaveq
0x00000000004000d0:retq
汇编程序转储结束。
(gdb)x/xg$rbp
0x0:无法访问地址0x0处的内存
如果rbp不指向任何内容,为什么要将其“保存”(推入)%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
leaveq
第一条指令将RBP
的原始值推送到堆栈上保存,然后第二条指令将RBP
设置为RSP
的原始值。在这之后,堆栈看起来与上面描述的完全一样,采用了漂亮的ASCII艺术
然后,函数执行它自己的事情,执行它想要执行的任何代码。如图所示,它可以通过使用RBP
(即RBP+x
)的正偏移量访问它在堆栈上传递的任何参数,并且可以通过使用RBP
(即RBP-x
)的负偏移量访问它在堆栈上分配空间的任何局部变量。如果您理解堆栈在内存中向下增长(地址变小),那么这种偏移方案是有意义的
最后,结束函数的样板结束代码是:
push %rbp
mov %rsp, %rbp
leaveq
或者,相当于:
mov %rbp, %rsp
pop %rbp
第一条指令将RSP
设置为RBP
(整个函数代码中使用的工作值),第二条指令将“原始/保存的RBP”从堆栈中弹出,放入RBP
。这与我们在上面看到的序言代码中所做的恰恰相反,这并非巧合
但请注意,这只是一个惯例。除非ABI要求,否则编译器可以自由使用RBP
作为通用寄存器,与堆栈指针无关。这是因为编译器可以在编译时计算从RSP
所需的偏移量,这是一种常见的优化,称为“帧指针省略”(或“帧指针省略”)。它在32位模式下尤其常见,在32位模式下,可用的通用寄存器数量非常少,但有时您也会在64位代码中看到它。当编译器省略了帧指针时,它不需要序言和尾声代码来操作它,因此这也可以省略
您看到所有这些帧指针簿记的原因是因为您正在分析未优化的代码,其中帧指针永远不会被忽略,因为有它在身边通常会使调试更容易(而且因为执行速度不是一个重要的问题)
进入函数时,RBPRBP
为0的原因似乎是,而不是您真正需要关心的问题。正如Shift_在注释中留下的注释,Linux下的GDB在将控制权移交给应用程序之前将所有寄存器(RSP除外)预初始化为0。如果您在调试器之外运行此程序,并且只需将RBP
的初始值打印到stdout,您就会看到它将是非零的
但是,同样,准确的数值对你来说并不重要。理解上面调用堆栈的示意图是关键。假设未省略帧指针,编译器不知道何时生成序言和尾声代码,因为它不知道函数将在调用堆栈的何处被调用。
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
leaveq
第一条指令将RBP
的原始值推送到堆栈上保存,然后第二条指令将RBP
设置为RSP
的原始值。在这之后,堆栈看起来与上面描述的完全一样,采用了漂亮的ASCII艺术
然后,函数执行它自己的事情,执行它想要执行的任何代码。如图所示,它可以通过使用RBP
(即RBP+x
)的正偏移量访问它在堆栈上传递的任何参数,并且可以通过使用RBP
(即RBP-x
)的负偏移量访问它在堆栈上分配空间的任何局部变量。如果您理解堆栈在内存中向下增长(地址变小),那么这种偏移方案是有意义的
最后,结束函数的样板结束代码是:
push %rbp
mov %rsp, %rbp
leaveq
或者,相当于:
mov %rbp, %rsp
pop %rbp
第一条指令将RSP
设置为RBP
(整个函数代码中使用的工作值),第二条指令将“原始/保存的RBP”从堆栈中弹出,放入RBP
。这并非巧合