以下链接解释了 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 系统调用约定是什么?
进一步阅读此处的任何主题: Linux 系统调用权威指南
我在 Linux 上使用 GNU Assembler (gas) 验证了这些。
x86-32 又名 i386 Linux 系统调用约定:
在 x86-32 中,Linux 系统调用的参数是使用寄存器传递的。%eax对于 syscall_number。%ebx, %ecx, %edx, %esi, %edi, %ebp 用于将 6 个参数传递给系统调用。
%eax
返回值在%eax. 所有其他寄存器(包括 EFLAGS)都保留在int $0x80.
int $0x80
我从Linux Assembly Tutorial中获取了以下片段,但我对此表示怀疑。如果有人能举个例子,那就太好了。
如果有超过六个参数,则 %ebx必须包含存储参数列表的内存位置 - 但不要担心这一点,因为您不太可能使用超过六个参数的系统调用。
%ebx
有关示例和更多阅读内容,请参阅http://www.int80h.org/bsdasm/#alternate-calling- convention。另一个使用 i386 Linux 的 Hello World 示例int 0x80:Hello, world in assembly language with Linux system calls?
int 0x80
有一种更快的方法来进行 32 位系统调用:使用sysenter. 内核将一页内存映射到每个进程(vDSO)中,用户空间方面的sysenter舞蹈必须与内核合作才能找到返回地址。用于注册映射的 Arg 与 for 相同int $0x80。您通常应该调用 vDSO 而不是sysenter直接使用。(有关链接和调用 vDSO 的信息,请参阅 Linux 系统调用权威指南sysenter,以及有关 的更多信息,以及与系统调用有关的所有其他内容。)
sysenter
x86-32 [Free|Open|Net|DragonFly]BSD UNIX 系统调用约定:
参数在堆栈上传递。将参数(最先推送的最后一个参数)压入堆栈。然后再推送一个额外的 32 位虚拟数据(它实际上不是虚拟数据。有关更多信息,请参阅以下链接),然后给出系统调用指令int $0x80
http://www.int80h.org/bsdasm/#default-calling- convention
(注意: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 的好东西。)
这是本节的片段:
用户级应用程序用作整数寄存器,用于传递序列 %rdi、%rsi、%rdx、%rcx、%r8 和 %r9。 内核接口使用 %rdi、%rsi、%rdx、%r10、%r8 和 %r9。 系统调用是通过 syscall 指令完成的。这会破坏 %rcx 和 %r11以及 %rax 返回值,但会保留其他寄存器。 系统调用的编号必须在寄存器 %rax 中传递。 系统调用仅限于六个参数,没有参数直接在堆栈上传递。 从系统调用返回,寄存器 %rax 包含系统调用的结果。介于 -4095 和 -1 之间的值表示错误,即-errno. 只有 INTEGER 类或 MEMORY 类的值被传递给内核。
syscall
-errno
请记住,这是来自 ABI 的特定于 Linux 的附录,即使对于 Linux,它也是信息性而非规范性的。(但实际上它是准确的。)
此 32 位int $0x80ABI 可 用于 64 位代码(但强烈不推荐)。 如果在 64 位代码中使用 32 位 int 0x80 Linux ABI 会发生什么?(它仍然将其输入截断为 32 位,因此它不适合指针,并将 r8-r11 归零。
x86-32 函数调用约定:
在 x86-32 中,参数在堆栈上传递。最后一个参数首先被压入堆栈,直到所有参数都完成,然后call执行指令。这用于从程序集调用 Linux 上的 C 库 (libc) 函数。
call
%esp现代版本的 i386 System V ABI(在 Linux 上使用)需要a 之前的16 字节对齐call,就像 x86-64 System V ABI 一直需要的一样。被调用者可以假设并使用 SSE 16 字节加载/存储未对齐的错误。但是从历史上看,Linux 只需要 4 字节的堆栈对齐,因此即使为 8 字节double或其他东西保留自然对齐的空间也需要额外的工作。
%esp
double
其他一些现代 32 位系统仍然不需要超过 4 字节的堆栈对齐。
x86-64 System V 在寄存器中传递 args,这比 i386 System V 的堆栈 args 约定更有效。它避免了将 args 存储到内存(缓存)然后在被调用者中再次加载它们的延迟和额外指令。这很有效,因为有更多可用的寄存器,并且对于延迟和乱序执行很重要的现代高性能 CPU 来说更好。(i386 ABI 很老了)。
在这种 新 机制中:首先将参数划分为类。每个参数的类决定了它传递给被调用函数的方式。
有关完整信息,请参阅:System V Application Binary Interface AMD64 Architecture Processor Supplement的“3.2 函数调用序列”,其中部分内容如下:
一旦参数被分类,寄存器被分配(按从左到右的顺序)用于传递,如下所示: 如果类是 MEMORY,则在堆栈上传递参数。 如果类是 INTEGER,则使用序列 %rdi、%rsi、%rdx、%rcx、%r8 和 %r9 的下一个可用寄存器
一旦参数被分类,寄存器被分配(按从左到右的顺序)用于传递,如下所示:
用于将整数/指针(即 INTEGER 类)参数传递给程序集中的任何 libc 函数 的%rdi, %rsi, %rdx, %rcx, %r8 and %r9寄存器也是如此。%rdi 用于第一个 INTEGER 参数。%rsi 代表第二个,%rdx 代表第三个,依此类推。然后应该给出指示。堆栈 ( ) 在执行时必须是 16B 对齐的。 __call``%rsp``call
%rdi, %rsi, %rdx, %rcx, %r8 and %r9
call``%rsp``call
如果 INTEGER 参数超过 6 个,则将第 7 个及以后的 INTEGER 参数传递到堆栈上。(调用者弹出,与 x86-32 相同。)
前 8 个浮点参数在 %xmm0-7 中传递,稍后在堆栈中。没有调用保留向量寄存器。(一个混合了 FP 和整数参数的函数可以有超过 8 个寄存器参数。)
可变参数函数likeprintf总是需要%al= FP 寄存器参数的数量。
printf
%al
对于何时将结构打包到寄存器(rdx:rax返回时)与内存中,有一些规则。有关详细信息,请参阅 ABI,并检查编译器输出以确保您的代码与编译器就应该如何传递/返回某些内容达成一致。
rdx:rax
请注意,Windows x64 函数调用约定与 x86-64 System V 有多个显着差异,例如 必须 由调用者保留的阴影空间(而不是红色区域)和调用保留的 xmm6-xmm15。arg 进入哪个寄存器的规则非常不同。