x86 程序的调用约定很多,常用调用约定由:cdecl, stdcall, fastcall。
x86-64 程序的调用约定只有两种:Microsoft x64 calling convention, System V AMD64 ABI。
x86 程序的调用约定
编译方法
64位平台编译32位汇编的方法如下
gcc -m32 -S test.c
主要区别
区别 | cdecl | stdcall | fastcall |
---|---|---|---|
寄存器 | ECX, EDX | ||
入栈 | 全部参数 | 全部参数 | 从第三个参数开始 |
调用者清理 | 是 | ||
被调用者清理 | 是 | 是 |
通过具体汇编代码进行说明
以下汇编均通过 Compiler Explorer 生成。使用 x86-64 gcc 11.1
cdecl
cdecl 是 x86 程序和库的默认调用约定。
int callee(int,int,int);
int caller(void)
{
return callee(1,2,3) + 5;
}
- 参数从右到左进行压栈
- 调用者清理参数
caller:
pushl %ebp
movl %esp, %ebp
subl $8, %esp ; reserves 8 bytes for local variables
subl $4, %esp ; for alignment
pushl $3
pushl $2
pushl $1
call callee
addl $16, %esp ; clean parameters (3 paras + 1 alignment)
addl $5, %eax
leave
ret
stdcall
stdcall 主要用于 32位的 windows。
__attribute__((stdcall)) int callee(int,int,int);
int caller(void)
{
return callee(1,2,3) + 5;
}
- 参数从右到左进行压栈
- 被调用者清理参数
caller:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
subl $4, %esp ; for alignment
pushl $3
pushl $2
pushl $1
call callee
addl $4, %esp ; only clean the alignment
addl $5, %eax
leave
ret
fastcall
__attribute__((fastcall)) int callee(int,int,int);
int caller(void)
{
return callee(1,2,3) + 5;
}
- 前两个参数由 ECX, EDX 进行传递
- 后面到参数自右向左进行压栈
- 被调用者清理
caller:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
subl $12, %esp ; for alignment
pushl $3
movl $2, %edx ; second parameter
movl $1, %ecx ; first parameter
call callee
addl $12, %esp ; only clean the alignment
addl $5, %eax
leave
ret
x86-64 调用约定
x86-64 调用约定主要使用更多的寄存器来传递参数。
int callee(int,int,int,int,int,int,int);
int caller(void)
{
return callee(1,2,3,4,5,6,7) + 8;
}
Microsoft x64 calling convention
微软x64调用约定
- 使用RCX, RDX, R8, R9四个寄存器用于存储函数调用时的4个参数(从左到右),
- 使用XMM0, XMM1, XMM2, XMM3来传递浮点变量
- 其他的参数直接入栈(从右至左)
- 整型返回值放置在RAX中,浮点返回值在XMM0中。
汇编
使用 x64 msvc v19.10 (WINE)
caller PROC
$LN3:
sub rsp, 72 ; 00000048H
mov DWORD PTR [rsp+48], 7
mov DWORD PTR [rsp+40], 6
mov DWORD PTR [rsp+32], 5 ; other paras on stack
mov r9d, 4
mov r8d, 3
mov edx, 2
mov ecx, 1
call callee
add eax, 8
add rsp, 72 ; 00000048H
ret 0
caller ENDP
System V AMD64 ABI
此约定主要在Solaris,GNU/Linux,FreeBSD和其他非微软OS上使用
- 使用寄存器RDI, RSI, RDX, RCX, R8和R9存储函数调用时的前6个参数(从左到右)
- 使用XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 和 XMM7 用来放置浮点变量
- 其他的参数直接入栈(从右至左)
- 整型返回值放置在RAX中,浮点返回值在XMM0中。
汇编
使用 x86-64 gcc 11.1
caller:
pushq %rbp
movq %rsp, %rbp
subq $8, %rsp
pushq $7 ; other paras on stack
movl $6, %r9d
movl $5, %r8d
movl $4, %ecx
movl $3, %edx
movl $2, %esi
movl $1, %edi
call callee
addq $16, %rsp
addl $8, %eax
leave
ret
参考: