當我們在學習C語言的時候,寫代碼一定會用到函數(main函數),但函數在使用過程中是如何調用的,當我們從彙編的角度來剖析函數的調用,會讓我們對函數的認識剛深一層。
注:使用的工具是VS2017
下面我将通過一段代碼來剖析函數是如何調用的
#include <stdio.h>
int Add(int x, int y)
{
int z = ;
z = x + y;
return z;
}
int main()
{
int a = ;
int b = ;
int ret = Add(a, b);
printf("%d\n", ret);
return ;
}
函數在調用的過程中,是需要為函數來開辟棧空間,用于本次函數的調用過程中臨時變量的儲存、現場保護,這塊空間就被稱為函數棧幀。
首先我們來研究一下main函數的彙編代碼:
int main()
{
push ebp
mov ebp,esp
sub esp,h
push ebx
A push esi
B push edi
C lea edi,[ebp-h]
mov ecx,h
mov eax,CCCCCCCCh
C rep stos dword ptr es:[edi]
int a = ;
E mov dword ptr [a],Ah
int b = ;
mov dword ptr [b],h
int ret = Add(a, b);
C mov eax,dword ptr [b]
F push eax
mov ecx,dword ptr [a]
push ecx
call _Add (Eh)
add esp,
在還沒有調用main函數之前,當_tmianCRTStartup在還沒有開始調用main函數之前, esp 和 edp 一起維護同一塊空間
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIwczLcVmds92czlGZvwVP9EUTDZ0aRJkSwk0LcxGbpZ2LcBDM08CXlpXazRnbvZ2LcRlMMVDT2EWNvwFdu9mZvwVMoJzYxgmMjxmUyIGasdUZwZkMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2LcRHelR3LcJzLctmch1mclRXY39DO2YzNzkjM2ETNycDM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
開始調用main函數
剛開始的三行代碼是為了main函數倆開辟棧幀空間的,其中 esp 存放指向函數棧幀棧頂的位址, ebp 存放指向函數棧幀棧底的位址。
81 push ebp
push 表示壓棧,從棧頂壓入。
mov ebp,esp
mov 表示移動,表示的就是将 esp 移動到 ebp 的位置
sub esp,h
sub 表示将esp減去 0E4h(0x000000e4),将 esp 上移,因為在計算機中上面為低位址,下面為高位址。
819 push ebx
81A push esi
81B push edi
這三行 push 就是将 ebx 、 esi 、 edi 先後壓入棧頂,并且 esp 将會上移
0106181C lea edi,[ebp-0E4h]
01061822 mov ecx,39h
01061827 mov eax,0CCCCCCCCh
0106182C rep stos dword ptr es:[edi]
這四條語句就是,将 eax 的内容重複 ecx 次,重複是從 edi 所指向的位址開始。
int a = ;
E mov dword ptr [a],Ah
表示将10(0Ah)放入a中,放在 ebp - 8 的位置
int b = 20;
01061835 mov dword ptr [b],14h
表示将20(14h)放入b中,放在 ebp - 20 的位置
下面開始調用Add函數
int ret = Add(a, b);
C mov eax,dword ptr [b]
F push eax
mov ecx,dword ptr [a]
push ecx
call _Add (Eh)
add esp,
C mov dword ptr [ret],eax
C mov eax,dword ptr [b]
F push eax
mov ecx,dword ptr [a]
push ecx
上述四行代碼就是先将b壓棧,然後再将a壓棧,實際上a,b就是傳給Add函數的形參
int Add(int x, int y)
{
push ebp
mov ebp,esp
sub esp,CCh
push ebx
A push esi
B push edi
C lea edi,[ebp-CCh]
mov ecx,h
mov eax,CCCCCCCCh
C rep stos dword ptr es:[edi]
int z = ;
E mov dword ptr [z],
z = x + y;
mov eax,dword ptr [x]
add eax,dword ptr [y]
B mov dword ptr [z],eax
return z;
E mov eax,dword ptr [z]
}
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret
前面建立棧幀的過程和main函數一樣,其中第一步是先将main函數的 ebp 壓棧,目的是為了在傳回時能夠傳回到main函數的棧底。
01061725 mov eax,dword ptr [x]
01061728 add eax,dword ptr [y]
0106172B mov dword ptr [z],eax
return z;
0106172E mov eax,dword ptr [z]
将兩數之和放入z中,并最後将z的值放在寄存器eax中
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret
接下來将會執行 pop 操作,也就是出棧,按照順序依次将 edi 、 esi 、 ebx 出棧。然後将 ebp 指派給 esp , ebp 出棧。注: ret 指令會使得出棧一次,并将出棧的内容當做位址,将程式跳轉到該位址處。将Add棧幀銷毀。
接下來就會從call指令處繼續向下執行
add esp,
C mov dword ptr [ret],eax
将 esp 向下移,将形參銷毀,把 eax放入ret( ebp-20h) 中。
下面就是對main棧幀的銷毀,和Add棧幀的銷毀一樣。