本文介绍了为什么Assembly x86_64 syscall参数不按字母顺序排列(如i386)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有一个问题困扰着我.

所以...为什么在 x86_32 中,将参数传递给我认为是字母(eax,,edxesi)和排名(esiediebp)

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的度量标准中很有用,因此选择了rdirsi作为前两个参数.但是最好不要使用rcx直到第4个arg,因为cl对于可变计数移位是必需的. (而且大多数函数不会碰巧将其第3个arg用作移位计数.)(可能是较老的GCC版本更积极地将memcpymemset内联为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)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-19 21:59