问题描述
有一个问题困扰着我.
所以...为什么在 x86_32 中,将参数传递给我认为是字母(eax
,,edx
,esi
)和排名(esi
,edi
,ebp
)
So ... Why in x86_32 the parameters are passed in registers that I feel are in alphabetically (eax
, ecx
, edx
, esi
) and ranked order (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的参数在看起来有点随机排列的寄存器中传递:
While in x86_64 syscall's parameters are passed in registers that look a little bit randomly arranged:
+---------+------+------+------+------+------+------+
| 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
他们这样做是出于特定原因吗?我在这里没看到东西吗?
Did they do that for a specific reason? Am I not seeing something here?
推荐答案
x86-64 System V ABI旨在最大程度地减少由gcc版本编译的SPECint中的指令计数(并在某种程度上是代码大小).在第一批AMD64 CPU出售之前是最新的.参见有关某些历史记录和列表归档链接的答案.
The x86-64 System V ABI was designed to minimize instruction-count (and to some degree code-size) in SPECint as compiled by the version of gcc that was current before the first AMD64 CPUs were sold. See this answer for some history and list-archive links.
x86-64不完全正交.一些指令隐式使用特定的寄存器.例如push
隐式使用rsp
作为堆栈指针,shl edx, cl
仅可用于cl
中的移位计数(直到BMI2 shlx
).
x86-64 is not fully orthogonal. Some instructions implicitly use specific registers. e.g. push
implicitly uses rsp
as the stack pointer, shl edx, cl
is only usable with a shift count in cl
(until BMI2 shlx
).
更不常用:加宽mul rdi
可以rdx:rax = rax*rdi
. rep-string指令隐式使用RDI,RSI和RCX,尽管它们通常不值得使用.
More rarely used: widening mul rdi
does rdx:rax = rax*rdi
. The rep-string instructions implicitly use RDI, RSI, and RCX, although they're often not worth using.
事实证明,选择arg传递寄存器,以使将其args传递给memcpy的函数可以内联它,因为rep movs
在使用Jan Hubicka的度量标准中很有用,因此选择了rdi
和rsi
作为前两个参数.但是最好不要使用rcx
直到第4个arg,因为cl
对于可变计数移位是必需的. (而且大多数函数不会碰巧将其第3个arg用作移位计数.)(可能是较老的GCC版本更积极地将memcpy
或memset
内联为rep movs
;对于小型数组,这与SIMD相比通常不值得这些天.)
It turns out that choosing the arg-passing registers so that functions that passed their args to memcpy could inline it as rep movs
was useful in the metric Jan Hubicka was using, thus rdi
and rsi
were chosen as the first two args. But that leaving rcx
unused until the 4th arg was better, because cl
is needed for variable-count shift. (And most functions don't happen to use their 3rd arg as a shift count.) (Probably older GCC versions inlined memcpy
or memset
as rep movs
more aggressively; it's usually not worth it vs. SIMD for small arrays these days.)
x86-64 System V ABI对函数的调用约定与对系统调用的调用约定几乎相同.这不是巧合:这意味着像mmap
这样的libc包装函数的实现可以是:
The x86-64 System V ABI uses almost the same calling convention for functions as it does for system calls. This is not a coincidence: it means the implementation for a libc wrapper function like mmap
can be:
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
这是一个很小的优势,但实际上没有理由不做到这一点.在分派给内核中系统调用的C实现之前,它还将一些指令保存在内核中,以设置arg-passing寄存器. (请参阅,用于系统调用处理的某些内核方面,主要是关于int 0x80
处理程序的问题,但我认为我提到了64位的syscall
处理程序,并将其分派给直接来自asm的函数表.)
This is a tiny advantage, but there's really no reason not to do this. It also saves a few instructions inside the kernel setting up the arg-passing registers before dispatching to the C implementation of the system call in the kernel. (See this answer for a look at some kernel side of system call handling. Mostly about the int 0x80
handler, but I think I mentioned the 64-bit syscall
handler and that it dispatches to a table of functions directly from asm.)
syscall
指令本身(以节省用户空间的RIP和RFLAGS,而无需使用微代码来设置内核堆栈),因此除非用户空间的约定避免了RCX,否则约定不能完全相同.和R11.但是RCX是一个方便的寄存器,其下半部分可以不使用REX前缀而使用,因此,与将其保留为像R11这样的被呼叫占据上风的纯暂存器相比,它可能会更糟.同样,用户空间约定将R10用作具有一流嵌套函数(非C/C ++)的语言的静态链"指针.
The syscall
instruction itself destroys RCX and R11 (to save user-space RIP and RFLAGS without needing microcode to set up the kernel stack) so the conventions can't be identical unless the user-space convention avoided RCX and R11. But RCX is a handy register whose low half can be used without a REX prefix so that probably would have been worse than leaving it as a call-clobbered pure scratch like R11. Also, the user-space convention uses R10 as a "static chain" pointer for languages with first-class nested functions (not C/C++).
对于整个代码大小,具有前四个参数可以避免使用REX前缀可能是最好的,并且使用RBX或RBP而不是RCX会很奇怪.拥有几个不需要REX前缀(EBX/EBP)的保留呼叫的寄存器是很好的.
Having the first 4 args able to avoid a REX prefix is probably best for overall code-size, and using RBX or RBP instead of RCX would be weird. Having a couple call-preserved registers that don't need a REX prefix (EBX/EBP) is good.
请参阅以实现函数调用和系统调用约定.
See What are the calling conventions for UNIX & Linux system calls on i386 and x86-64 for the function-call and system-call conventions.
i386系统调用约定很笨拙且不方便:ebx
是调用保留的,因此几乎每个syscall包装器都需要保存/恢复ebx
,无参的调用除外就像getpid
. (为此,您甚至不需要输入内核,只需调用vDSO:请参阅"> Linux系统调用权威指南(在x86上),以获取有关vDSO和大量其他内容的更多信息.)
The i386 system call convention is the clunky and inconvenient one: ebx
is call-preserved, so almost every syscall wrapper needs to save/restore ebx
, except for calls with no args like getpid
. (And for that you don't even need to enter the kernel, just call into the vDSO: see The Definitive Guide to Linux System Calls (on x86) for more about vDSO and tons of other stuff.)
但是i386函数调用约定将所有arg传递给堆栈,因此glibc包装函数仍然仍然需要mov
每个arg.
But the i386 function-calling convention passes all args on the stack, so glibc wrapper functions still need to mov
every arg anyway.
还要注意,x86寄存器的自然"顺序是EAX,ECX,EDX,EBX,这取决于它们在机器代码中的数字代码,以及pusha
/popa
使用的顺序.请参阅为什么要命名前四个x86 GPR .
Also note that the "natural" order of x86 registers is EAX, ECX, EDX, EBX, according to their numeric codes in machine code, and also the order that pusha
/ popa
use. See Why are first four x86 GPRs named in such unintuitive order?.
这篇关于为什么Assembly x86_64 syscall参数不按字母顺序排列(如i386)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!