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

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

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

156933 次浏览

也许你在找 x86 _ 64 ABI?

如果这不是你想要的,那么在你的首选搜索引擎中使用‘ x86 _ 64 abi’来寻找替代的引用。

这里的任何主题的进一步阅读: < strong > Linux 系统调用通用指南


我在 Linux 上使用 GNU 汇编程序(gas)验证了这些。

内核接口

X86-32又名 i386 Linux 系统调用约定:

在 x86-32中,Linux 系统调用的参数使用寄存器传递。用于 syscall _ number 的 %eax。% ebx、% ecx、% edx、% esi、% edi、% ebp 用于向系统调用传递6个参数。

返回值在 %eax中。所有其他寄存器(包括 EFLAGS)都保留在 int $0x80中。

我采取以下片段从 Linux 汇编教程,但我对此表示怀疑。如果有人能举个例子,那就太好了。

如果有超过六个理由, %ebx必须包含内存 参数列表的位置 是存储-但不要担心这一点 因为你不太可能 多于六的系统调用 争论。

关于一个例子和更多的阅读,请参考 http://www.int80h.org/bsdasm/#alternate-calling-convention。另一个使用 int 0x80: 你好,世界汇编语言与 Linux 系统调用?的 i386 Linux Hello World 示例

进行32位系统调用有一种更快的方法: 使用 sysenter。内核将一页内存映射到每个进程(vDSO) ,使用 sysenter舞蹈的用户空间部分,它必须与内核合作才能找到返回地址。Arg 到寄存器的映射与 int $0x80相同。您通常应该调用 vDSO,而不是直接使用 sysenter。(有关链接和调用 vDSO 的信息,以及有关 sysenter的更多信息,以及与系统调用有关的所有其他信息,请参见 < strong > Linux 系统调用通用指南 。)

X86-32[免费 | 开放 | 网络 | 蜻蜓] BSD UNIX 系统调用约定:

参数在堆栈上传递。将参数(先推送最后一个参数)推送到堆栈上。然后推送一个额外的32位虚拟数据(它实际上不是虚拟数据)。参考以下链接获得更多信息) ,然后给出系统调用指令 int $0x80

Http://www.int80h.org/bsdasm/#default-calling-convention


X86-64 Linux 系统调用约定:

(注: X86-64 Mac OS X 是相似但不同的 from Linux. TODO: check what * BSD does)

请参阅 系统应用二进制接口 AMD64架构处理器补充的“ A. 2 AMD64 Linux核心约定”一节。可以找到 i386和 x86-64 System V psABI 的最新版本 链接到 ABI 维护者的回购。(请参阅 标签 wiki 获取最新的 ABI 链接以及许多其他关于 x86 asm 的好东西。)

以下是本节的片段:

  1. 用户级应用程序用作整数寄存器来传递 序列% rdi,% rsi,% rdx,% rcx, 内核接口使用% rdi、% rsi、% rdx、% r10、% r8和% r9。
  2. 系统调用是通过 syscall指令完成的。这个 重击者% rcx 和% r11以及% rax 返回值,但是保留了其他寄存器。
  3. 系统调用的数量必须在寄存器% rax 中传递。
  4. 系统调用限制为六个参数,不传递任何参数 直接放在堆栈上。
  5. 从系统调用返回的 register% rax 包含 系统调用。范围在 -4095和 -1之间的值表示 一个错误,它是 -errno
  6. 只有类 INTEGER 或类 MEMORY 的值被传递给内核。

请记住,这是从特定于 Linux 的 ABI 附录中摘录的,即使对于 Linux 来说,它也是信息性的,而不是规范性的。(但事实上它是准确的。)

这个32位 int $0x80 ABI 可用于64位代码(但强烈不推荐)。它仍然将其输入截断为32位,因此它不适合指针,并且它的零是 r8-r11。

用户界面: 函数调用

X86-32函数调用约定:

