VC++函式呼叫過程彙編分析(基於vs2012)
本文將在VS2012環境下對函式呼叫過程的彙編程式碼進行分析。分析不到位或者存在錯誤的地方請批評指正,請與作者聯絡。
#include <iostream> #include <stack> #include <vector> #include <string> #include <stdlib.h> #include <sstream> #include <queue> using namespace std; int test(int a,int b){ int c; c = a + b; return c; } int main(){ int a =3,b =5; test(a,b); system("pause"); }
我們在main函式第一行程式碼處開始單步除錯。
ebp暫存器通常指向棧底,常用來定址當前函式內的區域性變數等。esp是指向堆疊棧頂的指標。X86/64 vc++的堆疊是從高地址向低地址生長,比如push一個32位暫存器入棧後,esp - 04H。
第一行是push ebp,意思是將ebp入棧保護起來,雖然這個函式是main函式,但是流程和一般函式呼叫一樣,都是先將ebp入棧保護。
mov ebp,esp 是將當前棧頂指標給ebp,使其能夠定址區域性變數。
sub esp,0D8H 把棧頂指標esp移動到0D8H之後,這樣留出一塊隔離的空間,這個隔離空間的大小是由編譯器決定的。預設的隔離空間是192位元組。如果定義一個int會在棧空間裡面佔用12位元組?
push esi
push edu
push ebx
是保護現場的3條彙編指令。
要理解lea edi,[ebp-0D8H] 的意思,先要搞清楚lea的意思。
假設:SI=1000H , DS=5000H, (51000H)=1234H
執行指令 LEA BX , [SI]後,BX=1000H 執行指令 MOV BX , [SI]後,BX=1234H實際上就是取[]內表示的資料的偏移地址。
lea edi,[ebp-108h]
mov ecx,42h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
這四句結合在一起就是對留出的隔離區域和區域性變數所佔的空間進行清空,置為0xCCCCCCCCH。將每個位元組置為0xCCH是有原因的,0xCCH是int 3的位元組碼,int 3時斷點中斷指令。當意外的執行了棧中的內容時,會直接提示錯誤。
實際上是STOS指令的作用是將eax中的值拷貝到ES:EDI指向的地址。rep指令是重複指令,重複次數存在暫存器ecx中。42H是10進位制的66,就是重複66次。每次將一個字的空間置為eax的值,重複66次就清空了264位元組。正好將隔離區和區域性變數所佔空間清空。
mov dword ptr [a],3
mov dword ptr [b],5
將立即數寫入棧空間。
下面是記憶體狀況。
圖1
因為下面要呼叫test函式,所以將a,b兩個int型變數入棧。
入棧順序為:
1.cdecl方式:C語言方式,引數從右邊開始入棧。
2.winapi方式:引數還是從右邊入棧。
上圖是test函式的彙編程式碼。可以看出,與main函式的過程十分相似。首先也是將ebp入棧儲存,因為這個時候的ebp還是main函式的,所以儲存起來,然後把棧頂指標esp給ebp。現場保護和清空棧區域的程式碼與main函式中的幾乎相同。
下一句是獲取引數資訊的。
mov eax,dword ptr [ebp+8]
這句ebp+8,實際上就是當前棧頂往棧底移動8個位元組。從這裡開始往後的4個位元組放入eax。下面那句彙編意思相同。
ebp+8的原因是,在呼叫call命令的時候會將當前的EIP指標入棧。所以,這裡後移8位元組就能訪問到引數了。
mov dword ptr [ebp-8],eax 這句將計算結果放入ebp-8的位置,也就是區域性變數c所在的棧空間。
mov eax,dword ptr [ebp-8] 這句將[ebp - 8]棧的內容放入eax。函式的返回值就是通過eax暫存器返回的。
下面是記憶體狀況:
圖2
下面幾句恢復現場。
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret
mov esp,ebp 這句將當前基指傳給棧頂指標。因為在test函式呼叫時,堆疊已經生長到這個地方了。
RET指令的內部操作是:棧頂字單元出棧,其值賦給IP暫存器。此時,IP指標就回到main函式繼續執行了。
最後返回main函式後會執行一句
add esp,8 這句的意思是平衡剛才有傳參佔用的堆疊空間。從圖2中可知,ret後,esp會回到引數b的下面。此時,+8會使esp回到呼叫test前的位置,即恢復了棧頂指標。
簡單函式呼叫的彙編分析到此結束。下次分析new、malloc的區別。以及不同段的區別。