小编典典

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

all

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

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


阅读 67

收藏
2022-08-07

共1个答案

小编典典

进一步阅读此处的任何主题: Linux
系统调用权威指南


我在 Linux 上使用 GNU Assembler (gas) 验证了这些。

内核接口

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

在 x86-32 中,Linux 系统调用的参数是使用寄存器传递的。%eax对于 syscall_number。%ebx, %ecx, %edx,
%esi, %edi, %ebp 用于将 6 个参数传递给系统调用。

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

我从Linux Assembly
Tutorial
中获取了以下片段,但我对此表示怀疑。如果有人能举个例子,那就太好了。

如果有超过六个参数,则 %ebx必须包含存储参数列表的内存位置 - 但不要担心这一点,因为您不太可能使用超过六个参数的系统调用。

有关示例和更多阅读内容,请参阅http://www.int80h.org/bsdasm/#alternate-calling-
convention。另一个使用
i386 Linux 的 Hello World 示例int 0x80Hello, world in assembly language with
Linux system calls?

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

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 与 Linux相似但不同。TODO:检查 *BSD 的作用)

请参阅System V Application Binary Interface AMD64 Architecture Processor
Supplement
的“A.2
AMD64 Linux Kernel Conventions”部分。i386 和 x86-64 System V psABI 的最新版本可以
ABI 维护者的 repo 中的此页面链接
找到。标签 wiki 以获取最新的 ABI 链接和许多其他关于 x86 asm
的好东西。)

这是本节的片段:

  1. 用户级应用程序用作整数寄存器,用于传递序列 %rdi、%rsi、%rdx、%rcx、%r8 和 %r9。 内核接口使用
    %rdi、%rsi、%rdx、%r10、%r8 和 %r9。
  2. 系统调用是通过 syscall 指令完成的。这会破坏 %rcx 和
    %r11以及 %rax 返回值,但会保留其他寄存器。
  3. 系统调用的编号必须在寄存器 %rax 中传递。
  4. 系统调用仅限于六个参数,没有参数直接在堆栈上传递。
  5. 从系统调用返回,寄存器 %rax 包含系统调用的结果。介于 -4095 和 -1 之间的值表示错误,即-errno.
  6. 只有 INTEGER 类或 MEMORY 类的值被传递给内核。

请记住,这是来自 ABI 的特定于 Linux 的附录,即使对于 Linux,它也是信息性而非规范性的。(但实际上它是准确的。)

此 32 位int $0x80ABI 用于 64 位代码(但强烈不推荐)。 如果在 64 位代码中使用 32 位 int 0x80 Linux
ABI 会发生什么?(它仍然将其输入截断为 32 位,因此它不适合指针,并将
r8-r11 归零。

用户界面:函数调用

x86-32 函数调用约定:

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

%esp现代版本的 i386 System V ABI(在 Linux 上使用)需要a 之前的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
函数调用序列”,其中部分内容如下:

一旦参数被分类,寄存器被分配(按从左到右的顺序)用于传递,如下所示:

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

用于将整数/指针(即 INTEGER 类)参数传递给程序集中的任何 libc 函数 %rdi, %rsi, %rdx, %rcx, %r8 and %r9寄存器也是如此。%rdi 用于第一个 INTEGER 参数。%rsi 代表第二个,%rdx 代表第三个,依此类推。然后应该给出指示。堆栈 ( )
在执行时必须是 16B 对齐的。 __call``%rsp``call

如果 INTEGER 参数超过 6 个,则将第 7 个及以后的 INTEGER 参数传递到堆栈上。(调用者弹出,与 x86-32 相同。)

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

可变参数函数likeprintf总是需要%al= FP 寄存器参数的数量。

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


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

2022-08-07