在 x86-32中,参数在堆栈上传递。最后一个参数被首先推送到堆栈上,直到所有参数都完成,然后执行 call指令。这用于从汇编调用 Linux 上的 C 库(libc)函数。

现代版本的 i386 System V ABI (在 Linux 上使用)需要在 call之前对 %esp进行16字节的对齐,就像 x86-64 System V ABI 一直需要的那样。被调用方可以假定这一点,并使用 SSE 16字节加载/存储未对齐时出错的内容。但是从历史上看,Linux 只需要4字节的堆栈对齐,所以即使为8字节的 double或其他东西保留自然对齐的空间也需要额外的工作。

其他一些现代32位系统仍然不需要超过4字节的堆栈对齐。


X86-64 System V 用户空间函数调用约定:

X86-64 System V 在寄存器中传递参数,这比 i386 System V 的堆栈参数约定更有效。它避免了将参数存储到内存(缓存)然后在被调用方中再次加载它们的延迟和额外指令。这种方法工作得很好,因为有更多的寄存器可用,而且更适用于现代高性能 CPU,因为它的延迟和乱序执行很重要。(i386ABI 非常古老)。

在这种 新的机制中: 首先将参数划分为类。每个参数的类决定了它传递给被调用函数的方式。

完整信息请参阅 系统应用二进制接口 AMD64架构处理器补充的“3.2函数调用序列”,其中部分内容如下:

一旦参数被分类,寄存器就会被分配(在 从左到右的顺序)传递如下:

  1. 如果该类是 MEMORY,则在堆栈上传递参数。
  2. 类的下一个可用寄存器 使用序列% rdi、% rsi、% rdx、% rcx、% r8和% r9

因此,%rdi, %rsi, %rdx, %rcx, %r8 and %r9是用于从汇编向任何 libc 函数传递整数/指针(即 INTEGER 类)参数的寄存器 按顺序。% rdi 用于第一个 INTEGER 参数。% rsi 表示第2,% rdx 表示第3,以此类推。然后给出 call指令。执行 call时,堆栈(%rsp)必须是16B 对齐的。

如果有多于6个 INTEGER 参数,则在堆栈上传递第7个 INTEGER 参数和后面的参数。(呼叫器弹出,与 x86-32相同。)

前8个浮点参数在% xmm0-7中传递,稍后在堆栈中传递。没有调用保留的向量寄存器。(一个混合了 FP 和整数参数的函数可以有8个以上的寄存器参数。)

可变函数(就像 printf)总是需要 %al = FP 寄存器参数的个数。

对于何时将结构打包到寄存器(返回时为 rdx:rax)与何时打包到内存中,有一些规则。有关详细信息,请参阅 ABI,并检查编译器输出,以确保您的代码与编译器在如何传递/返回内容方面达成一致。


请注意,Windowsx64函数调用约定与 x86-64 System V 有多个显著差异,比如 必须的被调用者保留的阴影空间(而不是红色区域)和调用保留的 xmm6-xmm15。对于哪个参数在哪个寄存器中,规则是非常不同的。

调用约定定义了当其他程序调用或被其他程序调用时参数在寄存器中的传递方式。这些约定的最佳来源是为每个硬件定义的 ABI 标准。为了便于编译,用户空间和内核程序也使用相同的 ABI。Linux/Freebsd 对 x86-64采用相同的 ABI,对32位采用另一套 ABI。但是 x86-64的 ABI 不同于 Linux/FreeBSD。而且通常 ABI 不区分系统调用和普通的“函数调用”。 比如,下面是一个特殊的 x86 _ 64调用约定示例,它适用于 Linux 用户空间和内核: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/(注意参数的序列 a,b,c,d,e,f) :

A good rendering of calling conventions vs registers usage

性能是这些 ABI 的原因之一(例如,通过寄存器传递参数而不是保存到内存堆栈中)

ARM 有各种 ABI:

Http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.subset.swdev.abi/index.html

Https://developer.apple.com/library/ios/documentation/xcode/conceptual/iphoneosabireference/iphoneosabireference.pdf

ARM64公约:

