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:xue23@163.com

下面是原代码。我在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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值