函数调用栈
函數(shù)調(diào)用棧比較有意思
作者:liigo
原文鏈接:http://blog.csdn.net/liigo/archive/2006/12/23/1456938.aspx
轉(zhuǎn)載請注明出處:http://blog.csdn.net/liigo
?
昨天和海洋一塊研究了下函數(shù)調(diào)用棧,順便寫兩句。不足或錯誤之處請包涵!
理解調(diào)用棧最重要的兩點是:棧的結(jié)構(gòu),EBP寄存器的作用。
首先要認識到這樣兩個事實:
1、一個函數(shù)調(diào)用動作可分解為:零到多個PUSH指令(用于參數(shù)入棧),一個CALL指令。CALL指令內(nèi)部其實還暗含了一個將返回地址(即CALL指令下一條指令的地址)壓棧的動作。
2、幾乎所有本地編譯器都會在每個函數(shù)體之前插入類似如下指令:PUSH EBP; MOV EBP ESP;
即,在程序執(zhí)行到一個函數(shù)的真正函數(shù)體時,已經(jīng)有以下數(shù)據(jù)順序入棧:參數(shù),返回地址,EBP。
由此得到類似如下的棧結(jié)構(gòu)(參數(shù)入棧順序跟調(diào)用方式有關(guān),這里以C語言默認的CDECL為例):
+| (棧底方向,高位地址) |
?| .................... |
?| .................... |
?| 參數(shù)3??????????????? |
?| 參數(shù)2??????????????? |
?| 參數(shù)1??????????????? |
?| 返回地址???????????? |
-| 上一層[EBP]????????? | <-------- [EBP]
“PUSH EBP”“MOV EBP ESP”這兩條指令實在大有深意:首先將EBP入棧,然后將棧頂指針ESP賦值給EBP。“MOV EBP ESP”這條指令表面上看是用ESP把EBP原來的值覆蓋了,其實不然——因為給EBP賦值之前,原EBP值已經(jīng)被壓棧(位于棧頂),而新的EBP又恰恰指向棧頂。
此時EBP寄存器就已經(jīng)處于一個非常重要的地位,該寄存器中存儲著棧中的一個地址(原EBP入棧后的棧頂),從該地址為基準,向上(棧底方向)能獲取返回地址、參數(shù)值,向下(棧頂方向)能獲取函數(shù)局部變量值,而該地址處又存儲著上一層函數(shù)調(diào)用時的EBP值!
一般而言,ss:[ebp+4]處為返回地址,ss:[ebp+8]處為第一個參數(shù)值(最后一個入棧的參數(shù)值,此處假設(shè)其占用4字節(jié)內(nèi)存),ss:[ebp-4]處為第一個局部變量,ss:[ebp]處為上一層EBP值。
由于EBP中的地址處總是“上一層函數(shù)調(diào)用時的EBP值”,而在每一層函數(shù)調(diào)用中,都能通過當時的EBP值“向上(棧底方向)能獲取返回地址、參數(shù)值,向下(棧頂方向)能獲取函數(shù)局部變量值”。
如此形成遞歸,直至到達棧底。這就是函數(shù)調(diào)用棧。
編譯器對EBP的使用實在太精妙了。
從當前EBP出發(fā),逐層向上找到所有的EBP是非常容易的:
unsigned int _ebp;
__asm _ebp, ebp;
while (not stack bottom)
{
??? //...
??? _ebp = *(unsigned int*)_ebp;
}
如果要寫一個簡單的調(diào)試器的話,注意需在被調(diào)試進程(而非當前進程——調(diào)試器進程)中讀取內(nèi)存數(shù)據(jù)。?
總結(jié)
- 上一篇: 使用正則表達式对URL进行解析
- 下一篇: 三层架构与四大天王之——查