理解呼叫棧最重要的兩點是:棧的結構,ebp暫存器的作用。
首先要認識到這樣兩個事實:
1、乙個函式呼叫動作可分解為:零到多個push指令(用於引數入棧),乙個call指令。call指令內部其實還暗含了乙個將返回位址(即call指令下一條指令的位址)壓棧的動作。
2、幾乎所有本地編譯器都會在每個函式體之前插入類似如下指令:push ebp; mov ebp esp;
即,在程式執行到乙個函式的真正函式體時,已經有以下資料順序入棧:引數,返回位址,ebp。
由此得到類似如下的棧結構(引數入棧順序跟呼叫方式有關,這裡以c語言預設的cdecl為例):
+| (棧底方向,高位位址) |
| .................... |
| .................... |
| 引數3 |
| 引數2 |
| 引數1 |
| 返回位址 |
-| 上一層[ebp] | <-------- [ebp]
「push ebp」「mov ebp esp」這兩條指令實在大有深意:首先將ebp入棧,然後將棧頂指標esp賦值給ebp。「mov ebp esp」這條指令表面上看是用esp把ebp原來的值覆蓋了,其實不然——因為給ebp賦值之前,原ebp值已經被壓棧(位於棧頂),而新的ebp又恰恰指向棧頂。
此時ebp暫存器就已經處於乙個非常重要的地位,該暫存器中儲存著棧中的乙個位址(原ebp入棧後的棧頂),從該位址為基準,向上(棧底方向)能獲取返回位址、引數值,向下(棧頂方向)能獲取函式區域性變數值,而該位址處又儲存著上一層函式呼叫時的ebp值!
一般而言,ss:[ebp+4]處為返回位址,ss:[ebp+8]處為第乙個引數值(最後乙個入棧的引數值,此處假設其占用4位元組記憶體),ss:[ebp-4]處為第乙個區域性變數,ss:[ebp]處為上一層ebp值。
由於ebp中的位址處總是「上一層函式呼叫時的ebp值」,而在每一層函式呼叫中,都能通過當時的ebp值「向上(棧底方向)能獲取返回位址、引數值,向下(棧頂方向)能獲取函式區域性變數值」。
如此形成遞迴,直至到達棧底。這就是函式呼叫棧。
編譯器對ebp的使用實在太精妙了。
從當前ebp出發,逐層向上找到所有的ebp是非常容易的:
unsigned int _ebp;
__asm _ebp, ebp;
while (not stack bottom)
函式呼叫堆疊變化分析
比如 我們有這樣乙個c函式 include long test int a,int b void main 寫成32位彙編就是這樣 386 model flat,stdcall 這裡我們用stdcall 就是函式引數 壓棧的時候從最後乙個開始壓,和被呼叫函式負責清棧 option casemap n...
函式呼叫堆疊變化分析
跟乙個朋友談堆疊的時候 就寫下了這段文字,順便發到這裡給需要的看看吧 彙編初學者比較頭痛的乙個問題 比如 我們有這樣乙個c函式 include long test int a,int b void main 寫成32位彙編就是這樣 386 model flat,stdcall 這裡我們用stdcal...
函式呼叫堆疊
一 函式呼叫堆疊 認真體會每一行指令位址!include intsum int a,int b mov ebp,esp 讓esp回退到ebp的位置,回退棧幀的過程中,沒有對棧幀中的值進行清0的操作 pop ebp 出棧並把出棧的值賦給ebp int main 下圖為上面示例函式,程式在sum函式中,...