理解呼叫棧最重要的兩點是:棧的結構,ebp暫存器的作用。
首先要認識到這樣兩個事實:
2、幾乎所有本地編譯器都會在每個函式體之前插入類似如下指令:push ebp; mov esp ebp;
+| (棧底方向,高位位址) |
| .................... |
| .................... |
| 引數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**:
int fun(int para)
void main()
彙編**:
1:2:
3: int fun(int para)
4: 0040b831 pop edi
0040b832 pop esi
0040b833 pop ebx
0040b834 mov esp,ebp
0040b836 pop ebp
0040b837 ret
10:11: void main()
12:
0040b7b2 pop edi
0040b7b3 pop esi
0040b7b4 pop ebx
0040b7b5 add esp,40h
0040b7b8 cmp ebp,esp
0040b7ba call __chkesp (004010a0)
0040b7bf mov esp,ebp
0040b7c1 pop ebp
0040b7c2 ret
歸納呼叫fun的過程為:
(1)引數入棧
(2)呼叫fun,保護斷點,eip入棧(內部完成,無彙編**)
(3)保護暫存器ebp
(4)定位新的ebp,此後ebp一般不變
(5)保護ebx,esi,edi
(6)初始化40bytes間隔空間和fun內部變數
此過程,資料空間變化如下:
(1)引數入棧
|低位址|...|引數值|...|高位址| ...
^esp指向此 ^ebp指向某一位置
(2)呼叫fun,保護斷點,eip入棧(內部完成,無彙編**)
|低位址|...|fun返回函式位址|引數值|...|高位址| ...
^esp指向此 ^ebp指向某一位置
(3)保護暫存器ebp
|低位址|...|ebp入棧值|fun返回函式位址|引數值|...|高位址| ...
^esp指向此 ^ebp指向某一位置
(4)定位新的ebp,此後ebp一般不變
|低位址|...|ebp入棧值|fun返回函式位址|引數值|...|高位址| ...
^esp和ebp指向此
(5)保護ebx,esi,edi
|低位址|...|edi入棧值|esi入棧值|ebx入棧值|40bytes間隔空間|fun內部變數值|ebp入棧值|fun返回函式位址|引數值|...|高位址| ...
^esp指向此 ^ebp指向(ebp入棧值)
(6)初始化40bytes間隔空間和fun內部變數
曾看到一程式:
#include "stdio.h"
int fun()
int main(int argc, char* argv)
下面是其中兩段彙編**:
15: int i=1;
0040b558 mov dword ptr [ebp-4],1
16:17: fun();
0040b55fcall @ilt+10(fun) (0040100f)
18: i++;
0040b564mov eax,dword ptr [ebp-4]
0040b567add eax,1
0040b56a mov dword ptr [ebp-4],eax
5: int a=0;
0040b508 mov dword ptr [ebp-4],0
6: int* p=&a;
0040b50f lea eax,[ebp-4]
0040b512 mov dword ptr [ebp-8],eax
7: p=p+2;
0040b515 mov ecx,dword ptr [ebp-8]
0040b518 add ecx,8
0040b51b mov dword ptr [ebp-8],ecx
8: *p=*p+3;
0040b51e mov edx,dword ptr [ebp-8]
0040b521 mov eax,dword ptr [edx]
0040b523 add eax,3
0040b526 mov ecx,dword ptr [ebp-8]
0040b529 mov dword ptr [ecx],eax
9:10: return 0;
0040b52b xor eax,eax
11: }
其資料空間為:
|低位址|...|40bytes間隔空間|p的值|a的值|ebp入棧值|fun返回函式位址|引數值|...|高位址| ...
^ebp指向|ebp入棧值|
由於p=&a,p指向|a的值|,則執行p=p+2後,p指向|fun返回函式位址|
跟蹤程式|fun返回函式位址|=0040b564,執行的*p=*p+3,|fun返回函式位址|=0040b567
即fun返回後程式從位址0040b567執行,跳過0040b564處的**mov eax,dword ptr [ebp-4],使得結果i=1,而不是i=2。
函式呼叫棧
當程式進行函式呼叫的時候,系統會用到下面三種暫存器 3.ebp ebp暫存器裡儲存的是棧基址,是在函式呼叫之前,由esp賦值給ebp的。棧底方向,高位位址 call fun arg1,arg2,arg3 修改esp,棧向下增長,引數入棧,返回位址入棧 arg3 arg2 arg1 返回位址 上一層e...
棧 函式呼叫
編譯以下程式,分析此程式以得出棧的精髓 1 主函式被上層呼叫者呼叫後,執行push ebp,esp 4 因為ebp入棧 ebp值沒有改變,值得注意的是剛開始分配站的時候,第乙個入棧的是return,主函式的返回位址,然後是ebp 2 然後是mov ebp,esp,將esp的值賦給ebp,該語句未執行...
函式呼叫 函式棧
函式呼叫大家都不陌生,呼叫者向被呼叫者傳遞一些引數,然後執行被呼叫者的 最後被呼叫者向呼叫者返回結果,還有大家比較熟悉的一句話,就是函式呼叫是在棧上發生的,那麼在計算機內部到底是如何實現的呢?對於程式,編譯器會對其分配一段記憶體,在邏輯上可以分為 段,資料段,堆,棧 段 儲存程式文字,指令指標eip...