c代碼反彙編研究初探(1),DEBUG篇。http://xue23.blog.163.com/blog/static/9793442005329319570/

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章