我遇到了一些行內匯編的問題。我正在撰寫一個編譯器,它正在編譯為匯編,為了可移植性,我讓它在 C 中添加了 main 函式,并且只使用行內匯編。盡管即使是最簡單的行內匯編也會給我一個段錯誤。謝謝你的幫助
int main(int argc, char** argv) {
__asm__(
"push $1\n"
);
return 0;
}
uj5u.com熱心網友回復:
TLDR 在底部。注意:這里的一切都是假設x86_64.
這里的問題是編譯器實際上永遠不會使用push或pop在函式體中(序言/結語除外)。
考慮這個例子。
當函式開始時,會在序言中的堆疊上留出空間:
push rbp
mov rbp, rsp
sub rsp, 32
這將創建 32 位元組的空間main。然后注意在整個函式中,不是將專案推入堆疊,而是mov通過以下偏移量將它們放入堆疊rbp:
mov DWORD PTR [rbp-20], edi
mov QWORD PTR [rbp-32], rsi
mov DWORD PTR [rbp-4], 2
mov DWORD PTR [rbp-8], 5
這樣做的原因是它允許變數隨時隨地存盤,并隨時隨地加載,而無需大量push/ pops。
考慮使用push和存盤變數的情況pop。假設一個變數存盤在函式的早期,我們稱之為 this foo。堆疊上的8個變數以后,你需要foo,你應該如何訪問它?
好吧,您可以彈出所有內容直到foo,然后將所有內容推回,但這代價高昂。
當您有條件陳述句時,它也不起作用。假設一個變數只有foo在某個特定值時才會被存盤。現在您有一個條件,其中堆疊指標可以位于它之后的兩個位置之一!
出于這個原因,編譯器總是喜歡使用rbp - N來存盤變數,因為在函式中的任何一點,變數仍將位于rbp - N.
注意:在不同的 ABI(例如 i386 系統 V)上,引數的引數可能會在堆疊上傳遞,但這不是什么大問題,因為 ABI 通常會指定應如何處理。同樣,以 i386 system V 為例,函式的呼叫約定將類似于:
push edi ; 2nd argument to the function.
push eax ; 1st argument to the function.
call my_func
; here, it can be assumed that the stack has been corrected
那么,為什么push實際上會導致問題?好吧,我會在代碼中添加一個小asm片段
在函式的末尾,我們現在有以下內容:
push 64
mov eax, 0
leave
ret
由于推送到堆疊,現在有兩件事失敗了。
第一個是leave指令(見這個執行緒)
leave指令將嘗試pop的價值rbp是被存盤在函式的開頭(注意只push編譯器在開始產生的:push rbp)。
這是為了在呼叫者的堆疊幀之后保留main。通過推送到堆疊,在我們的例子rbp中現在將被設定為64,因為最后推送的值是64。當被叫方main恢復它的執行,并試圖在說訪問的值,rbp - 8將發生崩潰,因為rbp - 8是0x38十六進制,這是一個無效的地址。
但這假設被呼叫者甚至可以恢復執行!
之后rbp有它的價值與無效值恢復,堆疊上的下一件事將是原來的值rbp。
該ret指令將從pop堆疊中獲取一個值,并回傳到該地址...
請注意這可能有點問題?
CPU 將嘗試跳轉到rbp函式開始時存盤的值!
在幾乎所有現代程式中,堆疊都是一個“不可執行”區域(參見此處),嘗試從那里執行代碼會立即導致崩潰。
因此,TLDR:推入堆疊違反了編譯器所做的假設,最重要的是關于函式的回傳地址。這種違規導致程式執行到堆疊上結束(一般),這會導致崩潰
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/379848.html
