C 函式呼叫過程深入分析

2021-09-10 02:16:28 字數 3914 閱讀 1576

0. 引言

首先對三個常用的暫存器做一下說明,eip是指令指標,即指向下一條即將執行的指令的位址;ebp為基址指標,常用來指向棧底;esp為棧指標,常用來指向棧頂。

看下面這個簡單的程式並在vc 6.0中檢視並分析彙編**。

圖11. 函式呼叫

g_func函式呼叫的彙編**如圖2:

圖2首先是三條push指令,分別將三個引數壓入棧中,可以發現引數的壓棧順序是從右向左的。這時我們可以檢視棧中的資料驗證一下。如圖3所示,從右邊的實時暫存器表中我們可以看到esp(棧頂指標)值為0x0012fef0,然後從中間的記憶體表中找到記憶體位址0x0012fef0處,我們可以看到記憶體中依次儲存了0x00000001(即引數1),0x00000002(即引數2),0x00000003(即引數3),即此時棧頂儲存的是三個引數值,說明壓棧成功。

圖3然後可以看到call指令跳轉到位址0x00401005,那麼該位址處是什麼呢?我們繼續跟蹤一下,在圖4中我們看到這裡又是一條跳轉指令,跳轉到0x00401030。我們再看一下位址0x00401030處,在圖5中可以看到這才是真正的g_func函式,0x00401030是該函式的起始位址,這樣就實現了到g_func函式的跳轉。

圖4

圖52. 儲存現場

此時我們再來檢視一下棧中的資料,如圖6所示,此時的esp(棧頂)值為0x0012feec,在記憶體表中我們可以看到棧頂存放的是0x00401093,下面還是前面壓棧的引數1,2,3,也就是執行了call指令後,系統預設的往棧中壓入了乙個資料(0x00401093),那麼它究竟是什麼呢?我們再看到圖3,call指令後面一條指令的位址就是0x00401093,實際上就是函式呼叫結束後需要繼續執行的指令位址,函式返回後會跳轉到該位址。這也就是我們常說的函式中斷前的「保護現場」。這一過程是編譯器隱含完成的,實際上是將eip(指令指標)壓棧,即隱含執行了一條push eip指令,在中斷函式返回時再從棧中彈出該值到eip,程式繼續往下執行。

圖6繼續往下看,進入g_func函式後的第一條指令是push ebp,即將ebp入棧。因為每乙個函式都有自己的棧區域,所以棧基址也是不一樣的。現在進入了乙個中斷函式,函式執行過程中也需要ebp暫存器,而在進入函式之前的main函式的ebp值怎麼辦呢?為了不被覆蓋,將它壓入棧中儲存。

下一條mov ebp, esp 將此時的棧頂位址作為該函式的棧基址,確定g_func函式的棧區域(ebp為棧底,esp為棧頂)。

再往下的指令是sub esp, 48h,指令的字面意思是將棧頂指標往上移動48h byte。那為什麼要移動呢?這中間的記憶體區域用來做什麼呢?這個區域為間隔空間,將兩個函式的棧區域隔開一段距離,如圖7所示。而該間隔區域的大小固定為40h,即64byte,然後還要預留出儲存區域性變數的記憶體區域。g_func函式有兩個區域性變數x和y,所以esp需移動的長度為40h+8=48h。

圖7接下來的幾行指令(如下)是將剛才留出的48h的記憶體區域賦值為0cccccccch。

00401039   lea        edi,[ebp-48h]

0040103c   mov      ecx,12h

00401041   mov            eax,0cccccccch

00401046   rep stos    dword ptr [edi] 。

接下來三條壓棧指令,分別將ebx,esi,edi壓入棧中,這也是屬於「保護現場」的一部分,這些是屬於main函式執行的一些資料。ebx,esi,edi分別為基址暫存器,源變址暫存器,目的變址暫存器。

3. 執行子函式

繼續往下看,接下來是區域性變數的x和y的賦值,看彙編指令中是怎樣去計算x和y的記憶體位址的呢?如圖8所示,是基於ebp去計算的,分別是[ebp-4]和[ebp-8]。我們檢視記憶體表可以看到相應的記憶體區域已經存入了0x11111111和0x22222222。

