Win32程序函數調用時堆棧變化情況區分
發表時間:2023-08-18 來源:明輝站整理相關軟件相關文章人氣:
[摘要]在經典的匯編語言教程中,函數調用時堆棧的使用都是著重講解的問題。如今隨著高級語言的越來越完善,單純使用匯編開發的程序已經不多了。但對函數調用時堆棧動向的了解仍有助于我們明晰程序的執行流程,從而在程序...
在經典的匯編語言教程中,函數調用時堆棧的使用都是著重講解的問題。如今隨著高級語言的越來越完善,單純使用匯編開發的程序已經不多了。但對函數調用時堆棧動向的了解仍有助于我們明晰程序的執行流程,從而在程序編寫和調試的過程中有一個清晰的思路。
一.調用約定
在Win32中,有關函數的調用主要有兩種約定。
1._stdcall
以__stdcall方式調用的函數有以下特征:
• 參數由右至左壓棧
• 調用返回時,堆棧由被調函數調整
2.__cdecl
__cdecl約定是C/C++函數的默認調用約定。它有以下特征:
• 參數由右至左壓棧
• 調用返回時,堆棧由調用者調整
二.Win32函數調用過程
1. 壓入參數
這里依據以上的調用方式將調用者給出的參數一一壓入堆棧。
2. 壓入斷點
當程序執行到Call指令的時候,當前語句的地址作為斷點地址壓入堆棧。
3. 跳轉
eip的值被重新設置為被調函數的起始地址。
4. mov ebp, esp
這里ebp被用來在堆棧中尋找調用者壓入的參數,同時作為調用者堆棧指針的一個備份。在此前還應該執行一條:
push ebp
把ebp中原來的數值保存。
5. sub esp,N
這里N是函數內局部變量的總字節數加上一個整數,一般為40。此后esp即為被調函數的堆棧指針了。
6. 初始化esp ~ esp-N之間的N字節空間
這是對堆棧中已分配給局部變量使用的內存空間的初始化,一般全部設置為0xcc。
7. 順序執行函數內語句。
此時函數的堆棧位于所有局部變量的內存空間之后,二者之間一般有40字節的隔離帶。
8.返回
為保障調用的正常返回,函數內應當保證規范使用堆棧,使即將返回的時候esp的值恢復為執行第一條語句前的狀態。說明白點,就是每一條push都要有相應的pop。
調用返回的過程如下:
mov esp, ebp
執行后,esp恢復為調用者的堆棧指針,棧頂除斷點地址外,還存有原ebp的值和調用時壓入的參數。
然后依次彈出ebp的值和斷點地址。如果是__cdecl約定則直接返回調用者,調用者將負責調整堆棧,丟棄調先前壓入的參數。如果是__stdcall則這個工作由被調函數來執行。
程序樣例如下:
……
0040B8E8 push 1 ;壓入參數
0040B8EA call 00401028 ;調用函數
……
00401028 jmp 0040b7c0 ;跳轉到函數入口
……
0040B7C0 push ebp ;保存ebp
0040B7C1 mov ebp,esp
0040B7C3 sub esp,44h ;設置函數的堆棧指針,此函數中有4
;字節的局部變量
0040B7C6 push ebx
0040B7C7 push esi
0040B7C8 push edi
0040B7C9 lea edi,[ebp-44h]
0040B7CC mov ecx,11h
0040B7D1 mov eax,0CCCCCCCCh
0040B7D6 rep stos dword ptr [edi] ;初始化局部變量空間
0040B7D8 mov eax,dword ptr [ebp+8]
0040B7DB mov dword ptr [ebp-4],eax
……
0040B7DE pop edi ;彈出曾壓棧的數據
0040B7DF pop esi
0040B7E0 pop ebx
0040B7E1 mov esp,ebp ;恢復調用者的堆棧
0040B7E3 pop ebp ;彈出原ebp值
0040B7E4 ret 4 ;返回并將堆棧向上調整4字節。
;此處為__stdcall約定,所以由函數調
;整堆棧
相應的C代碼如下:
void __stdcall fun(int);
int main(void)
{
……
fun(1);
……
return 0;
}
void __stdcall fun(int para)
{
int localpara = para;
……
}