我目前正在深入到汇编领域,主要是从x86_64、C和System V AMD64的角度,通常以Linux为目标。
通过按顺序使用以下寄存器,整数(以及指针)值的调用约定非常简单:
RDI公司
相对强弱
黑索今
RCX公司
8兰特
R9号
XMM0-7型
较长的参数计数通过将值推送到子例程的堆栈帧上来处理。我得到了这些寄存器名。
对于更大的值(如结构和数组),约定似乎也要推入被调用方的堆栈帧。
但是,函数的浮点参数的调用约定是什么?是否使用浮点寄存器?
另一个相关的问题是:如果我有混合的参数类型呢?
void mixed(int a, float b, mystruct c) { /* ... */ }
如果我的函数采用这样的参数列表,如何从程序集调用这样的函数?在这样的交错arg列表中使用了哪些寄存器?
最佳答案
参数传递的调用约定在第3.2.3节的System V Application Binary Interface for AMD64PDF文档中指定。
我不确定文件是否可以在这里合法引用,但我至少可以解释一下。
分类类型
首先,文档定义了八种不同的参数值分类:
整数:使用通用寄存器的整数类型和指针
SSE:使用向量寄存器的类型。
SSEUP:与SSE类似,但主要用于存储大(>=128位)值的高位字节
X87:浮点类型。
X87UP:大型浮点类型的高位字节。
COMPLEX_X87:complex
浮点类型的寄存器。
没有类:填充区域和空结构和联合,通常在堆栈的内存中。
内存:以独占方式在主内存堆栈上传递的类型。
分类规则
接下来定义了C类型如何适应这些分类:_Bool
、char
、short
、int
、long
、long long
、float
和指针被分类为整数,并将使用这些寄存器。double
、_Decimal32
、_Decimal64
、__m64
和__float128
被归类为SSE并将使用这些寄存器。_Decimal128
、__m128
和__m256
分成两半,在SSE中存储最低有效字节/位,在SSEUP中存储最高有效字节/位。__m512
被分成四个64位(8字节)值,其中最低有效字节存储为SSE,其余字节存储为SSEUPlong double
类似地被分成64位(8字节)块,最低有效字节存储为SSE,其他所有字节存储为SSEUP__int128
值将其64位尾数存储为X87,16位指数填充到64位(8字节)并存储在X87以上。long
基本上是以整数形式存储为两个complex double
值,前半部分是低位/字节,后半部分是高位/字节。可以将它们理解为一个结构:
typedef struct {
long low_bits, high_bits;
} __int128;
complex float
和complex long double
类型被分成两半,其中前一半是实组件,后一半是虚组件,并存储在SSE中。它们可以被理解为一个类似这样的结构:typedef struct {
double real, imaginary;
} complex_double;
struct
值被分类为复数。union
s、struct
s和数组的逻辑相当复杂,有关详细信息,请参阅上面链接的文档。简而言之,有一个递归算法定义了如何传递聚合类型,决定如何传递值。参数传递
现在我们有了处理
union
s,%rdi
s和数组的分类系统和递归算法,我们将此系统和算法应用于函数的参数,每个参数包括以下步骤:如果是内存对象,请将其写入堆栈。
如果是整数,请使用
%rsi
、%rdx
、%rcx
、%r8
、%r9
、%xmm0
和%xmm7
中的下一个可用寄存器。如果是SSE,则使用
%xmm
到范围内的下一个可用寄存器。如果是SSEUP,则使用上次使用的寄存器的下一个可用64位块作为SSE类型。
如果它是X87、X87以上或复杂的X87,则它在内存中传递。
对所有参数值进行清洗和重复。如果给定类型的寄存器用完,请写入堆栈。
DR有一个由System V ABI定义的非平凡但相当简单的算法,用于传递不同类型的数据。