__cdecl 和 __stdcall 压栈参数顺序是一致的,但平衡堆栈方式不一样。
__cdecl调用函数方式是调用者,即函数外部平衡堆栈,一般是在函数外部调用add esp, xxxx,函数内部只需要ret返回就行
__stdcall调用函数方式是被调用,即函数内部平衡堆栈,一般在函数内部结束的时候调用ret xxx或者add esp,xxxx ; ret
在vs2005里面C++工程默认都是__cdecl,所以需要外部平衡堆栈
看一个实例,本来__asm里面本来原意会去取函数参数,并打印出来
但经过编译过后,汇编代码如下:
Release版本
00401000 > . 56 push esi ; {
00401001 . 8B35 A4204000 mov esi,dword ptr ds:[<&MSVCR80.printf>]
00401007 . 68 F4204000 push huibianT.004020F4
0040100C . FFD6 call esi
0040100E . 83C4 04 add esp,4
00401011 . 50 push eax
00401012 . 8B4424 04 mov eax,dword ptr ss:[esp+4]
00401016 . A3 7C334000 mov dword ptr ds:[__native_startup_lockonement],eax
0040101B . 8B4424 08 mov eax,dword ptr ss:[esp+8]
0040101F . A3 78334000 mov dword ptr ds:[__native_startup_stateeement],eax
00401024 . 8B4424 0C mov eax,dword ptr ss:[esp+C]
00401028 . A3 74334000 mov dword ptr ds:[_adjust_fdivlestatussonement],eax
0040102D . 58 pop eax
0040102E . A1 74334000 mov eax,dword ptr ds:[_adjust_fdivlestatussonement]
00401033 . 8B0D 78334000 mov ecx,dword ptr ds:[__native_startup_stateeement]
00401039 . 8B15 7C334000 mov edx,dword ptr ds:[__native_startup_lockonement]
0040103F . 50 push eax
00401040 . 51 push ecx
00401041 . 52 push edx
00401042 . 68 F8204000 push huibianT.004020F8
00401047 . FFD6 call esi
00401049 . 68 10214000 push huibianT.00402110
0040104E . FF15 9C204000 call dword ptr ds:[<&MSVCR80.system>]
00401054 . 83C4 14 add esp,14
00401057 . 33C0 xor eax,eax
00401059 > . 5E pop esi
0040105A . C3 retn
Release版本优化过后函数参数不知去向。
而Debug版本的TestMy函数内部的esp已经早就破坏了,[esp+0x08]已经不再表示第一个参数。
00411449 53 push ebx
0041144A 56 push esi
0041144B 57 push edi
0041144C 8DBD 40FFFFFF lea edi,dword ptr ss:[ebp-C0]
00411452 B9 30000000 mov ecx,30
00411457 B8 CCCCCCCC mov eax,CCCCCCCC
0041145C F3:AB rep stos dword ptr es:[edi]
0041145E 8BF4 mov esi,esp
00411460 68 40574100 push huibianT.00415740
00411465 FF15 C8824100 call dword ptr ds:[<&MSVCR80D.printf>]
0041146B 83C4 04 add esp,4
0041146E 3BF4 cmp esi,esp
00411470 E8 DAFCFFFF call huibianT.0041114F
00411475 50 push eax
00411476 8B4424 04 mov eax,dword ptr ss:[esp+4]
0041147A A3 EC744100 mov dword ptr ds:[vEsp2ggerListeningIPTORWe>::NativeDll::ProcessVer>
0041147F 8B4424 08 mov eax,dword ptr ss:[esp+8]
00411483 A3 E8744100 mov dword ptr ds:[argc1ggerListeningIPTORWe>::NativeDll::ProcessVer>
00411488 8B4424 0C mov eax,dword ptr ss:[esp+C]
0041148C A3 E4744100 mov dword ptr ds:[argc2ggerListeningIPTORWe>::NativeDll::ProcessVer>
00411491 58 pop eax
00411492 8BF4 mov esi,esp
00411494 A1 E4744100 mov eax,dword ptr ds:[argc2ggerListeningIPTORWe>::NativeDll::Proces>
00411499 50 push eax
0041149A 8B0D E8744100 mov ecx,dword ptr ds:[argc1ggerListeningIPTORWe>::NativeDll::Proces>
04114A0 51 push ecx
004114A1 8B15 EC744100 mov edx,dword ptr ds:[vEsp2ggerListeningIPTORWe>::NativeDll::Proces>
004114A7 52 push edx
004114A8 68 A8574100 push huibianT.004157A8
004114AD FF15 C8824100 call dword ptr ds:[<&MSVCR80D.printf>]
004114B3 83C4 10 add esp,10
004114B6 3BF4 cmp esi,esp
004114B8 E8 92FCFFFF call huibianT.0041114F
004114BD 5F pop edi
004114BE 5E pop esi
004114BF 5B pop ebx
004114C0 81C4 C0000000 add esp,0C0
004114C6 3BEC cmp ebp,esp
004114C8 E8 82FCFFFF call huibianT.0041114F
004114CD 8BE5 mov esp,ebp
004114CF 5D pop ebp
004114D0 C3 retn
当使用__declspec(naked)调用约定的时候,如下面
需要注意的是使用这样约定调用的时候,函数内部不会帮你处理堆栈,需要你自己来处理,也就是说不会帮你ret,需要你自己来操作。但可以内部调用其他函数,而且会帮你的其他函数平衡好堆栈,即ebp和esp不会因为操作这些函数而改变,只有当你用__asm里面进行对其进行操作,才会改变。因为默认是__cdecl,所以我处理返回是ret。 另外我这里故意将内部临时变量从static int 变为 int,发现ebp和esp也并没有做改变。汇编代码如下:
Debug版本
00411440 > 8BF4 mov esi,esp ; {
00411442 68 40574100 push huibianT.00415740
00411447 FF15 C8824100 call dword ptr ds:[<&MSVCR80D.printf>]
0041144D 83C4 04 add esp,4
00411450 3BF4 cmp esi,esp
00411452 E8 F8FCFFFF call huibianT.0041114F
00411457 50 push eax ; push eax
00411458 8B4424 04 mov eax,dword ptr ss:[esp+4] ; mov eax, [esp + 0x04]
0041145C 8945 F8 mov dword ptr ss:[ebp-8],eax ; mov vEsp, eax
0041145F 8B4424 08 mov eax,dword ptr ss:[esp+8] ; mov eax, [esp + 0x08]
00411463 8945 EC mov dword ptr ss:[ebp-14],eax ; mov argc1, eax
00411466 8B4424 0C mov eax,dword ptr ss:[esp+C] ; mov eax,[esp + 0x0C]
0041146A 8945 E0 mov dword ptr ss:[ebp-20],eax ; mov argc2, eax
0041146D 58 pop eax ; pop eax
0041146E 8BF4 mov esi,esp
00411470 8B45 E0 mov eax,dword ptr ss:[ebp-20]
00411473 50 push eax
00411474 8B4D EC mov ecx,dword ptr ss:[ebp-14]
00411477 51 push ecx
00411478 8B55 F8 mov edx,dword ptr ss:[ebp-8]
0041147B 52 push edx
0041147C 68 A8574100 push huibianT.004157A8
00411481 FF15 C8824100 call dword ptr ds:[<&MSVCR80D.printf>]
00411487 83C4 10 add esp,10
0041148A 3BF4 cmp esi,esp
0041148C E8 BEFCFFFF call huibianT.0041114F
00411491 C3 retn
当我想对函数内部的int变量赋值的时候,发现会报错,看来这种调用约定是严格保护了ebp和esp的。
那么Release版本优化会不会改变ebp和esp的值呢
00401000 >/$ 8B35 A4204000 mov esi,dword ptr ds:[<&MSVCR80.printf>]
00401006 |. 68 F4204000 push huibianT.004020F4
0040100B |. FFD6 call esi
0040100D |. 83C4 04 add esp,4
00401010 |. 50 push eax ; push eax
00401011 |. 8B4424 04 mov eax,dword ptr ss:[esp+4] ; mov eax, [esp + 0x04]
00401015 |. 8945 F8 mov [local.2],eax ; mov vEsp, eax
00401018 |. 8B4424 08 mov eax,dword ptr ss:[esp+8] ; mov eax, [esp + 0x08]
0040101C |. 8945 FC mov [local.1],eax ; mov argc1, eax
0040101F |. 8B4424 0C mov eax,dword ptr ss:[esp+C] ; mov eax,[esp + 0x0C]
00401023 |. 8945 F4 mov [local.3],eax ; mov argc2, eax
00401026 |. 58 pop eax ; pop eax
00401027 |. 8B45 F4 mov eax,[local.3] ; printf("%x -- %d -- %d /n",vEsp, argc1, argc2);
0040102A |. 8B4D FC mov ecx,[local.1]
0040102D |. 8B55 F8 mov edx,[local.2]
00401030 |. 50 push eax
00401031 |. 51 push ecx
00401032 |. 52 push edx
00401033 |. 68 F8204000 push huibianT.004020F8
00401038 |. FFD6 call esi
0040103A |. 83C4 10 add esp,10
0040103D /. C3 retn
可以看到ebp和esp都保护得相当完美。
现在基本可以得到结论:
在没有__declspec(naked)调用约定的函数内部操作esp和ebp是不安全的。
而在有__declspec(naked)需要自己维护堆栈,但调用其他自己的函数,堆栈不会受影响