在一個函數調用另一個函數過程中到底發生了什麼?
舉例,函數A調用了函數B,形如
int A(void)
{
int i=B(int arg1,int arg2);
return i;
}
我想描述下在調用B這個過程中所發生的故事:
這裏是cdcel的調用方式
1,將傳遞給B的參數壓入堆棧,壓入順序爲從右向左,先將arg2壓入,然後壓入arg1
2,call B--調用函數B。這個過程會將B函數後執行後的地址,即return 的地址壓入堆棧。然後前IP改爲函數B的地址。開始執行B。
3,函數B在執行時,也要做些準備工作。
a,保存ebp,因爲ebp總是被我們用來保存函數執行前,即A的esp值。執行完B後我們要用ebp恢復esp;同樣A對它的上層函數也是一樣的過程。
push ebp;
b,保存esp到ebp中。
mov ebp,esp
c,堆棧中通過減少esp的值來空出空間保存B的局部變量,比如大小爲0c2h。
sub esp,0c2h
d,保存要使用到的寄存器值,如ebx,esi,edi
push ebx
push esi
push edi
d,將局部變量初始化爲0cccccccch----0cch是int 3指令的機器碼,這裏把這部分空間初始爲int 3主要是因爲這些局部變量是不能被執行,但如果程序意外要執行它們就會引發一個調試中斷來提示開發者。
lea edi,[ebp-0cch]
mov ecx,33h
mov eax,0cccccccc
rep stos dword ptr [edi] ;串寫入
這裏我們把從ebp-0cch開始的區域都初始化爲0cch
d,開始執行函數體的正常功能。在執行中如果要取得A傳遞來的參數。參數的獲取是ebp+12字節爲第二個參數,ebp+8爲第一個參數(倒序插入),依次增加。最後ebp+4正好是我們保存的返回地址。 最後在處理中我們要將函數的返回值,將返回值放在eax中,外部(A)通過eax得到返回的值。
e,恢復ebx,esi,edi,esp,ebp,最後返回。:
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret
這樣一個完整的函數調用就結束了,A繼續執行。。。。
繼續執行中。。。。。。。。。。。。。。。。。。。。。。。。。
還在執行中。。。。。。。。。。。。。。。。
快結束 了。。。。。。。。。。。。。。。!!!
馬上就要結束 了。。。。。。。。。。。。。。。。。。
啊!函數A結束了
C函數繼續執行中!!!!!!!!!!! - -!
: )
對於_stdcall方式的調用,堆棧是由被調用函數B在返回前自行處理的。