***********************************************************
首先,比较C++ 中的三种函数调用方式。
测试代码:
int _stdcall Add1(int x1, int x2, int x3)
{
return x1 + x2 + x3;
}
int __cdecl Add2(int x1, int x2, int x3)
{
return x1 + x2 + x3;
}
int __fastcall Add3(int x1, int x2, int x3)
{
return x1 + x2 + x3;
}
void FTest2()
{
int x[] = {1, 2, 3};
int ret;
ret = Add1(x[0], x[1], x[2]);
ret = Add2(x[0], x[1], x[2]);
ret = Add3(x[0], x[1], x[2]);
}
先看看在调用这些函数时调用者作些什么工作
__cdecl |
_stdcall |
__fastcall |
ret = Add2(x[0], x[1], x[2]); |
ret = Add1(x[0], x[1], x[2]); |
ret = Add3(x[0], x[1], x[2]); |
0040179F mov eax,dword ptr [ebp-4] 004017A2 push eax 004017A3 mov ecx,dword ptr [ebp-8] 004017A6 push ecx 004017A7 mov edx,dword ptr [x] 004017AA push edx 004017AB call Add2 (401750h) 004017B0 add esp,0Ch 004017B3 mov dword ptr [ret],eax |
0040178B mov eax,dword ptr [ebp-4] 0040178E push eax 0040178F mov ecx,dword ptr [ebp-8] 00401792 push ecx 00401793 mov edx,dword ptr [x] 00401796 push edx 00401797 call Add1 (401760h) 0040179C mov dword ptr [ret],eax |
004017B6 mov eax,dword ptr [ebp-4] 004017B9 push eax 004017BA mov edx,dword ptr [ebp-8] 004017BD mov ecx,dword ptr [x] 004017C0 call Add3 (401730h) 004017C5 mov dword ptr [ret],eax |
从上面可以看出调用时区别:
__cdecl 调用结束后由调用者将参数出栈
_stdcall 调用结束后不需要调用者将参数出栈
__fastcall 调用结束后不需要调用者将参数出栈,并且调用前最左侧两个参数直接存入寄存器EDX 和ECX 中
相同之处是三者均由右向左压栈
再看看在这些函数自己作些什么工作
__cdecl |
_stdcall |
__fastcall |
int __cdecl Add2(int x1, int x2, int x3) { 00401750 push ebp 00401751 mov ebp,esp return x1 + x2 + x3; 00401753 mov eax,dword ptr [x1] 00401756 add eax,dword ptr [x2] 00401759 add eax,dword ptr [x3] } 0040175C pop ebp 0040175D ret |
int _stdcall Add1(int x1, int x2, int x3) { 00401760 push ebp 00401761 mov ebp,esp return x1 + x2 + x3; 00401763 mov eax,dword ptr [x1] 00401766 add eax,dword ptr [x2] 00401769 add eax,dword ptr [x3] } 0040176C pop ebp 0040176D ret 0Ch |
int __fastcall Add3(int x1, int x2, int x3) { 00401730 push ebp 00401731 mov ebp,esp 00401733 sub esp,8 00401736 mov dword ptr [ebp-8],edx 00401739 mov dword ptr [ebp-4],ecx return x1 + x2 + x3; 0040173C mov eax,dword ptr [x1] 0040173F add eax,dword ptr [x2] 00401742 add eax,dword ptr [x3] } 00401745 mov esp,ebp 00401747 pop ebp 00401748 ret 4 |
从上面可以看出函数自身的区别:
_stdcall 函数结束后在返回时ret 命令带个参数,代表参数所占的栈大小,在返回时会把参数弹出栈
__fastcall 函数结束后ret 命令同样带个参数,并且它在函数入口把寄存器中的参数再放入栈中,fast 这个词不名副其实啊!
----------------------------------------------------------------------------------------------
下面,以 __cdecl 调用为例,看看函数调用前后内存栈和主要寄存器的变化
测试代码:
int __cdecl Add2(int x1, int x2, int x3)
{
int tmp[10];
return x1 + x2 + x3;
}
ret = Add2(x[0], x[1], x[2]);
1. 在ret = Add2(x[0], x[1], x[2]); 执行前
内存地址 |
内存中存储的值 |
寄存器位置 |
0x74H |
… |
EBP |
… 0x68H |
X[3] |
|
0x64H |
ret |
ESP |
… |
… |
… |
2. 当执行完下面红色语句后,进入Add2 之前
ret = Add2(x[0], x[1], x[2]);
004017AF mov eax,dword ptr [ebp-4]
004017B2 push eax
004017B3 mov ecx,dword ptr [ebp-8]
004017B6 push ecx
004017B7 mov edx,dword ptr [x]
004017BA push edx
004017BB call Add2 (401750h)
004017C0 add esp,0Ch
004017C3 mov dword ptr [ret],eax
内存地址 |
内存中存储的值 |
寄存器位置 |
0x74H |
… |
EBP |
… 0x68H |
X[3] |
|
0x64H |
ret |
|
0x60H |
X3 |
|
0x5BH |
X2 |
|
0x58H |
X1 |
|
0x54H |
[EIP] |
ESP |
可见是参数不断压入栈中,最后压入要返回时的代码地址([EIP] 的值)
3. 进入函数Add2 正式执行用户代码前,执行红色代码后
int __cdecl Add2(int x1, int x2, int x3)
{
00401750 push ebp
00401751 mov ebp,esp
00401753 sub esp,28h
int tmp[10];
return x1 + x2 + x3;
00401756 mov eax,dword ptr [x1]
00401759 add eax,dword ptr [x2]
0040175C add eax,dword ptr [x3]
}
0040175F mov esp,ebp
00401761 pop ebp
00401762 ret
内存地址 |
内存中存储的值 |
寄存器位置 |
0x74H |
… |
|
… 0x68H |
X[3] |
|
0x64H |
ret |
|
0x60H |
X3 (Add2()) |
|
0x5BH |
X2 |
|
0x58H |
X1 |
|
0x54H |
[EIP] |
|
0x50H |
[EBP] |
EBP |
… 0x28H |
Tmp[10] |
ESP |
这里主要工作是保存EBP, 并设置到栈顶,这样可以通过EBP 访问该函数中的局部变量。另外,由于函数内声明了局部变量 int tmp[10] ,占用大小 10 * 4 = 28H 个字节,所以把ESP 下移。(栈总是向下扩展)
4. 然后是执行函数体代码,返回值放入EAX( 或AX,AL) 中。最后返回,返回前重设ESP,EBP 的值,通过栈中的[EIP] 的值返回到调用函数之前的代码处。
最终:
内存地址 |
内存中存储的值 |
寄存器位置 |
0x74H |
… |
EBP |
… 0x68H |
X[3] |
|
0x64H |
ret |
ESP |
0x60H |
X3 ( 没用了) |
|
0x5BH |
X2 ( 没用了) |
|
0x58H |
X1 ( 没用了) |
|
0x54H |
[EIP] ( 没用了) |
|
0x50H |
[EBP] ( 没用了) |
|
… 0x28H |
Tmp[10] ( 没用了) |