_stdcall、_cdecl、_fastcall 三种函数调用协议区别
调用协议的常用场合如下:
- _stdcall: Windows API 默认的函数调用协议
- _cdecl: C/C++ 默认的函数调用协议
- _fastcall: 适用于对性能要求较高的场合
函数参数的入栈方式包含如下几种:
- _stdcall: 函数参数从右向左入栈
- _cdecl: 函数参数从右向左入栈
- _fastcall: 从左开始将不大于4字节的参数放入CPU的ecx和edx寄存器,其余参数从右向左入栈。
栈平衡的修复方式包含以下几种:
- _stdcall: 函数调用结束后由被调用函数来平衡栈。
- _cdecl: 函数调用结束后由函数调用者来平衡栈。
- _fastcall: 函数调用结束后由被调用函数来平衡栈
对于Linux 程序来说,通常采用_cdecl的调用方式
对于 x86 程序:
- 普通函数传参:参数基本都压在栈上
- syscall 传参:eax 对应系统调用号,ebx、ecx、edx、esi、edi、ebp 分别对应前6个参数。多余的参数压在栈上
对于 x64 程序:
- 普通函数传参:先使用rdi、rsi、rdx、rcx、r8、r9 寄存器作为函数参数的前6个参数,多余的参数会依次压在栈上。
- syscall传参:rax 对应系统调用号,传参规则与函数传参一致。
说明:
- 调用者 ------- 调用函数的一方
- 被调用者 ------- 被调用的函数
- 比如在main函数中调用 printf()函数时,调用者为main(),被调用者为 printf()。
例子
cdecl是主要在C语言中使用的方式,调用者负责处理栈。
#include "stdio.h"
int add(int a,int b)
{
return (a+b);
}
int main(int argc,char* argv[])
{
return add(1,2);
}
add() 函数的参数1、2以逆序方式入栈,调用add()函数后,使用ADD ESP,8 命令整理栈。调用者main()函数直接清理其压入栈的函数参数。
stdcall方式常用于Win 32API,该方式由被调用者清理栈。前面讲解过C语言默认的函数调用方式为cdecl。若想使用stdcall方式编译源码,只要使用_stdcall关键字即可。
#include "stdio.h"
int _stdcall add(int a,int b)
{
return (a+b);
}
int main(int argc,char* argv[])
{
return add(1,2);
}
在main()函数中调用add()函数后,省略了清理栈的代码(ADD ESP,8)
栈的清理工作由add()函数中最后的RETN 8命令来执行。RETN 8命令的含义为RETN+POP8字节,即返回后使ESP增加到指定大小