函数调用约定就是描述参数如何传递,堆栈由调用方还是被调用方平衡,返回值如何返回等规则。
函数调用约定的几种类型有:__stdcall, __cdecl, __fastcall, __thiscall, __nakedcall, __pascal
下面介绍几种常见的函数调用约定(以VS2010编译器为例):
(1) __cdecl调用约定
1. 参数从右向左传递,放在栈中
2. 栈平衡由调用函数来执行
3. 不定参数的函数可以使用
下面看一个汇编的例子
int a = 1, b = 2;
mov dword ptr [a],1
mov dword ptr [b],2
int sum = Sum(a, b);
mov eax,dword ptr [b] // 参数从右向左压入栈,压入参数b
push eax
mov ecx,dword ptr [a] // 参数从右向左压入栈,压入参数a
push ecx
call Sum (13611A9h) // 调用函数
add esp,8 // 调用方平衡堆栈(弹出参数)
mov dword ptr [sum],eax // 返回值保存在eax中
int __cdecl Sum(int a, int b)
{
push ebp // 保存上一层函数栈底指针
mov ebp,esp // 设置本层函数栈底指针
sub esp,0C0h // 设置本层函数栈顶指针
push ebx // 保存寄存器的值:ebx、esi、edi
push esi
push edi
lea edi,[ebp-0C0h] // 栈内容赋初值(调试代码中使用)
mov ecx,30h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
return a+b;
mov eax,dword ptr [a]
add eax,dword ptr [b] // 将返回值保存在eax中
}
pop edi // 恢复寄存器的值:edi、esi、ebx(相反的顺序弹出)
pop esi
pop ebx
mov esp,ebp // 恢复上层函数栈顶指针
pop ebp // 恢复上层函数栈底指针
ret // 没有栈平衡操作
__cdecl是c/c++默认的调用方式。从上面的汇编代码可以看到,__cdecl调用方式在函数内没有任何平衡参数的操作,而在退出函数后对esp执行了加8(add esp,8)的操作。C语言中经常使用的printf函数就是典型的__cdecl调用方式,由于printf的参数可以有多个,所以只能以__cdecl方式调用。
(2)__stdcall调用约定
1. 参数从右向左传递,放在栈中
2. 栈平衡操作由被调用函数执行
3. 不定参数的函数无法使用
看一下汇编的例子
int a = 1, b = 2;
mov dword ptr [a],1
mov dword ptr [b],2
int sum = Sum(a, b);
mov eax,dword ptr [b] // 参数从右向左压入栈,压入参数b
push eax
mov ecx,dword ptr [a] // 参数从右向左压入栈,压入参数a
push ecx
call Sum (13611C2h)
mov dword ptr [sum],eax // 没有平衡栈操作,平衡操作由函数Sum内部完成
int __stdcall Sum(int a, int b)
{
push ebp
mov ebp,esp
sub esp,0C0h
push ebx
push esi
push edi
lea edi,[ebp-0C0h]
mov ecx,30h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
return a+b;
mov eax,dword ptr [a]
add eax,dword ptr [b]
}
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 8 // 平衡栈操作,栈弹出8个字节,等价于esp += 8
__stdcall与__cdecl除了栈平衡任务由谁来完成以外,其余部分一样。(3)__fastcall调用约定
1. 最左边的两个不大于4字节的参数分别放在ecx和edx寄存器,其余参数仍然从右到左压入栈
2. 被调用方平衡栈
3. 不定参数无法使用
例子:
int a = 1, b = 2, c = 3;
mov dword ptr [a],1
mov dword ptr [b],2
mov dword ptr [c],3
int sum = Sum(a, b, c);
mov eax,dword ptr [c]
push eax
mov edx,dword ptr [b] // 最左边的第二个参数由edx传递
mov ecx,dword ptr [a] // 最左边的第一个参数由ecx传递
call Sum (0E611CCh)
mov dword ptr [sum],eax // 没有平衡栈操作,平衡栈操作由被调用方完成
int __fastcall Sum(int a, int b, int c)
{
push ebp
mov ebp,esp
sub esp,0D8h
push ebx
push esi
push edi
push ecx
lea edi,[ebp-0D8h]
mov ecx,36h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
pop ecx
mov dword ptr [ebp-14h],edx
mov dword ptr [ebp-8],ecx
return a+b+c;
mov eax,dword ptr [a]
add eax,dword ptr [b]
add eax,dword ptr [c]
}
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 4 // 平衡栈,4个字节,因为前两个参数是通过寄存器传递的,只有第三个参数压入栈中了
但是,对于浮点值、远指针和__int64类型总是通过栈来传递。
(4) __thiscall调用约定
thiscall仅仅用于c++成员函数。this指针存放于ecx寄存器中,参数从右到左压栈,被调用方平衡栈。thiscall不是关键词不能被程序员指定。
例子:
int a = 1, b = 2;
mov dword ptr [a],1
mov dword ptr [b],2
CTest test;
int sum = test.Sum(a, b);
mov eax,dword ptr [b]
push eax
mov ecx,dword ptr [a]
push ecx
lea ecx,[test] // 对象指针通过ecx传递
call CTest::Sum (1911D1h)
mov dword ptr [sum],eax
class CTest
{
public:
int Sum(int a, int b)
{
push ebp
mov ebp,esp
sub esp,0CCh
push ebx
push esi
push edi
push ecx
lea edi,[ebp-0CCh]
mov ecx,33h
mov eax,0CCCCCCCCh
rep stos dword ptr es:[edi]
pop ecx
mov dword ptr [ebp-8],ecx
return a+b;
mov eax,dword ptr [a]
add eax,dword ptr [b]
}
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret 8 // 平衡栈