为什么汇编x86_64系统调用参数不像i386那样按字母顺序排列

时间:2017-12-06 14:28:10

标签: assembly x86 x86-64 cpu-registers calling-convention

有一个问题困扰着我。

所以...为什么 x86_32 参数会在我认为按字母顺序eax的寄存器中传递, ecxedxesi)和排名订单(esiediebp

+---------+------+------+------+------+------+------+
| syscall | arg0 | arg1 | arg2 | arg3 | arg4 | arg5 |
+---------+------+------+------+------+------+------+
|   %eax  | %ebx | %ecx | %edx | %esi | %edi | %ebp |
+---------+------+------+------+------+------+------+

section .text
    global _start
_start:
    mov eax, 1     ; x86_64 opcode for sys_exit
    mov ebx, 0     ; first argument
    int 0x80

x86_64 中,系统调用的参数在寄存器中传递,这些寄存器看起来有点随机排列:

+---------+------+------+------+------+------+------+
| syscall | arg0 | arg1 | arg2 | arg3 | arg4 | arg5 |
+---------+------+------+------+------+------+------+
|   %rax  | %rdi | %rsi | %rdx | %r10 | %r8  | %r9  |
+---------+------+------+------+------+------+------+

section .text
    global _start
_start:
    mov eax, 1     ; x86_64 opcode for sys_exit
    mov edi, 0     ; first argument
    syscall

他们出于特定原因这样做了吗?我在这里没看到什么吗?

1 个答案:

答案 0 :(得分:3)

x86-64 System V ABI旨在最大限度地减少SPECint中的指令数量(在某种程度上代码大小),这是由第一个AMD64 CPU出售之前的当前gcc版本编译的。请参阅this answer for some history and list-archive links

  

在我认为所有寄存器相同之前5分钟,但由于惯例,它们的使用方式不同。现在一切都改变了我

x86-64不是完全正交的。某些指令隐式使用特定寄存器。例如push隐式使用rsp作为堆栈指针,mul rdi执行rdx:rax = rax*rdishl edx, cl仅适用于cl中的班次计数(直到BMI2 shlx)。 rep-string指令隐式使用RDI,RSI和RCX。 (rep movs / rep stos在某些情况下实际上值得使用,例如中到大的memcpy / memset。)

事实证明,选择arg传递寄存器,以便将其args传递给memcpy的函数可以将其内联为rep movs,这在Jan Hubicka使用的度量中很有用,因此rdi和{{ 1}}被选为前两个args。但是,在第4个arg之前使rsi未使用是更好的,因为变量计数移位需要rcx。 (并且大多数功能都不会使用他们的第3个arg作为移位计数。)

x86-64 System V ABI使用与系统调用几乎相同的函数调用约定。这不是巧合:它意味着像cl这样的libc包装函数的实现可以是:

mmap

这是一个很小的优势,但实际上没有理由这样做。它还会在调度到内核中系统调用的C实现之前,在内核中设置一些指令来设置arg传递寄存器。 (请参阅this answer以了解系统调用处理的某些内核方面。主要是关于mmap: mov r10, rcx ; syscall destroys rcx and r11; 4th arg passed in r10 for syscalls mov eax, __NR_mmap syscall cmp rax, -4096 ja .set_errno_and_stuff ret 处理程序,但我想我提到了64位int 0x80处理程序并将其分派给函数表直接来自asm。)

有关函数调用和系统调用约定,请参阅What are the calling conventions for UNIX & Linux system calls on i386 and x86-64

i386系统调用约定是笨重且不方便的:syscall是调用保留的,因此几乎每个系统调用包装器都需要保存/恢复ebx,除了没有像{{{{}}之类的args的调用1}}。 (为此您甚至不需要进入内核,只需调用vDSO:有关vDSO和其他大量内容的更多信息,请参阅The Definitive Guide to Linux System Calls (on x86)。)

但是i386函数调用约定会传递堆栈中的所有args,因此无论如何glibc包装函数仍然需要ebx每个arg。

另请注意,x86寄存器的“自然”顺序是EAX,ECX,EDX,EBX,根据机器代码中的数字代码,以及getpid / mov使用的顺序。请参阅Why are first four x86 GPRs named in such unintuitive order?