最近看了下匯編,主要是想了解下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):可以理解為子棧區的棧基址