1.前言
最近在做逆向时,突然感觉对__cdecl
,__stdcall
,__fastcall
,__thiscall
这些调用约定有些遗忘了,在网上找了一些博客,始终没有人把Linux和Win平台下函数调用约定区分开来。反而误导了我在Linux平台下的逆向工作。
既然已经踩过坑了,下面我便把这讲一讲。如有不当,不吝啬指点。
2.调用约定分类
2.1.按清除参数的方式分类
-
调用者清理栈中清除参数;
-
被调用者从栈中清除参数。在ASM代码中很容易识别此类约定,x86架构中
ret
指令允许使用一个可选的16位参数,该参数指定返回到调用方后要释放的堆栈字节数。这样的代码如下所示:ret 12
-
调用者或被调用者清理栈中清除参数,这种类型只有thiscall,thiscall用于调用C ++非静态成员函数。有两种主要的版本thiscall取决于编译器,以及是否该函数使用一个可变数量的参数使用。
3.Linux平台下常见的函数调用约定
3.1.Linux下x86平台
// TODO 目前大多数*nux都使用的x86-64版本,32位的比较少,只有使用十几年前的老系统来试验。才部分实用性比较低,暂时搁置在这里。以后有必要再补充吧。
3.2.Linux下x86-64平台
在x86-64架构的Solaris,Linux,FreeBSD,macOS上遵循System V AMD64 ABI的调用约定,并且是Unix和类Unix操作系统中的事实上的标准。x86-64上的OpenVMS调用标准基于System V ABI,并具有向后兼容所需的一些扩展。前6个整数或指针参数在寄存器RDI,RSI,RDX,RCX,R8,R9中传递(对于嵌套函数,R10用作静态链指针),与Microsoft x64调用约定中一样,其他参数在堆栈上传递。64位内的整数返回值存储在RAX中,而128位的返回值使用RAX和RDX存储。浮点返回值类似地存储在XMM0和XMM1中。较宽的YMM和ZMM寄存器用于代替XMM传递和返回较宽的值。
如果被调用放希望使用寄存器RBX,RSP,RBP和R12-R15,则在将控制权返回给调用方之前,它必须恢复其原始值。如果调用者希望保留所有其他寄存器的值,则必须保存所有其他寄存器。
对于叶节点函数(不调用任何其他函数的函数),仅在该函数的堆栈指针下方存储一个128(0x80)字节的空间。该空间称为红色区域。该区域不会被任何信号或中断处理程序所破坏。因此,编译器可以利用该区域来保存局部变量。通过使用该区域,编译器可能会在功能启动(RSP,RBP的调整)时省略一些指令。但是,其他功能可能会破坏此区域。因此,此区域应仅用于叶节点功能。gcc并clang提供该-mno-red-zone标志以禁用红区优化。
如果被调用方是可变参数函数,则必须由调用方在AL寄存器中提供矢量寄存器中传递给该函数的浮点参数的数量。
与Microsoft调用约定不同,它不提供阴影空间。在函数入口上,返回地址与堆栈上的第7个整数参数相邻。
在ubuntu1804系统上,使用下面用一段代码来说明函数调用约定:
int foo(int x1, int x2, int x3, int x4, int x5, int x6, int x7, int x8, int x9, int x10){
return x1+x2+x3+x4+x5+x6+x7+x8+x9+x10;
}
int main(){
foo(1,2,3,4,5,6,7,8,9,10);
return 0;
}
编译生成程序后,反汇编得到如下结果:
0000000000000644 <main>:
644: 55 push %rbp
645: 48 89 e5 mov %rsp,%rbp
648: 6a 0a pushq $0xa ; 第10个参数入栈
64a: 6a 09 pushq $0x9 ; 第9个参数入栈
64c: 6a 08 pushq $0x8 ; 第8个参数入栈
64e: 6a 07 pushq $0x7 ; 第7个参数入栈
650: 41 b9 06 00 00 00 mov $0x6,%r9d ; 将第6个参数放置在r9d寄存器
656: 41 b8 05 00 00 00 mov $0x5,%r8d ; 将第5个参数放置在r8d寄存器
65c: b9 04 00 00 00 mov $0x4,%ecx ; 将第4个参数放置在ecx寄存器
661: ba 03 00 00 00 mov $0x3,%edx ; 将第3个参数放置在edx寄存器
666: be 02 00 00 00 mov $0x2,%esi ; 将第2个参数放置在esi寄存器
66b: bf 01 00 00 00 mov $0x1,%edi ; 将第1个参数放置在edi寄存器
670: e8 85 ff ff ff callq 5fa <foo> ; 调用foo函数
675: 48 83 c4 20 add $0x20,%rsp
679: b8 00 00 00 00 mov $0x0,%eax
67e: c9 leaveq
67f: c3 retq
从汇编代码中静态地址0x648
处可以看出,先依次将0xa,0x9,0x8,0x7入栈,然后使用寄存器RDI,RSI,RDX,RCX,R8,R9中传递前6个参数。
System V AMD64 ABI调用约定的进一步详解,点击查看,《System V Application Binary Interface》的SEC-3.2 Function Calling Sequence,在16页
4.Win平台下常见的函数调用约定
4.1.Win下x86平台
4.1.1.cdecl
cdecl(C declaration)是 ,x86架构下微软编译器和许多C编译器使用的C语言的调用约定。在cdecl中,被调用函数的参数在堆栈上传递。整数值和存储器地址在EAX寄存器中返回,x87中浮点值在ST0寄存器中返回。调用者在必要时,需要保存寄存器EAX,ECX和EDX,其余寄存器的保存,由被调用者完成。在x87中调用新函数时,浮点寄存器ST0至ST7必须为空(弹出或释放),并且退出函数时ST1至ST7必须为空。当不用于返回值时,ST0也必须为空。
x87是x86体系结构指令集的与浮点相关的子集。它是对8086指令集的扩展,它以可选的浮点协处理器的形式与相应的x86 CPU协同工作。
在C编程语言的环境中,函数参数以从右到左的顺序被压入堆栈,即最后一个参数被首先压入。
C编程的可变函数的调用约定,必须使用**__cdecl
**
考虑以下C源代码片段:
int __cdecl func_cdecl(int x1,
int x2,
int x3,
int x4,
int x5,
int x6,
int x7){
return x1+x2+x3+x4+x5+x6+x7;
}
int main(){
int ret = 0;
ret = func_cdecl(1,2,3,4,5,6,7);
return ret;
}
生成程序反汇编,main函数的反汇编结果如下:
.00401020: 55 push ebp
.00401021: 8B EC mov ebp, esp
.00401023: 51 push ecx
.00401024: C7 45 FC 00 00 00 00 mov [ebp-04h], 00000000h
.0040102B: 6A 07 push 00000007h ; 第7个参数入栈
.0040102D: 6A 06 push 00000006h ; 第6个参数入栈
.0040102F: 6A 05 push 00000005h ; 第5个参数入栈
.00401031: 6A 04 push 00000004h ; 第4个参数入栈
.00401033: 6A 03 push 00000003h ; 第3个参数入栈
.00401035: 6A 02 push 00000002h ; 第2个参数入栈
.00401037: 6A 01 push 00000001h ; 第1个参数入栈
.00401039: E8 C2 FF FF FF call 00401000h ; 调用函数func_cdecl
.0040103E: 83 C4 1C add esp, 1Ch
.00401041: 89 45 FC mov [ebp-04h], eax
.00401044: 8B 45 FC mov eax, [ebp-04h]
.00401047: 8B E5 mov esp, ebp
.00401049: 5D pop ebp
.0040104A: C3 ret
4.1.2.stdcall
stdcall调用约定用来调用Win32 API函数。被调用者清理栈上的参数。
cdecl与stdcall的区别:
- cdecl在被调用函数 (Callee) 返回后,由调用方 (Caller) 调整堆栈。stdcall在被调用函数 (Callee) 返回前,由被调用函数 (Callee) 调整堆栈。
- cdecl每个调用的地方都需要生成一段调整堆栈的代码,所以最后生成的文件较大。stdcall调整堆栈的代码只存在在一个地方(被调用函数的代码内),所以最后生成的文件较小。
- cdecl函数的参数个数可变(就像 printf 函数一样),因为只有调用者才知道它传给被调用函数几个参数,才能在调用结束时适当地调整堆栈。stdcall函数的参数个数不能是可变的。
- cdecl对于定义在 C 程序文件中的输出函数,函数名会保持原样,不会被修饰。stdcall不论是 C 程序文件中的输出函数还是 C++ 程序文件中的输出函数,函数名都会被修饰。
考虑以下C源代码片段:
int __stdcall func_stdcall(int x1,
int x2,
int x3,
int x4,
int x5,
int x6,
int x7){
return x1+x2+x3+x4+x5+x6+x7;
}
float __stdcall func_stdcall_float(float f1,
float f2,
float f3,
float f4,
float f5,
float f6,
float f7){
float t1= 1+f1+f2-f3*f4/f5;
int t2 = 1;
if (t1 > f6){
int t2 = (int)f7;
}
return t1 * t2;
}
int main(){
int ret = 0;
ret = func_stdcall(1,2,3,4,5,6,7);
float f1 = 1.1f;
float f2 = 0.2f * f1;
float ret_float = func_stdcall_float(10.0f,20.0f,30.0f,40.0f,50.0f,60.0f,70.0f);
ret_float = ret_float + 111.f;
return ret;
}
生成程序反汇编,main函数的反汇编结果如下:
.00401080: 55 push ebp
.00401081: 8B EC mov ebp, esp
.00401083: 83 EC 10 sub esp, 10h
.00401086: C7 45 F4 00 00 00 00 mov [ebp-0Ch], 00000000h
.0040108D: 6A 07 push 00000007h
.0040108F: 6A 06 push 00000006h
.00401091: 6A 05 push 00000005h
.00401093: 6A 04 push 00000004h
.00401095: 6A 03 push 00000003h
.00401097: 6A 02 push 00000002h
.00401099: 6A 01 push 00000001h
.0040109B: E8 60 FF FF FF call 00401000h ; 开始调用func_stdcall函数
.004010A0: 89 45 F4 mov [ebp-0Ch], eax ; 将返回值赋值给ret
.004010A3: D9 05 98 C2 40 00 fld [0040C298h] ; 将1.1f装载到FPU寄存器堆栈
.004010A9: D9 5D F0 fstp [ebp-10h] ; 将FPU寄存器栈顶的1.1f弹出,并存入局部变量
.004010AC: D9 45 F0 fld [ebp-10h] ; 将1.1f装载到FPU寄存器堆栈
.004010AF: DC 0D 90 C2 40 00 fmul qword [0040C290h] ; FPU寄存器栈顶的1.1f与0.2f相乘,并将结果存入FPU寄存器栈顶
.004010B5: D9 5D F8 fstp [ebp-08h] ; 将FPU寄存器栈顶的乘积结果弹出,并存入局部变量
.004010B8: 51 push ecx ; ESP向下调整4个字节,将ECX寄存器中的值放入栈顶
.004010B9: D9 05 88 C2 40 00 fld [0040C288h] ; 将70.0f放入FPU寄存器栈顶
.004010BF: D9 1C 24 fstp [esp] ; 将FPU寄存器栈顶的70.0f弹出,并存入栈顶
.004010C2: 51 push ecx
.004010C3: D9 05 84 C2 40 00 fld [0040C284h]
.004010C9: D9 1C 24 fstp [esp]
.004010CC: 51 push ecx
.004010CD: D9 05 80 C2 40 00 fld [0040C280h]
.004010D3: D9 1C 24 fstp [esp]
.004010D6: 51 push ecx
.004010D7: D9 05 7C C2 40 00 fld [0040C27Ch]
.004010DD: D9 1C 24 fstp [esp]
.004010E0: 51 push ecx
.004010E1: D9 05 78 C2 40 00 fld [0040C278h]
.004010E7: D9 1C 24 fstp [esp]
.004010EA: 51 push ecx
.004010EB: D9 05 74 C2 40 00 fld [0040C274h]
.004010F1: D9 1C 24 fstp [esp]
.004010F4: 51 push ecx ; ESP向下调整4个字节,将ECX寄存器中的值放入栈顶
.004010F5: D9 05 70 C2 40 00 fld [0040C270h] ; 将10.0f放入FPU寄存器栈顶
.004010FB: D9 1C 24 fstp [esp] ; 将FPU寄存器栈顶的10.0f弹出,并存入栈顶
.004010FE: E8 1D FF FF FF call 00401020h ; 调用func_stdcall_float函数
.00401103: D9 5D FC fstp [ebp-04h] ; 将FPU栈顶的返回结果存入栈空间
.00401106: D9 45 FC fld [ebp-04h] ; 将返回结果存入FPU栈顶
.00401109: DC 05 68 C2 40 00 fadd qword [0040C268h] ; 将FPU栈顶的结果与111.0f相加
.0040110F: D9 5D FC fstp [ebp-04h] ; 将相加后的结果存入栈空间
.00401112: 8B 45 F4 mov eax, [ebp-0Ch] ;设置返回值
.00401115: 8B E5 mov esp, ebp ; 设置esp
.00401117: 5D pop ebp ; 恢复到main函数调用者的栈空间中
.00401118: C3 ret ; 返回
原程序中的浮点数,编译后被放在.rdata
section。如下图所示:
常见的x86浮点数指令:
4.1.3.fastcall
将前两个参数分别放入ECX和EDX,其余参数从右到左依次入栈。
考虑以下C源代码片段:
int __fastcall func_fastcall(int x1,
int x2,
int x3,
int x4,
int x5,
int x6,
int x7){
return x1+x2+x3+x4+x5+x6+x7;
}
class Foo{
public:
int __fastcall member_func_fastcall(int x1,
int x2,
int x3,
int x4,
int x5,
int x6,
int x7){
return x1+x2+x3+x4+x5+x6+x7;
}
};
int main(){
int ret = 0;
ret = func_fastcall(1,2,3,4,5,6,7);
Foo f1,f2;ret = f1.member_func_fastcall(1,2,3,4,5,6,7);
return ret;
}
生成程序反汇编,main函数的反汇编结果如下:
.00401060: 55 push ebp
.00401061: 8B EC mov ebp, esp
.00401063: 83 EC 0C sub esp, 0Ch
.00401066: C7 45 F8 00 00 00 00 mov [ebp-08h], 00000000h
.0040106D: 6A 07 push 00000007h
.0040106F: 6A 06 push 00000006h
.00401071: 6A 05 push 00000005h
.00401073: 6A 04 push 00000004h
.00401075: 6A 03 push 00000003h
.00401077: BA 02 00 00 00 mov edx, 00000002h
.0040107C: B9 01 00 00 00 mov ecx, 00000001h -> 1 bytes
.00401081: E8 7A FF FF FF call 00401000h ; 调用func_fastcall函数
.00401086: 89 45 F8 mov [ebp-08h], eax
.00401089: 6A 07 push 00000007h
.0040108B: 6A 06 push 00000006h
.0040108D: 6A 05 push 00000005h
.0040108F: 6A 04 push 00000004h
.00401091: 6A 03 push 00000003h
.00401093: 6A 02 push 00000002h
.00401095: BA 01 00 00 00 mov edx, 00000001h
.0040109A: 8D 4D F7 lea ecx, [ebp-09h] ; 将this指针放置在ECX寄存器中
.0040109D: E8 8E FF FF FF call 00401030h ; 调用member_func_fastcall函数
.004010A2: 89 45 F8 mov [ebp-08h], eax
.004010A5: 8B 45 F8 mov eax, [ebp-08h]
.004010A8: 8B E5 mov esp, ebp
.004010AA: 5D pop ebp
.004010AB: C3 ret
1问:空类,即不含任何成员变量和虚函数的类,空类大小为什么是1btye?
C++标准禁止对象大小为 0,因为两个不同的对象需要不同的地址表示。this指针始终指向对象空间,在栈中实例化一个空类对象时,this指向的这1byte空间。
在VS2008环境中使用如下代码,测试this指针和类实例地址的关系:
#include <cstdio> class Foo{ public: void func(){ printf("thisptr->%p\n",this);} }; int main(){ Foo f1; printf("f1 addr->%p, ",&f1); f1.func(); return 0; }
编译程序输出的结果是:
f1 addr->0039FA33, thisptr->0039FA33
4.1.4.thiscall
这种约定用于类的成员函数,参数从右到左依次入栈,this指针放置在ECX寄存器中。可变参数的函数,不适用该约定。
被调用者清理参数。
考虑以下C源代码片段:
class Foo{
public:
int __thiscall func_thiscall(int x1,
int x2,
int x3,
int x4,
int x5,
int x6,
int x7){
return x1+x2+x3+x4+x5+x6+x7;
}
};
int main(){
int ret = 0;
Foo f;ret = f.func_thiscall(1,2,3,4,5,6,7);
return ret;
}
生成程序反汇编,main函数的反汇编结果如下:
.00401030: 55 push ebp
.00401031: 8B EC mov ebp, esp
.00401033: 83 EC 08 sub esp, 08h
.00401036: C7 45 F8 00 00 00 00 mov [ebp-08h], 00000000h
.0040103D: 6A 07 push 00000007h
.0040103F: 6A 06 push 00000006h
.00401041: 6A 05 push 00000005h
.00401043: 6A 04 push 00000004h
.00401045: 6A 03 push 00000003h
.00401047: 6A 02 push 00000002h
.00401049: 6A 01 push 00000001h
.0040104B: 8D 4D FF lea ecx, [ebp-01h] ; 将this指针放置在ECX寄存器中
.0040104E: E8 AD FF FF FF call 00401000h ( -78 bytes )
.00401053: 89 45 F8 mov [ebp-08h], eax
.00401056: 8B 45 F8 mov eax, [ebp-08h]
.00401059: 8B E5 mov esp, ebp
.0040105B: 5D pop ebp
.0040105C: C3 ret
4.2.Win下x86-64平台
在Windows x86-64平台上遵循Microsoft x64调用约定,对于x86-64上的long mode(长模式),并预引导UEFI
UEFI,全称为Unified Extensible Firmware Interface,统一可扩展固件接口。它是一种个人电脑系统规格,用来定义操作系统与系统固件之间的软件接口,作为BIOS的替代方案。
long mode,长模式。在x86-64 计算机体系结构中,长模式是64位操作系统可以访问64位指令和寄存器的模式。64位程序在64位模式的子模式下运行,而32位程序和16位受保护模式的程序在兼容模式的子模式下运行。
前四个参数放置在寄存器中,这意味着RCX,RDX,R8,R9用于整数,结构或指针参数(按此顺序),而XMM0,XMM1,XMM2,XMM3用于浮点参数。其他参数将压入堆栈(从右到左)。如果小于或等于64位,则在RAX中返回整数返回值(类似于x86)。浮点返回值在XMM0中返回。少于64位长的参数/返回值,高位不会清零。
大小和整数匹配的结构和联合将被传递并返回,就好像它们是整数一样。否则,当用作参数时,它们将被指针替换。当需要返回超大的结构时,会将指向调用者提供的空间的另一个指针作为第一个参数,并将所有其他参数向右移一个位置。
在Windows环境中(无论使用Microsoft还是非Microsoft工具)针对x64体系结构进行编译时,stdcall,thiscall,cdecl和fastcall都解析为使用此约定。
在Microsoft x64调用约定中,在调用函数之前(无论使用的实际参数数如何),并在调用之后弹出堆栈,调用者都有责任在堆栈上分配32个字节的“阴影空间”。阴影空间用于溢出RCX,RDX,R8和R9寄存器。
例如,具有5个整数参数的函数将采用寄存器中的第一个至第四个,而第五个将被推入阴影空间的顶部。因此,当输入被调用的函数时,堆栈将由(按升序)返回地址组成,后跟阴影空间(32字节),后跟第五个参数。下面是一段简单的C代码,来说明32个字节的“阴影空间”。
int foo(int x1,
int x2,
int x3,
int x4,
int x5){
return x1+x2+x3+x4+x5;
}
int main(){
int ret = foo(1,2,3,4,5);
return 0;
}
生成程序后,反汇编的结果如下:
000000013FA01000 | mov dword ptr ss:[rsp+20],r9d |
000000013FA01005 | mov dword ptr ss:[rsp+18],r8d |
000000013FA0100A | mov dword ptr ss:[rsp+10],edx |
000000013FA0100E | mov dword ptr ss:[rsp+8],ecx |
000000013FA01012 | mov ecx,dword ptr ss:[rsp+10] |
000000013FA01016 | mov eax,dword ptr ss:[rsp+8] |
000000013FA0101A | add eax,ecx |
000000013FA0101C | add eax,dword ptr ss:[rsp+18] |
000000013FA01020 | add eax,dword ptr ss:[rsp+20] |
000000013FA01024 | add eax,dword ptr ss:[rsp+28] |
000000013FA01028 | ret |
000000013FA01029 | int3 |
000000013FA0102A | int3 |
000000013FA0102B | int3 |
000000013FA0102C | int3 |
000000013FA0102D | int3 |
000000013FA0102E | int3 |
000000013FA0102F | int3 |
000000013FA01030 | sub rsp,48 |
000000013FA01034 | mov dword ptr ss:[rsp+20],5 |; 第5个参数入栈
000000013FA0103C | mov r9d,4 |
000000013FA01042 | mov r8d,3 |
000000013FA01048 | mov edx,2 |
000000013FA0104D | mov ecx,1 |
000000013FA01052 | call demo_x64.13FA01000 |
000000013FA01057 | mov dword ptr ss:[rsp+30],eax |
000000013FA0105B | xor eax,eax |
000000013FA0105D | add rsp,48 |
000000013FA01061 | ret |
在000000013FA01030
处,栈顶向小调整0x48,并将ss:[rsp+0x20]
处设置为5,将第5个参数入栈。堆栈现场如下:
1问:在栈中00000000001BF8E8地址处,保存了一个字符指针0000000000512B70,该字符指针指向的字符串是
__COMPAT_LAYER=RunAsAdmin
,__COMPAT_LAYER=RunAsAdmin
有什么作用?答:__COMPAT_LAYER是一个系统环境变量,可让您设置兼容性层,即在右键单击可执行文件,选择“属性”并转到“兼容性”选项卡时可以调整的设置。如下图所示:
__COMPAT_LAYER常见的设置参数有:
- RunAsInvoker:应用程序应以与父进程相同的Windows特权和用户权限运行。此设置等效于没有该应用程序的应用程序兼容性数据库。该应用程序以与启动它的父进程相同的特权启动,从而减少了应用程序的安全风险。这是因为对于大多数应用程序,父级是Explorer.exe,它作为标准用户应用程序运行。
- RunAsHighest:应用程序应以当前用户可以获得的最高Windows特权和用户权限运行,但是该应用程序不一定要求该用户为管理员。该应用程序既可以由管理员也可以由标准用户运行,并且可以根据用户的特权和用户权限来调整其行为。该应用程序需要比标准用户更大的特权和用户权限,但是不需要该用户成为本地Administrators组的成员。
- RunAsAdmin:该应用程序应仅针对管理员运行,必须使用完整的管理员访问令牌启动,并且不能在标准用户上下文中正确运行。此请求的执行级别标记保留给要求用户成为本地Administrators组成员的Windows Vista以前的应用程序保留。
- 256Color:运行在256个色
- 640x480:以640x480屏幕分辨率运行
- DisableThemes:禁用视觉主题
可以使用多个选项,方法是使用空格将它们分开: __COMPAT_LAYER=
256Color 640x480
参考:
- https://www.itninja.com/question/can-you-please-explain-how-the-runasadmin-run-as-invoker-runashighest-shims-works
- https://ss64.com/nt/syntax-elevate.html
- https://ss64.com/nt/syntax-variables.html
- https://stackoverflow.com/questions/37878185/what-does-compat-layer-actually-do
在000000013FA01052 | call demo_x64.13FA01000
处,已经完成函数参数的设定,准备调用函数。
进入函数后,首先执行了:mov dword ptr ss:[rsp+20],r9d
,mov dword ptr ss:[rsp+18],r8d
,mov dword ptr ss:[rsp+10],edx
,mov dword ptr ss:[rsp+8],ecx
,4条指令。将r9d,r8d,edx,ecx寄存器中的内容逐出到栈,此部分栈区域被称为32个字节的“阴影空间”。堆栈现场如下:
此外,在x86-64中,Visual Studio 2008将浮点数存储在XMM6和XMM7(以及XMM8到XMM15)中。因此,对于x86-64,用户编写的汇编语言例程必须保留XMM6和XMM7(与x86相比,其中用户编写的汇编语言例程不需要保留XMM6和XMM7)。换句话说,当从x86移植到x86-64时,必须在函数之前/之后更新用户编写的汇编语言例程,以保存/恢复XMM6和XMM7 。
从Visual Studio 2013开始,Microsoft引入了__vectorcall调用约定,该约定扩展了x64约定。
5.函数调用约定汇总表
这些约定主要用于C / C ++编译器(尤其是下面的64位部分),因此在很大程度上是特殊情况。其他语言可能在其实现中使用其他格式和约定。
Architecture | Name | Operating system compiler | Use Registers to pass Params | Stack order of Params | Stack cleanup | Notes |
---|---|---|---|---|---|---|
8086 | cdecl | RTL © | Caller | |||
8086 | Pascal | LTR (Pascal) | Callee | |||
8086 | fastcall (non-member) | Microsoft | AX, DX, BX | LTR (Pascal) | Callee | Return pointer in BX. |
8086 | fastcall (member function) | Microsoft | AX, DX | LTR (Pascal) | Callee | this pointer on stack low address. Return pointer in AX. |
8086 | fastcall | Turbo C | AX, DX, BX | LTR (Pascal) | Callee | this pointer on stack low address. Return pointer on stack high address. |
8086 | Watcom | AX, DX, BX, CX | RTL © | Callee | Return pointer in SI. | |
IA-32 | cdecl | Unix-like (GCC) | RTL © | Caller | When returning struct/class, the calling code allocates space and passes a pointer to this space via a hidden parameter on the stack. The called function writes the return value to this address. Stack aligned on 16-byte boundary due to a bug. | |
IA-32 | cdecl | Microsoft | RTL © | Caller | When returning struct/class, - Plain old data(POD) return values 32 bits or smaller are in the EAX register - POD return values 33–64 bits in size are returned via the EAX:EDX registers. - Non-POD return values or values larger than 64-bits, the calling code will allocate space and passes a pointer to this space via a hidden parameter on the stack. The called function writes the return value to this address. Stack aligned on 4-byte boundary. | |
IA-32 | stdcall | Microsoft | RTL © | Callee | Also supported by GCC. | |
IA-32 | fastcall | Microsoft | ECX, EDX | RTL © | Callee | Return pointer on stack if not member function. Also supported by GCC. |
IA-32 | register | Delphi and Free Pascal | EAX, EDX, ECX | LTR (Pascal) | Callee | |
IA-32 | thiscall | Windows (Microsoft Visual C++) | ECX | RTL © | Callee | Default for member functions. |
IA-32 | vectorcall | Windows (Microsoft Visual C++) | ECX, EDX, [XY]MM0–5 | RTL © | Callee | Extended from fastcall. Also supported by ICC and Clang. |
IA-32 | Watcom compiler | EAX, EDX, EBX, ECX | RTL © | Callee | Return pointer in ESI. | |
x86-64 | Microsoft x64 calling convention[18] | Windows( Microsoft Visual C++, GCC, Intel C++ Compiler, Delphi), UEFI | RCX/XMM0, RDX/XMM1, R8/XMM2, R9/XMM3 | RTL © | Caller | Stack aligned on 16 bytes. 32 bytes shadow space on stack. The specified 8 registers can only be used for parameters 1 through 4. For C++ classes, the hiddenthis pointer parameter is the first parameter, and is passed in RCX. |
x86-64 | vectorcall | Windows( Microsoft Visual C++, Clang, ICC) | RCX/[XY]MM0, RDX/[XY]MM1, R8/[XY]MM2, R9/[XY]MM3 + [XY]MM4–5 | RTL © | Caller | Extended from MS x64. |
x86-64 | System V AMD64 ABI[25] | Solaris, Linux, BSD, macOS, OpenVMS(GCC, Intel C++ Compiler, Clang, Delphi) | RDI, RSI, RDX, RCX, R8, R9, [XYZ]MM0–7 | RTL © | Caller | Stack aligned on 16 bytes boundary. 128 bytes red zone below stack. The kernel interface uses RDI, RSI, RDX, R10, R8 and R9. In C++,this pointer is the first parameter. |
参考
https://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions
https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface
https://cs61.seas.harvard.edu/site/2018/Asm2/#:~:text=Calling%20conventions%20constrain%20both%20callers%20and%20callees.%20A,learn%20the%20x86-64%20calling%20conventions%20for%20Linux%20.
https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf
https://gcc.gnu.org/onlinedocs/gcc/x86-Function-Attributes.html#index-force_005falign_005farg_005fpointer-function-attribute_002c-x86
https://docs.microsoft.com/en-us/cpp/cpp/cdecl?view=msvc-160
https://docs.microsoft.com/en-us/cpp/cpp/clrcall?view=msvc-160
https://docs.microsoft.com/en-us/cpp/cpp/stdcall?view=msvc-160
https://docs.microsoft.com/en-us/cpp/cpp/fastcall?view=msvc-160
https://docs.microsoft.com/en-us/cpp/cpp/thiscall?view=msvc-160
https://docs.microsoft.com/en-us/cpp/cpp/vectorcall?view=msvc-160
http://www.openrce.org/articles/files/jangrayhood.pdf