您好,登錄后才能下訂單哦!
從匯編的角度解析函數調用過程
看看下面這個簡單函數的調用過程:
int Add(int x,int y)
{
int sum = 0;
sum = x + y;
return sum;
}
int main ()
{
int a = 10;
int b = 12;
int ret = 0;
ret = Add(a,b);
return 0;
}
今天主要用匯編代碼去講述這個過程,首先介紹幾個寄存器和簡單的匯編指令的意思。
先看幾個函數調用過程涉及到的寄存器:
(1)esp:棧指針寄存器(extended stack pointer),其內存放著一個指針,該指針永遠指向系統棧最上面一個棧幀的棧頂。
(2)ebp:基址指針寄存器(extended base pointer),其內存放著一個指針,該指針永遠指向系統棧最上面一個棧幀的底部。
(3)eax 是”累加器”(accumulator), 它是很多加法乘法指令的缺省寄存器。
(4)ebx 是”基地址”(base)寄存器, 在內存尋址時存放基地址。
(5)ecx 是計數器(counter), 是重復(REP)前綴指令和LOOP指令的內定計數器。
(6)edx 則總是被用來放整數除法產生的余數。
(7)esi/edi分別叫做”源/目標索引寄存器”(source/destination index),因為在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目標串.
在32位平臺上,ESP每次減少4字節。
再看幾條簡單的匯編指令:
mov :數據傳送指令,也是最基本的編程指令,用于將一個數據從源地址傳送到目標地址(寄存器間的數據傳送本質上也是一樣的)
sub:減法指令
lea:取偏移地址
push:實現壓入操作的指令是PUSH指令
pop:實現彈出操作的指令
call:用于保存當前指令的下一條指令并跳轉到目標函數。
這些指令當然能看懂最好,可以讓你很深刻的理解函數調用過程,不能看懂就只能通過我的描述去理解了。
進行分析之前,先來了解下內存地址空間的分布:
棧空間是向低地址增長的,主要是用來保存函數棧幀。 棧空間的大小很有限,僅有區區幾MB大小
匯編代碼實現:
main函數匯編代碼:
int main ()
{
011B26E0 push ebp
011B26E1 mov ebp,esp
011B26E3 sub esp,0E4h
011B26E9 push ebx
011B26EA push esi
011B26EB push edi
011B26EC lea edi,[ebp-0E4h]
011B26F2 mov ecx,39h
011B26F7 mov eax,0CCCCCCCCh
011B26FC rep stos dword ptr es:[edi]
int a = 10;
011B26FE mov dword ptr [a],0Ah
int b = 12;
011B2705 mov dword ptr [b],0Ch
int ret = 0;
011B270C mov dword ptr [ret],0
ret = Add(a,b);
011B2713 mov eax,dword ptr [b]
011B2716 push eax
011B2717 mov ecx,dword ptr [a]
011B271A push ecx
011B271B call @ILT+640(_Add) (11B1285h)
011B2720 add esp,8
011B2723 mov dword ptr [ret],eax
return 0;
011B2726 xor eax,eax
}
011B2728 pop edi
011B2729 pop esi
011B272A pop ebx
011B272B add esp,0E4h
011B2731 cmp
@ILT+450(__RTC_CheckEsp) (11B11C7h)
011B2738 mov esp,ebp
011B273A pop ebp
011B273B ret
Add函數匯編代碼
int Add(int x,int y)
{
011B26A0 push ebp
011B26A1 mov ebp,esp
011B26A3 sub esp,0CCh
011B26A9 push ebx
011B26AA push esi
011B26AB push edi
011B26AC lea edi,[ebp-0CCh]
011B26B2 mov ecx,33h
011B26B7 mov eax,0CCCCCCCCh
011B26BC rep stos dword ptr es:[edi]
int sum = 0;
011B26BE mov dword ptr [sum],0
sum = x + y;
011B26C5 mov eax,dword ptr [x]
011B26C8 add eax,dword ptr [y]
011B26CB mov dword ptr [sum],eax
return sum;
011B26CE mov eax,dword ptr [sum]
}
011B26D1 pop edi
011B26D2 pop esi
011B26D3 pop ebx
011B26D4 mov esp,ebp
011B26D6 pop ebp
011B26D7 ret
下面圖中詳細描述了調用過程地址變化(此處所有地址是取自32位windows系統vs編輯器下的調試過程。):
過程描述:
1、參數拷貝(參數實例化)。
2、保存當前指令的下一條指令,并跳轉到被調函數。
這些操作均在main函數中進行。
接下來是調用Add函數并執行的一些操作,包括:
1、移動ebp、esp形成新的棧幀結構。
2、壓棧(push)形成臨時變量并執行相關操作。
3、return一個值。
這些操作在Add函數中進行。
被調函數完成相關操作后需返回到原函數中執行下一條指令,操作如下:
1、出棧(pop)。
2、回復main函數的棧幀結構。(pop )
3、返回main函數
這些操作也在Add函數中進行。 至此,在main函數中調用Add函數的整個過程已經完成。
總結起來整個過程就三步:
1)根據調用的函數名找到函數入口;
2)在棧中審請調用函數中的參數及函數體內定義的變量的內存空間
3)函數執行完后,釋放函數在棧中的審請的參數和變量的空間,最后返回值(如果有的話)
如果你學了微機原理,你會想到cpu中斷處理過程,是的,函數調用過程和中斷處理過程一模一樣。
函數調用約定:
這里再補充一下各種調用規定的基本內容。
_stdcall調用約定
所有參數按照從右到左壓入堆棧,由被調用的子程序清理堆棧
_cdecl調用約定(The C default calling convention,C調用規定)
參數也是從右到左壓入堆棧,但由調用者清理堆棧。
_fastcall調用約定
顧名思義,_fastcall的目的主要是為了更快的調用函數。它主要依靠寄存器傳遞參數,剩下的參數依然按照從右到左的順序壓入堆棧,并由被調用的子程序清理堆棧。
本篇博文是按調用約定stdcall 調用函數。
csdn博客地址:http://blog.csdn.net/qq_38646470
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。