2017-06-21 94 views
0

已知%rsp指向堆棧幀的頂部,並且指向堆棧幀的基址。然後,我不明白爲什麼RBP%爲0x0在這段代碼:爲什麼%rbp指向沒有?

(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 

,爲什麼它很「節約」(推)%RBP堆棧,如果它指向什麼?

+0

分享您的原代碼 –

+2

其實,RSP指向棧幀的頂部,但程序框架和RBP指向程序框架頂部的底部。 Linux下的GDB開始將除RSP之外的所有寄存器設置爲NULL的應用程序。跟蹤到4000B4然後'x/xg $ rsp'將會工作。 –

+0

@Shift_Left你是對的。但要成爲事實,我無法理解這種行爲的原因。如果系統需要知道堆棧的範圍/大小,那麼%rbp從零開始的可能性如何?你不同意在程序開始時不知道堆棧幀的起始位置嗎? – alacerda

回答

4

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。在此之後,堆疊看起來完全像上面描述的那樣,在漂亮的ASCII藝術中。

然後函數就執行,在執行任何代碼就是了執行。如附圖中建議,可以通過使用積極偏移從RBPRBP+x)訪問它是在棧中傳遞的任何參數,並可以通過使用訪問任何局部變量分配給它的空間在堆棧上RBP偏移(即RBP-x)。如果你明白內存中的堆棧增長爲,則(地址變小),那麼這種抵消方案是有意義的。

最後,樣板尾聲代碼結束的功能是:

leaveq 

或者,等同地:

mov %rbp, %rsp 
pop %rbp 

該第一指令集RSP到的RBP值(工作值在整個函數的代碼中使用),並且第二條指令將「原始/保存的RBP」從堆棧中彈出,轉換爲RBP。這不是巧合,這恰恰是相對的,我們在上面看到的序言代碼中做了什麼。

但是請注意,這只是一個約定。除非ABI要求,否則編譯器可以自由使用RBP作爲通用寄存器,與堆棧指針無關。這是可行的,因爲編譯器可以在編譯時從RSP計算所需的偏移量,這是一種常見的優化,稱爲「幀指針省略」(或「幀指針省略」)。它是在32位模式下,如果可用的通用寄存器的數量是非常尤其常見,但有時候你會看到它在64位代碼,太。當編譯器忽略幀指針時,它不需要序言和尾聲代碼來操縱它,所以這也可以省略。

你看到這一切幀指針簿記的原因是因爲你分析未優化代碼,其中幀指針永遠不會省略掉,因爲有它周圍往往使得調試更容易(並且由於執行速度是不一個重大關切)。

進入你的功能後,RBP爲0的原因似乎是a peculiarity of GDB,而不是你真正需要關注的東西。由於註釋中的Shift_Left註釋,Linux下的GDB在將控制權交給應用程序之前,將所有寄存器(RSP除外)預初始化爲0。如果你在調試器外運行這個程序,並簡單地印刷的RBP初始值到標準輸出,你會看到,這將是非零。

但是,再說一次,確切的價值不應該的問題給你。瞭解上述調用堆棧的示意圖是關鍵。假設幀指針沒有被省略掉,編譯器時,它產生的序幕和尾聲代碼什麼RBP將有入境時,因爲它不知道在哪裏調用堆棧上的功能將最終被稱爲不知道。