返回 SystemV ABI 中的结构

St.*_*rio 3 c assembly x86-64 abi calling-convention

raxSystemV ABI:和中仅定义了 2 个返回寄存器rdx,但structs 的大小可以远大于 16 字节,并且可以具有 2 个以上的成员。所以我考虑了下面的例子:

struct test{
    unsigned long a;
    char *b;
    unsigned long c;
};

struct test get_struct(void){
    return (struct test){.a = 10, .b = "abc", .c = 20};
}

void get_struct2(struct test *tst){
    struct test tmp = {.a = 10, .b = "abc", .c = 20};
    *tst = tmp;
}
Run Code Online (Sandbox Code Playgroud)

这些函数的O3编译代码看起来几乎相同:gcc

Dump of assembler code for function get_struct:
   0x0000000000000820 <+0>:     lea    rdx,[rip+0x2f6]        # 0xb1d
   0x0000000000000827 <+7>:     mov    rax,rdi
   0x000000000000082a <+10>:    mov    QWORD PTR [rdi],0xa
   0x0000000000000831 <+17>:    mov    QWORD PTR [rdi+0x10],0x14
   0x0000000000000839 <+25>:    mov    QWORD PTR [rdi+0x8],rdx
   0x000000000000083d <+29>:    ret    
End of assembler dump.
Run Code Online (Sandbox Code Playgroud)

Dump of assembler code for function get_struct2:
   0x0000000000000840 <+0>:     lea    rax,[rip+0x2d6]        # 0xb1d
   0x0000000000000847 <+7>:     mov    QWORD PTR [rdi],0xa
   0x000000000000084e <+14>:    mov    QWORD PTR [rdi+0x10],0x14
   0x0000000000000856 <+22>:    mov    QWORD PTR [rdi+0x8],rax
   0x000000000000085a <+26>:    ret    
End of assembler dump.
Run Code Online (Sandbox Code Playgroud)

因此,get_struct函数签名被默默地修改为接受指向结构的指针并返回该指针。

问题:在返回结构的示例函数中,返回作为第一个参数传递的指针以及lea rdx,[rip+0x2f6]在这两种情况下都相似的第一个参数背后的原因是什么?这种用法是在 ABI 中标准化的还是依赖于编译器?

似乎lea rdx,[rip+0x2f6]代表 的加载char *,但它的程序集对我来说看起来有点令人困惑,因为它使用ripwith 处置(我猜这是显示该部分中元素的地址的问题rodata。)

如果结构体包含 2 个可以放入寄存器的成员,我们可以看到rax和的预期用法rdx

Mar*_*oom 7

返回传入的指针rdi(隐藏指针)对于调用者来说是一种方便。
\n被调用者无法返回寄存器中的整个结构,只能返回内存中的结构。
\n然而,被调用者无法为该结构分配缓冲区,因为这不仅效率低下,而且从所有权的角度来看也是有问题的(调用者如何释放它不知道如何分配的缓冲区?),因此它可以只返回调用者给出的指针。\n如果兼容(例如)并且编译器已经知道如何处理返回结构体指针的函数(即,无需任何特殊操作),这对于将值传递
给其他函数也很有用。f(g())

\n\n

隐藏指针的使用以及在 中返回它rax,记录在 ABI 中:

\n\n
\n

返回值

\n\n

值的返回是根据以下算法完成的:

\n\n
    \n
  1. 使用分类算法对返回类型进行分类。
  2. \n
  3. 如果该类型具有 MEMORY 类,则调用方为返回值提供空间,并在 %rdi 中传递此存储的地址,就好像它是函数的第一个参数一样
    \n 实际上,该地址成为 \xe2\x80\x9chidden\xe2\x80\x9d 第一个参数。此存储不得与被调用方通过除此参数之外的其他名称可见的任何数据重叠。
    \n 返回时%rax 将包含调用者在 %rdi 中传入的地址
  4. \n
\n
\n\n

lea rax,[rip+0x2d6]只是指向 的指针,这"abc"就是 PIE(不要与 PIC 混淆)访问其数据(只读或非只读)所必须执行的操作。

\n\n

最后:

\n\n
\n

如果聚合的大小超过两个八字节,并且前八个字节是\xe2\x80\x99t SSE 或任何其他八个字节是\xe2\x80\x99t SSEUP,则整个参数\n 将在内存中传递

\n
\n\n

IMO 的措辞并非 100% 正确,更好的版本是:“整个论证具有 MEMORY 类”。\n但效果是一样的:小于 16B 的结构可以在寄存器中传递返回。

\n\n

这里在实践中。

\n