【问题标题】:Linux 64-abi, calling conventionLinux 64-abi,调用约定
【发布时间】:2018-02-05 13:56:24
【问题描述】:

我正在阅读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

【问题讨论】:

  • 系统调用使用与用户空间略有不同的约定。是的,你可以在你的手写函数中做任何你想做的事情。
  • @Jester 做任何我想做的事听起来有点乱。我想了解并遵守调用约定。
  • 请注意 32 位和 64 位 linux 的系统调用接口非常不同。对于 32 位,%ebx、%ecx、%edx;对于 64 位 %rdi、%rsi、%rdx。但系统调用号总是通过%eax(或%rax)
  • “想做什么就做什么听起来有点乱。” 专门问你能不能……我刚刚回答了。
  • @St.Antario 确实!里面说的都是对的。请注意,§3.3 为系统调用指定了一个单独的调用约定,其中包括您已经观察到的 rax 的使用。

标签: linux assembly x86-64 calling-convention


【解决方案1】:

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

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

但更重要的是,因为 RAX 中的 call number 是一个更好的 ABI,并且由于传统:这也是 i386 系统调用 ABI 所做的。以及 i386 System V 函数-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,所以它不必写任何带有返回信息的内存,并且不会'不要强迫用户空间把它放在内核可以读取它的地方(就像 32 位 sysenter 一样)。

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

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


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

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

选择 arg 寄存器以使调用者(或最重要的调用者)更轻松,否则您将使用具有固定寄存器选择的任何寄存器(如 div)。

注意哪些寄存器被破坏,哪些被注释保留。只需费心保存/恢复调用者实际需要保存/恢复的寄存器,并选择您使用哪些 tmp regs 来最小化推送/弹出。如果您的函数很短,请避免推送/弹出保存/重新加载作为调用者关键延迟路径的一部分的寄存器。

【讨论】:

  • 之前没有注意到内核约定的附录。
猜你喜欢
  • 2020-02-27
  • 2017-10-07
  • 2015-08-18
  • 1970-01-01
  • 2015-02-06
  • 1970-01-01
  • 1970-01-01
  • 2019-04-26
  • 1970-01-01
相关资源
最近更新 更多