C&C++函式呼叫過程
阿新 • • 發佈:2019-01-09
呼叫函式主要關注三個方面分別是函式名,返回值和引數列表,我接下來將會深入底層講解呼叫函式的過程。
呼叫函式的過程主要有四方面,①函式引數代入,②函式棧幀開闢,③函式返回值,④函式棧幀回退
首先來看一段簡單的c檔案程式碼,和它的彙編碼,只需簡單瀏覽即可:
原始碼:
int fun1(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int a = fun1(10, 20);
return 0;
}
彙編碼:
main函式
int main() { 00C31410 push ebp 00C31411 mov ebp,esp 00C31413 sub esp,0CCh 00C31419 push ebx 00C3141A push esi 00C3141B push edi 00C3141C lea edi,[ebp-0CCh] 00C31422 mov ecx,33h 00C31427 mov eax,0CCCCCCCCh 00C3142C rep stos dword ptr es:[edi] int a = fun1(10, 20); 00C3142E push 14h 00C31430 push 0Ah 00C31432 call fun1 (0C31127h) 00C31437 add esp,8 00C3143A mov dword ptr [a],eax return 0; 00C3143D xor eax,eax } 00C3143F pop edi 00C31440 pop esi 00C31441 pop ebx 00C31442 add esp,0CCh 00C31448 cmp ebp,esp 00C3144A call __RTC_CheckEsp (0C3113Bh) 00C3144F mov esp,ebp 00C31451 pop ebp 00C31452 ret
fun1函式
int fun1(int a, int b) { 009813D0 push ebp 009813D1 mov ebp,esp 009813D3 sub esp,0CCh 009813D9 push ebx 009813DA push esi 009813DB push edi 009813DC lea edi,[ebp-0CCh] 009813E2 mov ecx,33h 009813E7 mov eax,0CCCCCCCCh 009813EC rep stos dword ptr es:[edi] int c = a + b; 009813EE mov eax,dword ptr [a] 009813F1 add eax,dword ptr [b] 009813F4 mov dword ptr [c],eax return c; 009813F7 mov eax,dword ptr [c] } 009813FA pop edi 009813FB pop esi 009813FC pop ebx 009813FD mov esp,ebp 009813FF pop ebp 00981400 ret
首先知道ebp為棧底暫存器,esp為棧頂暫存器。push為操作,操作方式為在esp中的棧頂存放資料,棧頂上移。
在檢視fun函式引數如何代入之前,我們先看一下main的棧頂和棧底
(1)函式引數代入
兩次push後,引數就被放在了main函式的棧頂,且入棧為從右往左,效果如下:
(2)fun函式棧幀開闢
可以看到,call就相當於呼叫函式,這裡重新設定了棧底ebp和棧頂esp,過程如下:
檢視fun函式的開闢棧幀的過程會發現它與main函式的開闢及其類似,其實,main函式的下面也有主函式引數,也就是可以把main函式也看做為一個普通函式。
(3)函式返回
利用暫存器帶回,將暫存器的值寫入接收返回值的常量
(4)棧幀回退
將fun的棧頂指向fun的棧底:
mian函式中:esp移動8位,消除引數
現在就回到了main函式的棧頂。
(5)其他問題
①因為引數大小不同,8位元組及以上的引數採用的是提前在棧頂開闢記憶體,以儲存大位元組的引數。
②返回8位元組及以上的返回值也是採用提前開闢記憶體的方法。
③有三種不同的約定呼叫方式,分別為__cedel、__stdcall、__fastcall,這三種方式有一些細節的不同,但是思想相同,本文講的是__cedel方式。