如何在Mac(NASM)上从x86-64程序集将结构传递给C函数

时间:2019-03-23 00:00:45

标签: c assembly struct x86-64

来自here

nanosleep((const struct timespec[]){{0, 500000000L}}, NULL);

它传递一个结构。我不确定如何通过寄存器将结构传递给syscall或库函数。想知道是否可以显示一个将结构传递给此syscall的NASM程序集的世界实例。

此外,如果将此函数包装在C中,则它不再是syscall。我想知道如何编写程序集,以便在这种情况下可以更好地工作。因此,基本上,如何在汇编中构建结构并将其传递给Mac上x86-64中的C函数。有许多采用结构的C库函数,因此有兴趣了解如何将结构通用地传递给它们。

2 个答案:

答案 0 :(得分:4)

x86_64 V系统中的IIRC ABI这样的小结构只是在常规参数寄存器上“分解”了;但是情况并非如此-nanosleep对该结构采用了 pointer (通常,我什至不认为syscall调用约定允许按值传递结构)。 / p>

IOW,该代码几乎等同于:

struct timespec ts{0, 500000000L};
nanosleep(&ts, NULL);

因此,您必须为ts划分出16个字节的堆栈空间并将其填充(您甚至可以摆脱两个push),获得指向它的指针(您可以需要lea)并将结果作为第一个参数传递给nanosleep(因此,在rdi中,在0中使用rsi)。

在Linux上类似:

push 500000000  ; push last 64 bit of ts
push 0          ; push first 64 bit of ts
mov rdi,rsp     ; the stack pointer now points to ts; use it as first arg
xor esi,esi     ; the second arg is NULL
mov eax,35      ; syscall 35 -> nanosleep
syscall
add rsp,16      ; restore the stack

在macOS AFAIK上应该相同,唯一的区别应该是系统调用号。

答案 1 :(得分:3)

如果您使用C编译器对此进行了编译,并查看了asm输出,您会发现它只是将 pointer 传递给该结构。

C正在创建struct timespec[] 的匿名 array ,它是一个左值,因此在传递时“衰减”到指针是合法的到
 int nanosleep(const struct timespec *req, struct timespec *rem);

如果您查看系统调用的手册页,将会看到它同时使用了两个args作为指针。

实际上,没有POSIX系统调用按值接受struct args 。这种设计选择很有意义,因为并非所有体系结构中的所有调用约定都以相同的方式处理传递的结构。系统调用调用约定通常与功能调用调用约定不完全匹配,并且通常没有用于整数/指针类型的规则。

系统调用通常最多限制为6个args,对于大型args不会回退到堆栈内存。内核需要一种通用机制来从用户空间收集args并将它们从函数指针表中分发给内核函数,因此所有系统调用都需要具有与asm级别的syscall(uintptr_t a, uintptr_t b, ... uintptr_t f)兼容的签名。

如果OS引入了一个按值接受结构的系统调用,则它必须定义将ABI详细信息传递给它所支持的每个体系结构的细节。这可能会变得棘手,例如在32位架构上,像struct timespec这样的16字节结构将占用4个寄存器宽度的arg-pass插槽。 (假设时间仍然是64位,否则您将使用year-2038 rollover problem。)


正如Matteo所说,x86-64 System V将最多16个字节的结构压缩到最多2个用于调用函数的寄存器中。这些规则的文档in the ABI有据可查,但是通常最简单的方法是编写一个简单的测试函数,将其args存储到volatile long x或返回其中一个,然后在启用优化的情况下对其进行编译。

例如on Godbolt

#include <stdint.h>
struct padded {
    int16_t a;
    int64_t b;
};
int64_t ret_a(int dummy, padded s) { return s.a;  }
int64_t ret_b(int dummy, padded s) { return s.b; }

针对x86-64系统V编译为该asm,因此我们可以看到该结构在RDX:RSI中传递,未使用RSI的高6个字节(可能保留垃圾),就像内存中的对象表示形式为6填充字节,因此int64_t成员具有alignof(int64_t) = 8对齐。

ret_a(int, padded):
        movsx   rax, si
        ret
ret_b(int, padded):
        mov     rax, rdx
        ret

编写一个将args放入正确寄存器的调用程序应该很明显。