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