在i386和x86-64上,UNIX和Linux系统调用的调用约定是什么?
以下链接解释了UNIX(BSD flavor)和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上使用GNU Assembler(gas)validation了这些。
内核接口
x86-32 Linux系统调用约定:
在Linux系统调用的x86-32参数中使用寄存器传递。 syscall_number的%eax
。 %ebx,%ecx,%edx,%esi,%edi,%ebp用于将6个parameter passing给系统调用。
返回值是%eax
。 所有其他的寄存器(包括EFLAGS)都保存在int $0x80
。
我从Linux程序集教程中拿出了以下代码片断,但是我对此有疑问。 如果有人能举一个例子,那就太好了。
如果有超过6个参数,
%ebx
必须包含存储参数列表的内存位置,但不要担心,因为不太可能使用超过6个参数的系统调用。
有关示例和更多的阅读,请参阅http://www.int80h.org/bsdasm/#alternate-calling-convention
有更快的方式进行32位系统调用:使用sysenter
。 内核将一页内存映射到每个进程(vdso)中,而sysenter的用户空间一方必须与内核配合才能find返回地址。 arg到寄存器的映射与int $0x80
是一样的,但代替那个指令,代码应该在vdso中调用一个函数。 (TODO:用链接和/或特定信息更新)。
x86-32 [Free | Open | Net | DragonFly] BSD UNIX系统调用约定:
参数在栈上传递。 将参数(最先推入的参数)推入堆栈。 然后再推送一个额外的32位的虚拟数据(其实不是虚拟数据,参考下面的链接了解更多信息),然后给系统调用指令int $0x80
http://www.int80h.org/bsdasm/#default-calling-convention
x86-64 Linux和* BSD系统调用约定:
请参考系统V应用程序二进制接口AMD64体系结构处理器补充文件 “A.2 AMD64 Linux内核惯例”一节。 最新版本的i386和x86-64 System V psABIs可以从ABI维护者的回购的这个页面find。 (关于最新的ABI链接,请参见x86标签wiki以及关于x86 asm的许多其他好东西。)
以下是本节中的片段:
- 用户级应用程序使用整数寄存器来传递序列%rdi,%rsi,%rdx,%rcx,%r8和%r9。 内核接口使用%rdi,%rsi,%rdx,%r10,%r8和%r9。
- 系统调用是通过
syscall
指令完成的。 这个clobbers%rcx和%r11,以及%rax,但其他寄存器都被保留下来。- 系统调用的号码必须在寄存器%rax中传递。
- 系统调用被限制为六个参数,没有参数直接在栈上传递。
- 从系统调用返回,注册%rax包含系统调用的结果。 范围在-4095和-1之间的值表示错误,它是
-errno
。- 只有INTEGER类或MEMORY类的值被传递给内核。
用户界面
x86-32函数调用约定:
在x86-32参数被传递到堆栈上。 最后一个参数先被压入堆栈,直到所有参数完成,然后执行call
指令。 这用于从程序集调用Linux上的C库(libc)函数。
x86-64函数调用约定:
x86-64在寄存器中传递参数,这比i386 System V的堆栈参数约定更有效。 它避免了将参数存储到内存(caching)的延迟和额外的指令,然后再次将它们加载回被调用者。 这样做效果很好,因为有更多的寄存器可供使用,对于现代高性能CPU而言,延迟和无序执行很重要。 (i386 ABI很旧)。
在这个新机制中:首先参数被分成几类。 每个参数的类决定了传递给被调用函数的方式。
有关完整的信息,请参阅: System V应用程序二进制接口 “3.2函数调用序列” AMD64 Architecture Processor Supplement部分读取:
一旦参数被分类,寄存器被分配(按照从左到右的顺序),如下所示:
- 如果类是MEMORY,则将parameter passing给堆栈。
- 如果类是INTEGER,则使用序列%rdi,%rsi,%rdx,%rcx,%r8和%r9的下一个可用寄存器
因此, %rdi, %rsi, %rdx, %rcx, %r8 and %r9
是用于将整数/指针(即INTEGER类)parameter passing给程序集中的任何libc函数的寄存器。 %rdi用于第一个INTEGER参数。 第二个为%rsi,第三个为第rdx个,等等。 然后给予指导。 执行call
时,堆栈( %rsp
)必须与16Balignment。
如果有多于6个INTEGER参数,则第7个INTEGER参数及更高版本将在堆栈上传递。 (呼叫者popup,与x86-32相同。)
前8个浮点数参数在%xmm0-7中传递,稍后放在堆栈中。 没有调用保存向量寄存器。 (一个FP和整数参数混合的函数可以有8个以上的寄存器参数。)
variables函数( 如printf
)总是需要%al
= FP寄存器参数的数量。
关于何时将结构打包到寄存器( rdx:rax
返回)还是在内存中有规则。 有关详细信息,请参阅ABI,并检查编译器输出以确保您的代码与编译器同意如何传递/返回。
也许你正在寻找x86_64 ABI?
如果这不符合您的要求,请在首选search引擎中使用“x86_64 abi”以查找其他参考。
调用约定定义了在调用或被其他程序调用时参数如何传递到寄存器中。 这些约定的最佳来源是为每个硬件定义的ABI标准。 为了便于编译,用户空间和内核程序也使用相同的ABI。 Linux / Freebsd遵循相同的ABI为x86-64和另一组为32位。 但是Windows的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):
性能是这些ABI的原因之一(例如,通过寄存器传递参数而不是保存到内存堆栈中)
对于ARM来说,有各种ABI:
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.subset.swdev.abi/index.html
library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/iPhoneOSABIReference.html
ARM64约定:
help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.html
对于PowerPC上的Linux:
http://refspecs.freestandards.org/elf/elfspec_ppc.pdf
而embedded式有PPC EABI:
http://www.freescale.com/files/32bit/doc/app_note/PPCEABI.pdf
本文件是所有不同惯例的良好概述:
除了Jonathan Leffler在他的回答中提供的链接外,Agner Fog的呼叫约定 pdf也许对您有用。