函數調用的匯編解釋


最近看了下匯編,主要是想了解下cdecl和stdcall的區別。

之前沒有匯編基礎,只知道少許簡單的匯編指令,如mov等等。這兩天看了若干,總結一下吧,當然只是部分知識點,對我而言已經很受用了。

1.  cdecl 和 stdcall 的區別(從匯編層面解釋)

其實在選擇這兩者時,最主要的考慮是可變參數的問題,也就是誰負責清棧。那么從匯編層面是如何反應出來的呢?

1.1 stdcall 范式

int __stdcall add(int a, int b)
{
002613A0 push ebp      //保存調用者棧底地址
002613A1 mov ebp,esp   //設置新的被調用者的棧底指針
002613A3 sub esp,0C0h
002613A9 push ebx
002613AA push esi
002613AB push edi
002613AC lea edi,[ebp-0C0h]
002613B2 mov ecx,30h
002613B7 mov eax,0CCCCCCCCh
002613BC rep stos dword ptr es:[edi]
return a+b;
002613BE mov eax,dword ptr [a]  
002613C1 add eax,dword ptr [b]
}
002613C4 pop edi
002613C5 pop esi
002613C6 pop ebx
002613C7 mov esp,ebp   //將棧頂指針設置為被調用函數幀棧棧底地址
002613C9 pop ebp       //還原調用者幀棧棧底地址
002613CA ret 8         //返回,ret 8 等價於 pop EIP; add sp 8。首先,恢復返回后執行指令的地址,然后,參數出棧,這里是兩個int,故8個字節。
//這里就解釋了stdcall是被調用者清棧
int main() { 002613E0 push ebp 002613E1 mov ebp,esp 002613E3 sub esp,0CCh 002613E9 push ebx 002613EA push esi 002613EB push edi 002613EC lea edi,[ebp-0CCh] 002613F2 mov ecx,33h 002613F7 mov eax,0CCCCCCCCh 002613FC rep stos dword ptr es:[edi] int sum; sum = add(1,2); 002613FE push 2 //參數2壓棧 00261400 push 1 //參數1壓棧 00261402 call add (261109h) //調用函數add 00261407 mov dword ptr [sum],eax return 0; 0026140A xor eax,eax } 0026140C pop edi 0026140D pop esi 0026140E pop ebx 0026140F add esp,0CCh 00261415 cmp ebp,esp 00261417 call @ILT+315(__RTC_CheckEsp) (261140h) 0026141C mov esp,ebp 0026141E pop ebp 0026141F ret

1.2 cdecl

int add(int a, int b)
{
00E713A0 push ebp
00E713A1 mov ebp,esp
00E713A3 sub esp,0C0h
00E713A9 push ebx
00E713AA push esi
00E713AB push edi
00E713AC lea edi,[ebp-0C0h]
00E713B2 mov ecx,30h
00E713B7 mov eax,0CCCCCCCCh
00E713BC rep stos dword ptr es:[edi]
return a+b;
00E713BE mov eax,dword ptr [a]
00E713C1 add eax,dword ptr [b]
}
00E713C4 pop edi
00E713C5 pop esi
00E713C6 pop ebx
00E713C7 mov esp,ebp
00E713C9 pop ebp
00E713CA ret       //對比上面那個,不同之處在於ret等價於pop IP。沒有后續參數出棧的操作


int main()
{
00E713E0 push ebp
00E713E1 mov ebp,esp
00E713E3 sub esp,0CCh
00E713E9 push ebx
00E713EA push esi
00E713EB push edi
00E713EC lea edi,[ebp-0CCh]
00E713F2 mov ecx,33h
00E713F7 mov eax,0CCCCCCCCh
00E713FC rep stos dword ptr es:[edi]
int sum;
sum = add(1,2);
00E713FE push 2
00E71400 push 1
00E71402 call add (0E71096h)
00E71407 add esp,8              //看見沒有,參數出棧的地方放到了調用者這里。
00E7140A mov dword ptr [sum],eax
return 0;
00E7140D xor eax,eax
}
00E7140F pop edi
00E71410 pop esi
00E71411 pop ebx
00E71412 add esp,0CCh
00E71418 cmp ebp,esp
00E7141A call @ILT+315(__RTC_CheckEsp) (0E71140h)
00E7141F mov esp,ebp
00E71421 pop ebp
00E71422 ret

通過上面的注釋,應該很清楚了吧~

2.函數調用時匯編做的事兒

參見上面代碼示例,可以總結下,函數調用時匯編是怎么做的

1)參數壓棧,push &1, push&2...

2)調用call指令,這里call等價於push EIP,jmp XXX; 如果是lcall,那么等價於push CS, push EIP, jmp XXX,即保存函數返回后下一條指令地址,並轉移到被調用函數指令

3)保存“調用者”棧基地址,push ebp

4)設置新的“被調用者”的棧基地址,mov ebp, esp

5)執行函數。。。

6)棧頂指針指向“被調用者”棧底 mov esp, ebp

7)還原“調用者”幀棧基址,pop ebp

8)還原函數返回后下一條指令地址,並根據調用方式決定是否清理參數幀棧,ret或ret n,也等價於pop EIP或 pop EIP, add esp, n。

很明確了吧?

附錄:

intel 各個寄存器的用途

通用:

EAX(累加器)

EBX(基址)

ECX(計數)

EDX(數據)

EBP(基指針):為了傳送存儲器數據,EBP指向存儲單元

EDI(目的地址):尋址指令的目的數據串

ESI(源變址):尋址指令的源數據串

 

專用:

EIP(指令指針):代碼存儲區的下一條指令

ESP(堆棧指針):堆棧

EFLAGS:指示處理器的狀態並控制它的操作(詳細了解)奇偶標准以1為標准

 

CS(代碼段)實模式下:64KB;保護模式下:4GB;

DS(數據段)實模式下:64KB;保護模式下:4GB;

ES(附加段)附加的數據段,為某些串指令存放目的數據

SS(堆棧段)為堆棧定義了一個存儲區域,由堆棧段和堆棧指針寄存器確定堆棧段內當前的入口地址,BP也可尋址堆棧段內的數據。

CS:EIP(CS:IP):代碼起點:下一條指令的偏移地址

SS:ESP(SS:SP):尋址堆棧區:SS+ESP的存儲單元,棧頂地址

SS:EBP(SS:BP):可以理解為子棧區的棧基址

 


免責聲明!

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



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