1. _stdcall是Pascal程序的缺省調用方式,通常用於Win32 Api中,函數採用從右到左的壓棧方式,自己在退出時清空堆棧。
2、_cdecl是C和C++程序的缺省調用方式. C調用約定(即用__cdecl關鍵字說明)(The C default calling convention)按從右至左的順序壓參數入棧,由調用者把參數彈出棧。對於傳送參數的內存棧是由調用者來維護的,這樣,實現可變參數 vararg的函數(如printf)只能使用該調用約定。
c代碼反彙編研究初探(1),DEBUG篇。
Author:xue23
email:[email protected]
下面是原代碼。我在vc6.0中對這段代碼進行完全的反彙編,以研究c語言在機器碼級
的運行狀態。這一部分研究DEBUG下的情況。
int __stdcall stdcalltest(int i, int j)
{
int ret = i + j;
return ret;
}
int __cdecl cdecltest(int i, int j)
{
int ret = i + j;
return ret;
}
int __cdecl cdecltest2(int i, int j)
{
int ret = i + j;
return ret;
}
int __stdcall stdcalltest2(int i, int j)
{
int ret = i + j;
return ret;
}
int main() {
int p1 = 1;
int p2 = 2;
int p3 = 3;
int p4 = 4;
int ret;
ret = stdcalltest(p1, p2);
ret = cdecltest(p3, p4);
ret = stdcalltest2(p1, p2);
ret = cdecltest2(p3, p4);
return 0;
}
下面是反彙編後的情況。
--- F:\Project\consoletest\consoletest.cpp
--------------------------------------------------------------------------
------
45:
46:
47:
48: int main() {
//首先保護現場,以便本程序執行完後,恢復現場。
00401150 push ebp ;保存上一層函數的棧,函數就是利用它來實現
一層層傳遞與迴歸(回溯).
00401151 mov ebp,esp ;用本函數的棧頂更新ebp.
00401153 sub esp,54h ;預留足夠大的空間存儲局部變量
00401156 push ebx ;
00401157 push esi ;
00401158 push edi ;保存通用寄存器啦。
00401159 lea edi,[ebp-54h] ;指向棧底
0040115C mov ecx,15h ;
00401161 mov eax,0CCCCCCCCh ;
00401166 rep stos dword ptr [edi] ;初始化這段棧區,用cc初始化每個字
節,大小爲15h * 4 = 54h. 這就是爲什麼要循環因子設爲15h的原因。
//從橈頂向下對局部變量賦值.ebp-4指向棧頂處的第一個存儲區域,以此類推!
52:
53: int p1 = 1;
00401168 mov dword ptr [ebp-4],1
54: int p2 = 2;
0040116F mov dword ptr [ebp-8],2
55: int p3 = 3;
00401176 mov dword ptr [ebp-0Ch],3
56: int p4 = 4;
0040117D mov dword ptr [ebp-10h],4
57:
58: int ret;
59: ret = stdcalltest(p1, p2);
//這裏要注意的是,壓棧順序從右向右,看一下__stdcall與__cdecl之間的調用區別
。
@ILT的含義見下面的函數表.
00401184 mov eax,dword ptr [ebp-8]
00401187 push eax
00401188 mov ecx,dword ptr [ebp-4]
0040118B push ecx
0040118C call @ILT+0(stdcalltest) (00401005);stdcalltest參數使用
的棧區在stdcalltest內清除。
00401191 mov dword ptr [ebp-14h],eax
60: ret = cdecltest(p3, p4);
00401194 mov eax,dword ptr [ebp-10h]
00401197 push eax
00401198 mov ecx,dword ptr [ebp-0Ch]
0040119B push ecx
0040119C call @ILT+30(cdecltest) (00401023);cdecltest參數使用的棧
區在cdecltest外部清除,方法就是下面這一行。
004011A1 add esp,8
004011A4 mov dword ptr [ebp-14h],eax
//下面兩個函數與上面是兩個函數是一樣的處理方法
61: ret = stdcalltest2(p1, p2);
004011A7 mov eax,dword ptr [ebp-8]
004011AA push eax
004011AB mov ecx,dword ptr [ebp-4]
004011AE push ecx
004011AF call @ILT+10(stdcalltest2) (0040100f)
004011B4 mov dword ptr [ebp-14h],eax
62: ret = cdecltest2(p3, p4);
004011B7 mov eax,dword ptr [ebp-10h]
004011BA push eax
004011BB mov ecx,dword ptr [ebp-0Ch]
004011BE push ecx
004011BF call @ILT+5(cdecltest2) (0040100a)
004011C4 add esp,8
004011C7 mov dword ptr [ebp-14h],eax
63:
64: return 0;
004011CA xor eax,eax; main返回值是0,因爲返回值是放在eax裏的,故
將eax清0就OK了。
65: }
;下面按函數開始的時候的反順序來恢復cpu現場。
004011CC pop edi
004011CD pop esi
004011CE pop ebx
004011CF add esp,54h
004011D2 cmp ebp,esp
004011D4 call __chkesp (004082d0)
004011D9 mov esp,ebp
004011DB pop ebp
004011DC ret
--- No source file
--------------------------------------------------------------------------
------------------------------
編譯器建立一張函數表,每個函數在裏面都有一個索引。如下所示:
函數表:
...
00401004 int 3
@ILT+0(?stdcalltest@@YGHHH@Z):
00401005 jmp stdcalltest (00401050)
@ILT+5(?cdecltest2@@YAHHH@Z):
0040100A jmp cdecltest2 (004010d0)
@ILT+10(?stdcalltest2@@YGHHH@Z):
0040100F jmp stdcalltest2 (00401110)
@ILT+15(?id@?$ctype@G@std@@$D):
00401014 jmp std::ctype::id (00401240)
@ILT+20(?id@?$ctype@G@std@@$E):
00401019 jmp std::ctype::id (004012e0)
@ILT+25(_main):
0040101E jmp main (00401150)
@ILT+30(?cdecltest@@YAHHH@Z):
00401023 jmp cdecltest (00401090)
00401028 int 3
...
Release篇
release模式
release模式下,大家會看到很多棧的操作已經被優化掉了,大大加快了程序的運行效率,函體內在很多簡單的情況下不會預留空間給臨時變量,因爲根本就沒有臨時變量存在了,而直接用register和memory來操作.在release下,cdcel和stdcall的區別就更明顯了。
stdstall會在在退出的時候,ret 8, 也就把esp + 8, 把參數空間從棧上釋放
而cdcel則由上級函數在調用它之間通過 add esp, 8來釋放參數空間。因爲這段代碼比較簡單,所以對應的asm也很簡單,但這段簡單的代碼卻提示了c++的優化機制是多少的智能和高效。
下面是release下的彙編代碼
PUBLIC ?stdcalltest@@YGHHH@Z ; stdcalltest
; COMDAT ?stdcalltest@@YGHHH@Z
_TEXT SEGMENT
_i$ = 8
_j$ = 12
?stdcalltest@@YGHHH@Z PROC NEAR ; stdcalltest, COMDAT
; 24 : int ret = i + j; 實際ret已經被優化掉了,所以在編寫代碼的時候,
;有的變量可能在運行的時候並沒有存在,如果確保沒有被優化的話,在這個變量前面加上voliate
00000 8b 44 24 08 mov eax, DWORD PTR _j$[esp-4]
00004 8b 4c 24 04 mov ecx, DWORD PTR _i$[esp-4]
00008 03 c1 add eax, ecx
; 25 : return ret;
; 26 : }
0000a c2 08 00 ret 8
?stdcalltest@@YGHHH@Z ENDP ; stdcalltest
_TEXT ENDS
PUBLIC ?cdecltest@@YAHHH@Z ; cdecltest
; COMDAT ?cdecltest@@YAHHH@Z
_TEXT SEGMENT
_i$ = 8
_j$ = 12
?cdecltest@@YAHHH@Z PROC NEAR ; cdecltest, COMDAT
; 29 : int ret = i + j;
00000 8b 44 24 08 mov eax, DWORD PTR _j$[esp-4] ;_i$和_j在編譯時已經確定下來了,增加了運行速度。
;相當於 move eax, DWORD PTR [esp - 4 + _j$],至於爲什麼要用[esp-4]這種形式,我想應該保護上級堆,畢竟esp-4是指向函數體以下的代碼區。
00004 8b 4c 24 04 mov ecx, DWORD PTR _i$[esp-4]
00008 03 c1 add eax, ecx
; 30 : return ret;
; 31 : }
0000a c3 ret 0
?cdecltest@@YAHHH@Z ENDP ; cdecltest
_TEXT ENDS
PUBLIC ?cdecltest2@@YAHHH@Z ; cdecltest2
; COMDAT ?cdecltest2@@YAHHH@Z
_TEXT SEGMENT
_i$ = 8
_j$ = 12
?cdecltest2@@YAHHH@Z PROC NEAR ; cdecltest2, COMDAT
; 34 : int ret = i + j;
00000 8b 44 24 08 mov eax, DWORD PTR _j$[esp-4]
00004 8b 4c 24 04 mov ecx, DWORD PTR _i$[esp-4]
00008 03 c1 add eax, ecx
; 35 : return ret;
; 36 : }
0000a c3 ret 0
?cdecltest2@@YAHHH@Z ENDP ; cdecltest2
_TEXT ENDS
PUBLIC ?stdcalltest2@@YGHHH@Z ; stdcalltest2
; COMDAT ?stdcalltest2@@YGHHH@Z
_TEXT SEGMENT
_i$ = 8
_j$ = 12
?stdcalltest2@@YGHHH@Z PROC NEAR ; stdcalltest2, COMDAT
; 39 : int ret = i + j;
00000 8b 44 24 08 mov eax, DWORD PTR _j$[esp-4]
00004 8b 4c 24 04 mov ecx, DWORD PTR _i$[esp-4]
00008 03 c1 add eax, ecx
; 40 : return ret;
; 41 : }
0000a c2 08 00 ret 8
?stdcalltest2@@YGHHH@Z ENDP ; stdcalltest2
_TEXT ENDS
PUBLIC _main
; COMDAT _main
_TEXT SEGMENT
_main PROC NEAR ; COMDAT
; 46 :
; 47 : int p1 = 1;
; 48 : int p2 = 2;
; 49 : int p3 = 3;
; 50 : int p4 = 4;
; 51 :
; 52 : int ret;
; 53 : ret = stdcalltest(p1, p2);
00000 6a 02 push 2
00002 6a 01 push 1
00004 e8 00 00 00 00 call
?stdcalltest@@YGHHH@Z ; stdcalltest
; 54 : ret = cdecltest(p3, p4);
00009 6a 04 push 4
0000b 6a 03 push 3
0000d e8 00 00 00 00 call
?cdecltest@@YAHHH@Z ; cdecltest
00012 83 c4 08 add esp, 8
; 55 : ret = stdcalltest2(p1, p2);
00015 6a 02 push 2
00017 6a 01 push 1
00019 e8 00 00 00 00 call
?stdcalltest2@@YGHHH@Z ; stdcalltest2
; 56 : ret = cdecltest2(p3, p4);
0001e 6a 04 push 4
00020 6a 03 push 3
00022 e8 00 00 00 00 call
?cdecltest2@@YAHHH@Z ; cdecltest2
00027 83 c4 08 add esp, 8
; 57 :
; 58 : return 0;
0002a 33 c0 xor eax, eax
; 59 : }
0002c c3 ret 0
_main ENDP
_TEXT ENDS