Http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/ihi0055b_aapcs64.pdf

用于 PowerPC 上的 Linux:

Http://refspecs.freestandards.org/elf/elfspec_ppc.pdf

Http://www.0x04.net/doc/elf/psabi-ppc64.pdf

对于嵌入式系统,有 PPC EABI:

Http://www.freescale.com/files/32bit/doc/app_note/ppceabi.pdf

本文件很好地概述了所有不同的公约:

Http://www.agner.org/optimize/calling_conventions.pdf

Linux 内核5.0源代码注释

我知道 x86的细节在 arch/x86下面,而系统调用的东西在 arch/x86/entry下面。因此,在该目录中的一个快速 git grep rdi将我引向 Arch/x86/entry/entry _ 64. S:

/*
* 64-bit SYSCALL instruction entry. Up to 6 arguments in registers.
*
* This is the only entry point used for 64-bit system calls.  The
* hardware interface is reasonably well designed and the register to
* argument mapping Linux uses fits well with the registers that are
* available when SYSCALL is used.
*
* SYSCALL instructions can be found inlined in libc implementations as
* well as some other programs and libraries.  There are also a handful
* of SYSCALL instructions in the vDSO used, for example, as a
* clock_gettimeofday fallback.
*
* 64-bit SYSCALL saves rip to rcx, clears rflags.RF, then saves rflags to r11,
* then loads new ss, cs, and rip from previously programmed MSRs.
* rflags gets masked by a value from another MSR (so CLD and CLAC
* are not needed). SYSCALL does not save anything on the stack
* and does not change rsp.
*
* Registers on entry:
* rax  system call number
* rcx  return address
* r11  saved rflags (note: r11 is callee-clobbered register in C ABI)
* rdi  arg0
* rsi  arg1
* rdx  arg2
* r10  arg3 (needs to be moved to rcx to conform to C ABI)
* r8   arg4
* r9   arg5
* (note: r12-r15, rbp, rbx are callee-preserved in C ABI)
*
* Only called from user space.
*
* When user can change pt_regs->foo always force IRET. That is because
* it deals with uncanonical addresses better. SYSRET has trouble
* with them due to bugs in both AMD and Intel CPUs.
*/

以及 Arch/x86/entry/entry _ 32. S的32位:

/*
* 32-bit SYSENTER entry.
*
* 32-bit system calls through the vDSO's __kernel_vsyscall enter here
* if X86_FEATURE_SEP is available.  This is the preferred system call
* entry on 32-bit systems.
*
* The SYSENTER instruction, in principle, should *only* occur in the
* vDSO.  In practice, a small number of Android devices were shipped
* with a copy of Bionic that inlined a SYSENTER instruction.  This
* never happened in any of Google's Bionic versions -- it only happened
* in a narrow range of Intel-provided versions.
*
* SYSENTER loads SS, ESP, CS, and EIP from previously programmed MSRs.
* IF and VM in RFLAGS are cleared (IOW: interrupts are off).
* SYSENTER does not save anything on the stack,
* and does not save old EIP (!!!), ESP, or EFLAGS.
*
* To avoid losing track of EFLAGS.VM (and thus potentially corrupting
* user and/or vm86 state), we explicitly disable the SYSENTER
* instruction in vm86 mode by reprogramming the MSRs.
*
* Arguments:
* eax  system call number
* ebx  arg1
* ecx  arg2
* edx  arg3
* esi  arg4
* edi  arg5
* ebp  user stack
* 0(%ebp) arg6
*/

Glibc 2.29 Linux x86 _ 64系统调用实现

现在让我们通过查看主要的 libc 实现来作弊,看看它们在做什么。

还有什么比查看我现在正在使用的 glibc 来写这个答案更好的呢? : -)

Glibc 2.29在 sysdeps/unix/sysv/linux/x86_64/sysdep.h上定义了 x86 _ 64 syscalls,其中包含一些有趣的代码,例如:

