Linux 64-abi,调用约定

Linux 64-abi, calling convention

我正在阅读 intel manual 关于调用约定以及哪个寄存器有哪个用途的内容。这是 Figure 3.4: Register Usage 中指定的内容:

%rax       temporary register; with variable arguments
           passes information about the number of vector
           registers used; 1st return register

但是在linux api中我们使用rax来传递函数号。是否与intel手册中规定的一致?实际上我预计(根据手册)我们会将函数编号传递给 rdi (它用于第一个参数)。等等...

我可以使用 rax 来传递手写函数中的第一个函数参数吗?例如

mov rax, [array_lenght_ptr]
mov rdi, array_start_ptr
callq _array_sum

那句话是在谈论 函数 调用约定,它由 x86-64 System V ABI 文档标准化。

您正在考虑 Linux 的系统调用调用约定,它在 ABI 文档的附录中进行了描述,但那部分不是规范的。无论如何,系统调用 ABI 将调用号放在 rax 中,因为它不是系统调用 的参数 。或者,您可以将其视为第 0 个 arg,就像可变参数函数调用在 al 中传递 FP 寄存器参数的数量一样。 (有趣的事实:这使得调用者可以根据需要传递堆栈上的第一个 FP arg。)

但更重要的是,因为 RAX 中的调用编号会产生更好的 ABI,并且由于传统:这也是 i386 系统调用 ABI 所做的。 和 i386 系统V function-call ABI 完全不同,只使用堆栈参数。

这意味着系统调用包装函数可以只设置 eax 和 运行 syscall 而不需要做类似

的事情
libc_write_wrapper_for_your_imagined_syscall_convention:
   ; copy all args to the next slot over
    mov r10, rdx   ; size_t count
    mov rdx, rsi   ; void *buf
    mov esi, edi   ; int fd
    mov edi, 1     ; SYS_write
    syscall
    cmp  rax, -4095
    jae  set_errno
    ret

而不是

actual_libc_write_wrapper:   ; glibc's actual code I think also checks for pthread cancellation points or something...
    mov eax, 1     ; SYS_write
    syscall
    cmp  rax, -4095
    jae  set_errno
    ret

注意使用 r10 而不是 rcx 因为 syscall 破坏了 rcxr11 与保存的 RIP 和 RFLAGS,所以它不会'不必用 return 信息写入任何内存,也不会强制用户 space 将其放在内核可以读取的地方(就像 32 位 sysenter 那样)。

所以系统调用约定不能与函数调用约定相同。 (或者函数调用约定将不得不选择不同的寄存器。)

对于具有 4 个或更多参数的系统调用(或适用于任何系统调用的通用包装器),您确实需要 mov r10, rcx,仅此而已。 (与包装器必须从堆栈加载参数的 32 位约定不同,并且 save/restore ebx 因为内核选择不当的 ABI 将其用于第一个参数。)


Can I use rax to pass the first function argument in my hand-written functions?

是的,对不需要从 C 调用的私有辅助函数做任何您想做的事。

选择 arg 寄存器以使呼叫者(或最重要的呼叫者)更容易,或者您将使用具有固定寄存器选择的任何寄存器(如 div)。

注意哪些寄存器被破坏,哪些通过注释保留。只需费心 save/restore 注册您的调用者实际需要保存/恢复,并选择您使用哪个 tmp regs 来最小化 push/pop。如果您的函数很短,请避免 push/pop save/reload 寄存器,它们是调用者中关键延迟路径的一部分。