2016-09-17 42 views
2

在功能輸入,標準的序言如何引用堆棧正確

push rbp 
mov rbp, rsp 
sub rsp, 128 ; large space for storing doubles, for example 

如何現在引用本地變量的局部變量,通過RSP +正偏移,或通過RBP +負偏移?

閱讀https://en.wikibooks.org/wiki/X86_Disassembly/Functions_and_Stack_Frames,的確很理解。 它寫入

... esp的值不能可靠地用於確定(使用適當的偏移量)特定局部變量的內存位置。爲了解決這個問題,許多編譯器使用ebp寄存器的負偏移來訪問局部變量。

爲什麼不可靠?直到這個問題,我是通過RSP訪問本地變量,像這樣:

mov rax, [rsp+.length] ; get length of array 
mov [rsp+8], rax ; store sum at the stack 

一切順利很好地使用RSP堆棧引用。

+1

堆棧指針*通常可用於確定堆棧上變量的地址。但是,如果函數使用可變長度數組或等價於'alloca()',則使用堆棧指針的偏移量可能不再可行。 – EOF

+0

@EOF,所以應該在所有情況下使用相對於rsp的尋址,除了兩個? –

回答

3

看看gcc的輸出。優化時默認爲,只有當函數使用可變長度數組或者需要將堆棧對齊到16B以上時才創建堆棧幀。

該wiki頁面基本上是錯誤的。沒有可怕的怪物讓它「不可靠」。唯一不能做到這一點的是當你需要修改RSP的數量不是彙編時間常數時。


但是,如果你做使push rbp/mov rbp, rsp堆棧幀,你應該使用RBP-相對尋址方式。效率更高,因爲[rsp + 8]需要額外的字節進行編碼(與[rbp - 8]相比)。使用RSP作爲基址寄存器的尋址模式總是需要一個SIB字節,即使沒有索引寄存器。

使用RSP-相對尋址方式的一點是,你可以避免浪費指示進行堆棧幀,所以RBP是另一個調用保存的寄存器(如RBX),您可以保存/恢復和使用任何你要。


的另一大優點是RBP相對尋址是,從RBP停留整個函數恆定的偏移給定的變量。與編譯器不同的是,我們小小的人類很容易被推送和彈出混淆,這會改變函數中的RSP。當然,64位代碼幾乎不會在序言和結尾之間的函數內改變RSP,因爲兩個ABI都通過寄存器中的參數。在序言/結尾中保存/恢復一些保存呼叫的寄存器(比如RBX或R12-R15)往往比在功能內部使用push/pop更好(並且肯定比循環內更好)。當你需要泄漏/重新加載時,mov隨機訪問通常是最好的。

在32位代碼中,在手寫代碼中製作堆棧幀通常更有意義,特別是,爲了可維護性。在64位代碼中,通常不是什麼問題。雖然用一對額外的push/pop保存/恢復一個額外的寄存器確實會改變堆棧佈局,如果任何參數在堆棧上傳遞的話(例如,一個大的struct按值,但是編寫你的函數來獲取一個const指針arg代替!)。

+0

非常有用,特別是關於效率的提示。所以在編譯優化(幀指針省略)的情況下,會得到更大的代碼,因爲需要更多的字節來編碼rsp尋址? –

+1

@BulatM .:有時省去了每個功能3或4條指令,可以平衡它。這是1 + 3個字節的序言,1 + 0或3個字節的尾聲。 (可選的'mov rsp,rbp',然後'pop rbp'或者一些編譯器使用LEAVE,它是1個字節,這只是3 uops IIRC,所以如果RSP沒有指向保存的RBP值,那麼這是一個不錯的選擇。函數的結尾) –

+2

當你手寫程序集時,棧指針可能會非常棘手,因爲本地變量可能沒有固定的棧指針偏移量。 – fuz