函数调用约定就是描述参数如何传递,堆栈由调用方还是被调用方平衡,返回值如何返回等规则。
函数调用约定的几种类型有:__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 // 没有栈平衡操作
(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 // 平衡栈