c語言中函數調用的本質從匯編角度分析


今天下午寫篇博客吧,分析分析c語言中函數調用的本質,首先我們知道c語言中函數的本質就是一段代碼,但是給這段代碼起了一個名字,這個名字就是他的的這段代碼的開始地址

這也是函數名的本質,其實也就是匯編中的標號。下面我們會接觸到一些東西 比如 eip 就是我們常常說的程序計數器,還有ebp和esp (這里是倆個指針,記得我們以前學8086也就一個sp堆棧指針)分別為EBP是指向棧底的指針,在過程調用中不變,又稱為幀指針。ESP指向棧頂,程序執行時移動,ESP減小分配空間,ESP增大釋放空間,ESP又稱為棧指針。當然現在不理解沒關系(在堆棧中變量分布是從高地址到低地址分布)。

好了我們開始正式話題吧:

1.先看圖,下面我先貼出一個調用代碼。

# include <stdio.h>

int fun(int a, int b)
{
	int c = 0;
	c= a + b;
	return c;

}
int main(void)
{
	int a = 1;
	int b = 3;
	fun(a,b);


	return 0;
}

  反匯編后的代碼

   
--- 匯編代碼-----------------------------------------------------------------------

__CxxUnhandledExceptionFilter:
00A51113  jmp         __CxxUnhandledExceptionFilter (0A525E0h)  
___CxxSetUnhandledExceptionFilter:
00A51118  jmp         __CxxSetUnhandledExceptionFilter (0A52660h)  
_QueryPerformanceCounter@4:
00A5111D  jmp         _QueryPerformanceCounter@4 (0A53BB0h)  、

_fun:                  ;注意了 fun在這里


00A51122  jmp         fun (0A513C0h)  ;可以看出fun還要跳轉 這次跳到了0A513C0h


__unlock:
00A51127  jmp         __unlock (0A536F4h)  
_GetCurrentProcessId@0:
00A5112C  jmp         _GetCurrentProcessId@0 (0A53BB6h)  
@_RTC_CheckStackVars2@12:
00A51131  jmp         _RTC_CheckStackVars2 (0A51490h)  
___set_app_type:
00A51136  jmp         ___set_app_type (0A5269Eh)  



--- 被調函數的真正地址 -----------------------------------------


     1: # include <stdio.h>
     2: 
     3: int fun(int a, int b)
     4: {
00A513C0  push        ebp         ;壓棧 ebp 保護ebp
00A513C1  mov         ebp,esp     ;將現在的esp地址給ebp換句話說ebp現在指向了這里
                                  ;其實也就是棧幀的最下面
00A513C3  sub         esp,0CCh  
00A513C9  push        ebx  
00A513CA  push        esi  
00A513CB  push        edi  
00A513CC  lea         edi,[ebp-0CCh]  
00A513D2  mov         ecx,33h  
00A513D7  mov         eax,0CCCCCCCCh  
00A513DC  rep stos    dword ptr es:[edi]  
     5:     int c = 0;
00A513DE  mov         dword ptr [c],0  
     6:     c= a + b;
00A513E5  mov         eax,dword ptr [a]  
00A513E8  add         eax,dword ptr [b]  
00A513EB  mov         dword ptr [c],eax  
     7:     return c;
00A513EE  mov         eax,dword ptr [c]  
     8: 
     9: }
00A513F1  pop         edi  
00A513F2  pop         esi  
00A513F3  pop         ebx  
00A513F4  mov         esp,ebp  
00A513F6  pop         ebp  
00A513F7  ret  


--- 主調函數 -----------------------------------------------------------------------


    10: int main(void)
    11: {
00A51A11  mov         ebp,esp  
00A51A13  sub         esp,0D8h  
00A51A19  push        ebx  
00A51A1A  push        esi  
00A51A1B  push        edi  
00A51A1C  lea         edi,[ebp-0D8h]  
00A51A22  mov         ecx,36h  
00A51A27  mov         eax,0CCCCCCCCh  
00A51A2C  rep stos    dword ptr es:[edi]  
    12:     int a = 1;
00A51A2E  mov         dword ptr [a],1  ;定義變量a
    13:     int b = 3;
00A51A35  mov         dword ptr [b],3  ;定義變量b
    14:     fun(a,b);
00A51A3C  mov         eax,dword ptr [b]  ;把變量b給eax
00A51A3F  push        eax                ;eax壓棧 也就b壓棧
00A51A40  mov         ecx,dword ptr [a]  ;同上
00A51A43  push        ecx  
00A51A44  call        _fun (0A51122h)    ;匯編開始調用,在匯編中函數名前面加下划線當標號處理
                                         ;地址是0A51122h,現在我們去哪里
00A51A49  add         esp,8  
    15: 
    16: 
    17:     return 0;
00A51A4C  xor         eax,eax  
    18: }
00A51A4E  pop         edi  
00A51A4F  pop         esi  
00A51A50  pop         ebx  
00A51A51  add         esp,0D8h  
00A51A57  cmp         ebp,esp  
00A51A59  call        __RTC_CheckEsp (0A5113Bh)  
00A51A5E  mov         esp,ebp  
00A51A60  pop         ebp  
00A51A61  ret  
--- 無源文件 -----------------------------------------------------------------------

  2.是不是看上面的已經懵逼了,沒關系了,我來介紹一下

上面我是在vs中進行了反匯編,原本准備gcc下搞,后來懶得折騰了。先講一下函數調用的過程,函數調用的時候其實也就是匯編中的地址的跳轉,匯編中的跳轉源於標號地址。其實這個也好理解,不知道地址,你讓我如何找你。但是在找的開始,我們需要先記錄一下回家地址,當前的一些寄存器狀態(這是因為調用到里面也可能用到這些寄存器)注意還要壓入一些函數調用參數。來張圖我們看看

我們從上面的圖可以看到,函數調用的時候依次壓棧從右到左。壓棧完畢調用call。call的作用有倆個,就是壓棧返回值,然后修改程序計數器eip,實現程序跳轉到被調函數。接着壓棧ebp里面的內容(是什么我們先不講)然后將esp賦值給ebp。也就是ebp里面的內容被改變,變為現在的esp內容,esp不就是棧頂,也就是說現在都指向了棧頂,然后壓棧結束了或者可能換有一些其他的參數,比如我們遞歸調用,那下面就是下一個函數的參數,返回地址等等等。現在我們討論的是ebp的作用是什么:那就是ebp指向了一個堆棧中一個棧幀的底部。而esp指向了頂部。我們可以利用ebp的偏移實現,局部變量和參數的訪問。下面我們要討論的就是如何返回。其實就是參數依次出棧,最后老ebp彈出到現在ebp。ebp指后到上一次的棧幀底部。但我們問一下參數是如何出棧的,難道是彈出,嗎?彈出還有什么用,因為局部變量用完后就沒用了呀,也沒必要彈出給寄存器,其實是ebp將值賦給esp,esp由以前的棧頂指向棧底也就是ebp的地方。然后老ebp彈出到ebp。ebp歸為到以前的ebp。esp再減4。esp回到返回地址處,然后在修改eip返回。然后esp再減4,回到新的棧頂。而返回的指令源於ret。

其實這個過程也不是很難,就是繁瑣。需要對着棧圖分析。需要理解局部變量的拋棄源於ebp對esp的修改。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM