彙編相關 -by codesnail

 

 

簡單的函數調用,通過簡單的函數調用反彙編可以清楚瞭解如下

1.棧到底是什麼,如何操縱棧的?

2.參數和臨時變量是以什麼形式在哪存放?

3.如何傳遞返回值?


舉例:

#include <stdio.h>


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

int main(void)
{
     int x=0;
     int y=3;
     int z=4;
     x=add(y,z);
     return 0;
}

這是一個簡單的通過調用函數計算兩數之和的程序

VC6.0生成的彙編代碼如下:

add函數

{


0040D750   push        ebp

//把main函數的ebp壓棧,ebp=1000,esp=896
0040D751   mov         ebp,esp

//得到“新”棧基址,這裏的新的意思是每個函數訪問屬於自己的一塊棧區域,其實是相鄰的內存區域,或者說棧只有一個。ebp=896,esp=896
0040D753   sub         esp,44h

//ebp=896,esp=828
0040D756   push        ebx
0040D757   push        esi
0040D758   push        edi

//ebp=896,esp=816
0040D759   lea         edi,[ebp-44h]
0040D75C   mov         ecx,11h
0040D761   mov         eax,0CCCCCCCCh
0040D766   rep stos    dword ptr [edi]

//初始化內部變量區
5:        int c=0;
0040D768   mov         dword ptr [ebp-4],0

//c放入“新”棧基址
6:        c=a+b;
0040D76F   mov         eax,dword ptr [ebp+8]
0040D772   add         eax,dword ptr [ebp+0Ch]

//因爲“新”棧基地址就是“舊”棧頂地址,所以通過ebp訪問傳過來的參數,ebp+8到ebp+c是因爲ebp上方的8字節用於存儲調用函數的調用地址和“舊”堆棧基地址了。
0040D775   mov         dword ptr [ebp-4],eax

//運算結果放入c中
7:        return c;
0040D778   mov         eax,dword ptr [ebp-4]

//用寄存器eax返回結果
8:    }
0040D77B   pop         edi
0040D77C   pop         esi
0040D77D   pop         ebx

//恢復寄存器的值,ebp=896,esp=828
0040D77E   mov         esp,ebp

//恢復“舊”棧頂地址,ebp=896,esp=896,此函數堆棧被釋放!
0040D780   pop         ebp

//恢復“舊”棧基地址,ebp=1000,esp=900,此時恢復到調用前的棧基地址和頂地址

0040D781   ret

//返回調用點,ebp=1000,esp=904,調用點地址被彈出,返回到調用點

 

main函數

{
0040D790   push        ebp
0040D791   mov         ebp,esp

//用棧頂地址作爲棧基地址,目的是不和調用前棧空間衝突,爲了敘述方便假設此時的棧基址ebp=1000,esp=1000。
0040D793   sub         esp,4Ch

//esp下移,開闢出0x4C字節的空間,這個空間是留給內部參數用的,這個空間的大小隨內部變量多少由編譯器決定。ebp=1000,esp=1000-0x4C=924
0040D796   push        ebx
0040D797   push        esi
0040D798   push        edi

//保護 ebx,esi,edi的值,ebp=1000,esp=924-12=912
0040D799   lea         edi,[ebp-4Ch]
0040D79C   mov         ecx,13h
0040D7A1   mov         eax,0CCCCCCCCh

0040D7A6   rep stos    dword ptr [edi]

//把內部參數佔用的空間每個字節都初始化爲0xCC,這個是爲了在DUBUG程序的方便,編譯器加入的,如果不在DEBUG狀態下,這個區域是沒有被初始化的,也就是說是隨機值。
12:       int x=0;
0040D7A8   mov         dword ptr [ebp-4],0
13:       int y=3;
0040D7AF   mov         dword ptr [ebp-8],3
14:       int z=4;
0040D7B6   mov         dword ptr [ebp-0Ch],4

//內部變量放入剛纔被初始化爲0xCC的內部變量區,x佔用四字節在地址9996-9999,y,z一次類推
15:       x=add(y,z);
0040D7BD   mov         eax,dword ptr [ebp-0Ch]
0040D7C0   push        eax
0040D7C1   mov         ecx,dword ptr [ebp-8]
0040D7C4   push        ecx

//把參數按照stdcall方式從右到左壓棧,ebp=1000,esp=912-8=904,z首先入棧在908-911,y在904-907
0040D7C5   call        @ILT+15(_add) (00401014)

//把返回下一行代碼即 add esp,8 的地址壓棧,轉向add函數,ebp=1000,esp=900,看add函數
0040D7CA   add         esp,8

//ebp=1000,esp=912,恢復到壓入參數前棧基地址和頂地址,這個步驟叫做堆棧修正
0040D7CD   mov         dword ptr [ebp-4],eax

//返回的變量放到x中
16:       return 0;
17:   }

 


現在來總結開始提出的三個問題

1.棧到底是什麼,如何操縱棧的?

   棧是操作系統分配給程序運行的一塊內存區域,有以下特點

   1.1、改變堆棧用push, pop,用的esp棧頂指針,而讀指針則用ebp棧基指針靈活訪問

   1.2、每當一個函數跳轉到另一個函數時,會在上一個函數用到的棧空間下方開闢空間

2.參數和臨時變量是以什麼形式在哪存放?

   2.1、參數放在舊棧的返回地址和舊棧基地址的上方,而臨時變量則在新棧的最上方處,變量名會被編譯器連接一個地址,程序在被編譯成彙編以後,變量名就是虛無了。

3.如何傳遞返回值?

   3.1、傳遞一個值的情況下,通過eax傳遞

可以看出,棧溢出是由於編譯器沒有檢查棧是否還有空間。

 

發佈了7 篇原創文章 · 獲贊 1 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章