i386调用约定
调用约定是由硬件的汇编指令call和ret的微指令和软件的汇编指令规范共同完成的
一般为如下顺序
- 父函数参数入栈(软件汇编代码)
- 父函数call(硬件微指令)
- 子函数栈建立(软件汇编代码)
- 子函数栈清理(软件汇编代码)
- 子函数ret(硬件微指令)
- 父函数栈清理(软件汇编代码)
下文中将演示代码 f(A1, A2, A3, A4, ..., An-1, An)
和 a.f(A1, A2, A3, A4, ..., An-1, An)
在不同的调用约定下, 栈的变化过程
其中, 用数字表示以上的6个阶段, 其后紧接着的是该阶段结束后的栈的状态和寄存器参数的示意
"["表示栈底, 处于地址高部, 由ebp/rbp寄存器的值直接决定, 其左边紧贴着的值的地址为ebp+0/rbp+0, 每往右一个参数地址递减4/8
最右边的参数由esp/rsp寄存器的值直接决定, 其地址为esp+0/rsp+0, 每往左一个参数地址递增4/8
"_"表示任意值
call指令只有一种, 因此1到2的变化都是一样的
ret指令有两种, 一种只出栈eip, 另一种不仅出栈eip, 还令esp增加一个数(即清理栈)
x86
每个参数大小为4B
__stdcall
传参寄存器: 无
- [, …, An, An-1, …, A4, A3, A2, A1
- [, …, An, An-1, …, A4, A3, A2, A1, 上级eip
- …, An, An-1, …, A4, A3, A2, A1, 上级eip, 上级ebp [
- [, …, An, An-1, …, A4, A3, A2, A1, 上级eip
- [, …
- [, …
返回值寄存器: eax
__cdecl
传参寄存器: 无
- [, …, An, An-1, …, A4, A3, A2, A1
- [, …, An, An-1, …, A4, A3, A2, A1, 上级eip
- …, An, An-1, …, A4, A3, A2, A1, 上级eip, 上级ebp [
- [, …, An, An-1, …, A4, A3, A2, A1, 上级eip
- [, …, An, An-1, …, A4, A3, A2, A1
- [, …
返回值寄存器: eax
__fastcall
传参寄存器: edx=A2, ecx=A1
- [, …, An, An-1, …, A4, A3
- [, …, An, An-1, …, A4, A3, 上级eip
- …, An, An-1, …, A4, A3, 上级eip, 上级ebp [, A2, A1
- [, …, An, An-1, …, A4, A3, 上级eip
- [, …
- [, …
返回值寄存器: eax
__vectorcall
略(用到时再补充试验)
thiscall
传参寄存器: ecx=this
- [, …, An, An-1, …, A4, A3, A2, A1
- [, …, An, An-1, …, A4, A3, A2, A1, 上级eip
- …, An, An-1, …, A4, A3, A2, A1, 上级eip, 上级ebp [, this
- [, …, An, An-1, …, A4, A3, A2, A1, 上级eip
- [, …
- [, …
返回值寄存器: eax
x64
每个参数大小为8B
__fastcall
传参寄存器: r9d=A4, r8d=A3, rdx=A2, rcx=A1
- [, …, An, An-1, …, A6, A5, _, _, _, _
- [, …, An, An-1, …, A6, A5, _, _, _, _, 上级eip
- [, …, An, An-1, …, A6, A5, A4, A3, A2, A1, 上级eip
- [, …, An, An-1, …, A6, A5, A4, A3, A2, A1, 上级eip
- [, …, An, An-1, …, A6, A5, A4, A3, A2, A1
- [, …, An, An-1, …, A6, A5, _, _, _, _
返回值寄存器: eax
父函数不能假定子函数一定给A1到A4在内存中预留的空间赋了值, 故应为任意值
注: 当参数数少于4时, 依然要在调用栈留下32B的空间, 即
- [, …, _, _, _, _
- [, …, _, _, _, _, 上级eip
- [, …, _, A3, A2, A1, 上级eip
- [, …, _, A3, A2, A1, 上级eip
- [, …, _, A3, A2, A1
- [, …, _, _, _, _
__vectorcall
当参数含__m128型数据时, 使用对应的xmm寄存器传参, 同时应将调用约定改为__vectorcall
传参寄存器: r9d/xmm3=A4, r8d/xmm2=A3, rdx/xmm1=A2, rcx/xmm0=A1
- [, …, An, An-1, …, A6, A5, _, _, _, _
- [, …, An, An-1, …, A6, A5, _, _, _, _, 上级eip
- [, …, An, An-1, …, A6, A5, A4, A3, A2, A1, 上级eip
- [, …, An, An-1, …, A6, A5, A4, A3, A2, A1, 上级eip
- [, …, An, An-1, …, A6, A5, A4, A3, A2, A1
- [, …, An, An-1, …, A6, A5, _, _, _, _
返回值寄存器: eax/xmm0
视具体情况, 第3步可能会腾出空间在内存中暂存数据, 如
传参寄存器: r9d=A4, r8d=A3, xmm1=A2, xmm0=A1
- [, …, An, An-1, …, A6, A5, _, _, _, _
- [, …, An, An-1, …, A6, A5, _, _, _, _, 上级eip
- [, …, An, An-1, …, A6, A5, A4, A3, &A2, &A1, 上级eip, _, A2(高位), A2(低位), A1(高位), A1(低位)
- [, …, An, An-1, …, A6, A5, A4, A3, &A2, &A1, 上级eip
- [, …, An, An-1, …, A6, A5, A4, A3, _, _
- [, …, An, An-1, …, A6, A5, _, _, _, _
thiscall
传参寄存器: r9d/xmm3=a3, r8d/xmm2=a2, rdx/xmm1=a1, rcx/xmm0=this
- [, …, An, An-1, …, A5, A4 _, _, _, _
- [, …, An, An-1, …, A5, A4, _, _, _, _, 上级eip
- [, …, An, An-1, …, A5, A4, A3, A2, A1, this, 上级eip
- [, …, An, An-1, …, A5, A4, A3, A2, A1, this, 上级eip
- [, …, An, An-1, …, A5, A4, A3, A2, A1, this
- [, …, An, An-1, …, A5, A4, _, _, _, _
返回值寄存器: eax/xmm0
相当于this成了原第一个参数的__fastcall或__vectorcall