/* The Linux/x86-64 kernel expects the system call parameters in
registers according to the following table:


syscall number  rax
arg 1       rdi
arg 2       rsi
arg 3       rdx
arg 4       r10
arg 5       r8
arg 6       r9


The Linux kernel uses and destroys internally these registers:
return address from
syscall     rcx
eflags from syscall r11


Normal function call, including calls to the system call stub
functions in the libc, get the first six parameters passed in
registers and the seventh parameter and later on the stack.  The
register use is as follows:


system call number in the DO_CALL macro
arg 1      rdi
arg 2      rsi
arg 3      rdx
arg 4      rcx
arg 5      r8
arg 6      r9


We have to take care that the stack is aligned to 16 bytes.  When
called the stack is not aligned since the return address has just
been pushed.




Syscalls of more than 6 arguments are not supported.  */

以及:

/* Registers clobbered by syscall.  */
# define REGISTERS_CLOBBERED_BY_SYSCALL "cc", "r11", "cx"


#undef internal_syscall6
#define internal_syscall6(number, err, arg1, arg2, arg3, arg4, arg5, arg6) \
({                                  \
unsigned long int resultvar;                    \
TYPEFY (arg6, __arg6) = ARGIFY (arg6);              \
TYPEFY (arg5, __arg5) = ARGIFY (arg5);              \
TYPEFY (arg4, __arg4) = ARGIFY (arg4);              \
TYPEFY (arg3, __arg3) = ARGIFY (arg3);              \
TYPEFY (arg2, __arg2) = ARGIFY (arg2);              \
TYPEFY (arg1, __arg1) = ARGIFY (arg1);              \
register TYPEFY (arg6, _a6) asm ("r9") = __arg6;            \
register TYPEFY (arg5, _a5) asm ("r8") = __arg5;            \
register TYPEFY (arg4, _a4) asm ("r10") = __arg4;           \
register TYPEFY (arg3, _a3) asm ("rdx") = __arg3;           \
register TYPEFY (arg2, _a2) asm ("rsi") = __arg2;           \
register TYPEFY (arg1, _a1) asm ("rdi") = __arg1;           \
asm volatile (                          \
"syscall\n\t"                           \
: "=a" (resultvar)                          \
: "0" (number), "r" (_a1), "r" (_a2), "r" (_a3), "r" (_a4),     \
"r" (_a5), "r" (_a6)                      \
: "memory", REGISTERS_CLOBBERED_BY_SYSCALL);            \
(long int) resultvar;                       \
})

我觉得这是不言而喻的。请注意,这似乎是如何设计的,以完全匹配常规 System V AMD64 ABI 函数的调用约定: https://en.wikipedia.org/wiki/X86_calling_conventions#List_of_x86_calling_conventions

提醒一下那些暴徒:

  • cc表示标志寄存器。但是 Peter Cordes 评论表示这在这里是不必要的。
  • memory意味着指针可以在程序集中传递并用于访问内存

有关从头开始显式的最小可运行示例,请参见以下答案: 如何在内联汇编中通过 syscall 或 sysenter 调用系统调用?

在汇编中手动执行一些系统调用

不是很科学,但很有趣:

  • X86 _ 64. S

    .text
    .global _start
    _start:
    asm_main_after_prologue:
    /* write */
    mov $1, %rax    /* syscall number */
    mov $1, %rdi    /* stdout */
    mov $msg, %rsi  /* buffer */
    mov $len, %rdx  /* len */
    syscall
    
    
    /* exit */
    mov $60, %rax   /* syscall number */
    mov $0, %rdi    /* exit status */
    syscall
    msg:
    .ascii "hello\n"
    len = . - msg
    

    GitHub 上游。

从 C 打系统调用

下面是一个带有寄存器约束的示例: 如何在内联汇编中通过 syscall 或 sysenter 调用系统调用?

Aarch64

我在这里展示了一个最小的可运行 userland 示例: https://reverseengineering.stackexchange.com/questions/16917/arm64-syscalls-table/18834#18834 TODO grep 内核代码,应该很简单。