有一个问题困扰着我。
所以...为什么 x86_32 参数会在我认为按字母顺序(eax
的寄存器中传递, ecx
,edx
,esi
)和排名订单(esi
,edi
,ebp
)
+---------+------+------+------+------+------+------+
| 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
他们出于特定原因这样做了吗?我在这里没看到什么吗?
答案 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*rdi
。 shl 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?。