linux - i386 和 x86-64 上的 UNIX 和 Linux 系统调用(和用户空间函数)的调用约定是什么

标签 linux assembly x86-64 calling-convention abi

以下链接解释了 UNIX(BSD 风格)和 Linux 的 x86-32 系统调用约定:

  • http://www.int80h.org/bsdasm/#system-calls
  • http://www.freebsd.org/doc/en/books/developers-handbook/x86-system-calls.html

  • 但是 UNIX 和 Linux 上的 x86-64 系统调用约定是什么?

    最佳答案

    进一步阅读此处的任何主题:The Definitive Guide to Linux System Calls

    我在 Linux 上使用 GNU Assembler (gas) 验证了这些。
    内核接口(interface)
    x86-32 又名 i386 Linux 系统调用约定:
    在 x86-32 中,Linux 系统调用的参数是使用寄存器传递的。 %eax对于 syscall_number。 %ebx, %ecx, %edx, %esi, %edi, %ebp 用于将 6 个参数传递给系统调用。
    返回值在 %eax .所有其他寄存器(包括 EFLAGS)都保留在 int $0x80 中。 .
    我从 Linux Assembly Tutorial 中获取了以下片段但我对此表示怀疑。如果有人能举个例子,那就太好了。

    If there are more than six arguments, %ebx must contain the memory location where the list of arguments is stored - but don't worry about this because it's unlikely that you'll use a syscall with more than six arguments.


    有关示例和更多阅读内容,请参阅 http://www.int80h.org/bsdasm/#alternate-calling-convention .另一个用于 i386 Linux 的 Hello World 示例,使用 int 0x80 :Hello, world in assembly language with Linux system calls?
    有一种更快的方法来进行 32 位系统调用:使用 sysenter .内核将一页内存映射到每个进程(vDSO),用户空间端为 sysenter舞蹈,它必须与内核合作才能找到返回地址。注册映射的参数与 int $0x80 相同.您通常应该调用 vDSO 而不是使用 sysenter直接地。 (有关链接和调用 vDSO 的信息,以及有关 sysenter 的更多信息,以及与系统调用有关的所有其他信息,请参阅 The Definitive Guide to Linux System Calls。)
    x86-32 [Free|Open|Net|DragonFly]BSD UNIX 系统调用约定:
    参数在堆栈上传递。将参数(最先推送的最后一个参数)压入堆栈。然后再推送一个额外的 32 位虚拟数据(它实际上不是虚拟数据。请参阅以下链接了解更多信息),然后给出系统调用指令 int $0x80 http://www.int80h.org/bsdasm/#default-calling-convention

    x86-64 Linux 系统调用约定:
    (注意:x86-64 Mac OS X is similar but different 来自 Linux。TODO:检查 *BSD 做了什么)
    请参阅 System V Application Binary Interface AMD64 Architecture Processor Supplement 的“A.2 AMD64 Linux 内核约定”部分.可以找到 i386 和 x86-64 System V psABI 的最新版本 linked from this page in the ABI maintainer's repo . (另请参阅 标签 wiki 以获得最新的 ABI 链接和许多其他关于 x86 asm 的好东西。)
    这是本节的片段:
    1. User-level applications use as integer registers for passing the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9. The kernel interface uses %rdi, %rsi, %rdx, %r10, %r8 and %r9.
    2. A system-call is done via the syscall instruction. This clobbers %rcx and %r11 as well as the %rax return value, but other registers are preserved.
    3. The number of the syscall has to be passed in register %rax.
    4. System-calls are limited to six arguments, no argument is passed directly on the stack.
    5. Returning from the syscall, register %rax contains the result of the system-call. A value in the range between -4095 and -1 indicates an error, it is -errno.
    6. Only values of class INTEGER or class MEMORY are passed to the kernel.

    请记住,这是来自 ABI 的特定于 Linux 的附录,即使对于 Linux,它也是信息性的而非规范性的。 (但实际上它是准确的。)
    这个 32 位 int $0x80 ABI 可用于 64 位代码(但强烈不推荐)。 What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?它仍然将其输入截断为 32 位,因此它不适合指针,并将 r8-r11 置零。
    用户界面:函数调用
    x86-32 函数调用约定:
    在 x86-32 中,参数在堆栈上传递。最后一个参数首先被压入堆栈,直到所有参数都完成,然后 call指令被执行。这用于从汇编调用 Linux 上的 C 库 (libc) 函数。
    i386 System V ABI 的现代版本(在 Linux 上使用)需要 %esp 的 16 字节对齐。之前 call ,就像一直需要的 x86-64 System V ABI。允许被调用者假设并使用在未对齐时出错的 SSE 16 字节加载/存储。但从历史上看,Linux 只需要 4 字节堆栈对齐,因此即使为 8 字节 double 保留自然对齐的空间也需要额外的工作。或者其他的东西。
    其他一些现代 32 位系统仍然不需要超过 4 字节的堆栈对齐。

    x86-64 System V 用户空间函数调用约定:
    x86-64 System V 在寄存器中传递 args,这比 i386 System V 的堆栈 args 约定更有效。它避免了将 args 存储到内存(缓存)然后在被调用者中再次加载它们的延迟和额外指令。这很有效,因为有更多可用的寄存器,并且更适合延迟和乱序执行很重要的现代高性能 CPU。 (i386 ABI 很旧了)。
    在这种新机制中:首先将参数分为几类。每个参数的类决定了它传递给被调用函数的方式。
    有关完整信息,请参阅:System V Application Binary Interface AMD64 Architecture Processor Supplement 的“3.2 函数调用序列”部分内容如下:

    Once arguments are classified, the registers get assigned (in left-to-right order) for passing as follows:

    1. If the class is MEMORY, pass the argument on the stack.
    2. If the class is INTEGER, the next available register of the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9 is used

    所以%rdi, %rsi, %rdx, %rcx, %r8 and %r9是用于将整数/指针(即 INTEGER 类)参数从汇编传递给任何 libc 函数的寄存器。 %rdi 用于第一个 INTEGER 参数。 %rsi 表示第二个,%rdx 表示第三个,依此类推。然后call应给予指示。当 %rsp 时,堆栈 ( call ) 必须是 16B 对齐的执行。
    如果有超过 6 个 INTEGER 参数,则第 7 个 INTEGER 参数及之后的参数将在堆栈上传递。 (来电弹出,与 x86-32 相同。)
    前 8 个浮点参数在 %xmm0-7 中传递,稍后在堆栈中。没有调用保留的向量寄存器。 (混合了 FP 和整数参数的函数可以有 8 个以上的寄存器参数。)
    可变参数函数 ( like printf ) 总是需要 %al = FP 寄存器 args 的数量。
    何时将结构打包到寄存器(返回时 rdx:rax)与内存中是有规则的。有关详细信息,请参阅 ABI,并检查编译器输出以确保您的代码与编译器就应如何传递/返回某些内容达成一致。

    请注意 the Windows x64 function calling convention与 x86-64 System V 有多个显着差异,例如调用者必须保留的阴影空间(而不是红色区域)和调用保留的 xmm6-xmm15。并且对于哪个 arg 进入哪个寄存器非常不同的规则。

    关于linux - i386 和 x86-64 上的 UNIX 和 Linux 系统调用(和用户空间函数)的调用约定是什么,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2535989/

    相关文章:

    c - Linux 内核中的栈内存

    assembly - 使用内联汇编启动 shell

    assembly - 在ARM64汇编代码中,何时将31 XZR与SP相对?

    assembly - "conditional call"在 amd64 上的性能

    x86 - x87 相对于 SSE 的优势

    java - Delphi xe5 exec root命令转换

    c - Posix Pthread 互斥

    assembly - 处理(可能)从JITed cod提前调用的编译函数的调用

    c - 为条形码读取器读取数据并将其写入缓冲区的最佳方法是什么?

    assembly - 如何在 MacOSX 上使用 nasm 进行编译