首先,我編譯然後拆開你的函數,所以你可以看到在ASM水平實際上是怎麼回事。我禁用優化和編譯爲32位代碼,讓事情變得簡單:
Dump of assembler code for function add:
0x080483cb <+0>: push %ebp
0x080483cc <+1>: mov %esp,%ebp
0x080483ce <+3>: sub $0x10,%esp
0x080483d1 <+6>: mov 0x8(%ebp),%edx
0x080483d4 <+9>: mov 0xc(%ebp),%eax
0x080483d7 <+12>: add %edx,%eax
0x080483d9 <+14>: mov %eax,-0x4(%ebp)
0x080483dc <+17>: mov -0x4(%ebp),%eax
0x080483df <+20>: leave
0x080483e0 <+21>: ret
End of assembler dump.
嘗試看看上面的拆卸,並承認它正在做什麼,以及它如何符合你的C代碼。現在回答你的問題。
現在Prolog部分:將舊的EBP地址壓入堆棧,並使EBP指向ESP。最後,局部變量壓入堆棧[難道這些只是存儲它們的值,其中的地址嗎?]
這裏序言去從0x080483cb <+0>
到包括0x080483ce <+3>
。 首先我們用push %ebp; mov %esp,%ebp
創建一個框架,然後我們爲sub $0x10,%esp
的堆棧上的局部變量分配0x10字節的空間。該指令所做的只是將堆棧指針向下移動0x10個字節。它不存儲任何值,如果我們想要的話,它只留下一些我們可以用於局部變量的空間(我們將看到編譯器甚至沒有使用它)。
接下來我們有該函數的實際邏輯。 首先,我們從堆棧中兩個參數x和y加載到寄存器:
0x080483d1 <+6>: mov 0x8(%ebp),%edx
0x080483d4 <+9>: mov 0xc(%ebp),%eax
我們把它們相加:
0x080483d7 <+12>: add %edx,%eax
現在我們將結果存儲在一個局部變量。該局部變量實際上只是我們在prolog中分配的堆棧空間。我們分配給本地變量爲0x10字節,而在這裏我們只用了前4個字節來存儲加法的結果:
0x080483d9 <+14>: mov %eax,-0x4(%ebp)
而且因爲沒有任何的優化,我們立即從本地加載正確的結果變回一個寄存器,這樣我們可以退貨:
0x080483dc <+17>: mov -0x4(%ebp),%eax
正如你所看到的代碼是非常低效的,但至少它很容易閱讀。 現在只有收尾留,這是很簡單的:
0x080483df <+20>: leave
0x080483e0 <+21>: ret
的leave
破壞我們在序言中創建的幀,ret
返回到調用函數的下一條指令。
Epilog是堆棧要解開當前幀的時候。 ESP移至EBP,因此局部變量不可訪問(通常)。舊的EBP彈出堆棧,並指向它的原始地址。 ESP移動指向保存的EIP,這是在添加(3,4)被調用之前的位置。
在給出的解釋中,最後一部分是返回指令將保存的EIP值彈回EIP寄存器。 [當然,這不是函數中的return語句,而是機器級別的ret語句,對吧?]
函數中的return語句對應於機器級別的ret指令。這是直接翻譯。 請記住,您的計算機不直接運行C代碼,所有該C編譯爲機器代碼第一,並且這裏的ret
指令確實是彈出EIP。
最後一個問題,有人可以解釋一下當函數中的代碼正在執行時,以及在所有那些調用,序言和epilog期間發生的事情發生了什麼?或者提供一個清晰的解釋的好鏈接?
您在上面看到的反彙編是計算機運行的粗略文本表示形式。 EIP包含計算機將運行的下一條指令的地址。當程序運行時,它存儲在內存中的某個地方,EIP直接指向內存中的指令。
所以計算機將按照寫入順序運行函數,並且prolog和epilog是函數的一部分。
prolog和epilog是一個約定,但它們只是代碼。如果你願意,你可以完全刪除序言和寫一個瘋狂的結語,它也可以工作。
我建議你去玩反彙編和調試器,以熟悉它是如何工作的。這並不困難,也很合理。
防範漏洞的方法是防止輸入溢出分配給它的空間。像'int i;'或'char * ptr'這樣的函數參數和局部變量不能溢出堆棧,但是像char [20];這樣的數組和可以使用動態分配給char * ptr的內存的數組可以溢出在堆上分配。 –
爲什麼不編譯程序並使用調試器逐步完成某個功能?你會比我們在這裏可以告訴你的任何東西都學到更多。 – JS1
執行x86'call'指令後,'call'後面的指令地址被壓入堆棧。函數局部變量通常不會被壓入堆棧 - 相反,堆棧指針會減少以爲當地人分配所需的存儲空間,這些存儲空間將在稍後進行初始化。而'return'確實是一個彙編指令。 –