Linux和Win平台下函数调用参数传递约定详解

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。如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i7FH43wJ-1615535970898)(/home/cmp/.config/Typora/typora-user-images/image-20210311201546629.png)]

常见的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],r9dmov dword ptr ss:[rsp+18],r8dmov dword ptr ss:[rsp+10],edxmov 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位部分),因此在很大程度上是特殊情况。其他语言可能在其实现中使用其他格式和约定。

ArchitectureNameOperating system
compiler
Use Registers
to pass Params
Stack order
of Params
Stack cleanupNotes
8086cdeclRTL ©Caller
8086PascalLTR (Pascal)Callee
8086fastcall
(non-member)
MicrosoftAX,
DX,
BX
LTR (Pascal)CalleeReturn pointer in BX.
8086fastcall
(member function)
MicrosoftAX,
DX
LTR (Pascal)Calleethis pointer on stack low address. Return pointer in AX.
8086fastcallTurbo CAX,
DX,
BX
LTR (Pascal)Calleethis pointer on stack low address. Return pointer on stack high address.
8086WatcomAX,
DX,
BX,
CX
RTL ©CalleeReturn pointer in SI.
IA-32cdeclUnix-like (GCC)RTL ©CallerWhen 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-32cdeclMicrosoftRTL ©CallerWhen 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-32stdcallMicrosoftRTL ©CalleeAlso supported by GCC.
IA-32fastcallMicrosoftECX,
EDX
RTL ©CalleeReturn pointer on stack if not member function. Also supported by GCC.
IA-32registerDelphi and Free PascalEAX,
EDX,
ECX
LTR (Pascal)Callee
IA-32thiscallWindows
(Microsoft Visual C++)
ECXRTL ©CalleeDefault for member functions.
IA-32vectorcallWindows
(Microsoft Visual C++)
ECX,
EDX,
[XY]MM0–5
RTL ©CalleeExtended from fastcall. Also supported by ICC and Clang.
IA-32Watcom compilerEAX,
EDX,
EBX,
ECX
RTL ©CalleeReturn pointer in ESI.
x86-64Microsoft x64 calling convention[18]Windows(
Microsoft Visual C++,
GCC,
Intel C++ Compiler,
Delphi),
UEFI
RCX/XMM0,
RDX/XMM1,
R8/XMM2,
R9/XMM3
RTL ©CallerStack 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-64vectorcallWindows(
Microsoft Visual C++,
Clang,
ICC)
RCX/[XY]MM0,
RDX/[XY]MM1,
R8/[XY]MM2,
R9/[XY]MM3 + [XY]MM4–5
RTL ©CallerExtended from MS x64.
x86-64System 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 ©CallerStack 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

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值