圖8此時我們對整個記憶體區域中儲存的內容應該非常清晰了(如圖9所示)。

圖94. 恢復現場

這時子函式部分的**已經執行完,繼續往下看,編譯器將會做一些事後處理的工作(如圖10所示)。首先是三條出棧指令,分別從棧頂讀取edi,esi和ebx的值。從圖9的記憶體資料分布我們可以得知此時棧頂的資料確實是edi,esi和ebx,這樣就恢復了呼叫前的edi,esi和ebx值,這是「恢復現場」的一部分。

圖10

第四條指令是mov esp, ebp 即將ebp的值賦給esp。那這是什麼意思呢?看看圖9的記憶體資料分布,我們就能很明白了,這條語句是讓esp指向ebp所指的記憶體單元,也就是讓esp跳過了一段區域,很明顯跳過的恰好是間隔區域和區域性資料區域,因為函式已經退出了,這兩個區域都已經沒有用處了。實際上這條語句是進入函式時建立間隔區域的語句 sub esp, 48h的相反操作。

再往下是pop ebp,我們從圖9的記憶體資料分布可以看出此時棧頂確實是儲存的前ebp值,這樣就恢復了呼叫前的ebp值,這也是「恢復現場」的一部分。該指令執行完後,記憶體資料分布如圖11所示。

圖11

再往下是一條ret指令,即返回指令,他會怎麼處理呢?注意在執行ret指令前的esp值和eip值(如圖12所示),esp指向棧頂的0x00401093,eip的值是0x0040105c(即ret指令的位址)。

圖12執行ret指令後我們來檢視esp和eip值(如圖13所示),此時esp為0012fef0,即往下移動了4byte。顯然此處編譯器隱含的執行了一條pop指令。再來看一下eip的值,變為了0x00401093,這個值怎麼這麼熟悉呢!它實際上就是棧頂的4byte資料,所以這裡隱含執行的指令應該是pop eip。而這個值就是前面講到過的,在呼叫call指令前壓棧的call的下一條指令的位址。從圖13中可以看出,正是因為eip的值變成了0x00401093,所以程式跳轉到了call指令後面的一條指令,又回到了中斷前的地方,這就是所謂的恢復斷點。

圖13還沒有完全結束,此時還有最後一條指令add esp, 0ch。這個就很簡單了,從圖13中可以看出現在棧頂的資料是1,2,3,也就是函式呼叫前壓入的三個實參。這是函式已經執行完了,顯然這三個引數沒有用處了。所以add esp, 0ch就是讓棧頂指標往下移動12byte的位置。為什麼是12byte呢,很簡單,因為入棧的是3個int資料。這樣由於函式呼叫在棧中新增的所有資料都已清除,棧頂指標(esp)真正回到了函式呼叫前的位置,所有暫存器的值也恢復到了函式呼叫之前。

posted @

2014-09-26 00:53

00000000o 閱讀(

...)

編輯收藏

C 函式呼叫過程深入分析

0.引言 首先對三個常用的暫存器做一下說明,eip是指令指標,即指向下一條即將執行的指令的位址 ebp為基址指標,常用來指向棧底 esp為棧指標,常用來指向棧頂。看下面這個簡單的程式並在vc 6.0中檢視並分析彙編 圖11.函式呼叫 g func函式呼叫的彙編 如圖2 圖2首先是三條push指令,分...

C 高階 C 函式呼叫過程深入分析

c 函式呼叫過程深入分析 0.引言 首先對三個常用的暫存器做一下說明,eip是指令指標,即指向下一條即將執行的指令的位址 ebp為基址指標,常用來指向棧底 esp為棧指標,常用來指向棧頂。看下面這個簡單的程式並在vc 6.0中檢視並分析彙編 1.函式呼叫 g func函式呼叫的彙編 如圖2 首先是三...

反彙編深入分析函式呼叫

函式 int fun inta,intb int main f11 跟蹤到fun,alt 8 看反彙編 00401078push1100h 引數壓棧,這裡遵循 cdecl 呼叫規範,引數由右向左 0040107d push8899h 壓棧 00401082call ilt 0 fun 0040100...