在i386和x86-64上,UNIX和Linux系统调用的调用约定是什么

问题描述 投票:124回答:4

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

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

linux unix assembly x86-64 abi
4个回答
200
投票

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


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

Kernel 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采取了以下片段,但我对此表示怀疑。如果有人可以展示一个例子,那就太好了。

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

有关示例和更多阅读,请参阅http://www.int80h.org/bsdasm/#alternate-calling-convention。另一个使用int 0x80的i386 Linux Hello World的例子:What parts of this HelloWorld assembly code are essential if I were to write the program in assembly?

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

x86-32 [免费|打开|网络| DragonFly] BSD UNIX系统调用约定:

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

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


x86-64 Linux System Call convention:

x86-64 Mac OS X is similar but different。 TODO:检查* BSD的作用。

请参阅System V Application Binary Interface AMD64 Architecture Processor Supplement的“A.2 AMD64 Linux内核约定”一节。可以找到最新版本的i386和x86-64 System V psABIs linked from this page in the ABI maintainer's repo。 (另请参阅标记wiki以获取最新的ABI链接以及许多关于x86 asm的其他好东西。)

以下是本节的摘录:

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

请记住,这是来自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归零。

User Interface: function calling

x86-32函数调用约定:

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

现代版本的i386 System V ABI(在Linux上使用)需要在%esp之前对call进行16字节对齐,就像x86-64 System V ABI一直需要的那样。允许被调用者使用SSE 16字节加载/存储未对齐的错误。但从历史上看,Linux只需要4字节堆栈对齐,因此即使对于8字节的double也需要额外的工作来保留自然对齐的空间。

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


x86-64 System V user-space Function Calling convention:

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的下一个可用寄存器

所以%rdi, %rsi, %rdx, %rcx, %r8 and %r9是用于将整数/指针(即INTEGER类)参数传递给汇编的任何libc函数的顺序的寄存器。 %rdi用于第一个INTEGER参数。 %rsi代表第二名,%rdx代表第三名,依此类推。然后应该给出call指令。当%rsp执行时,堆栈(call)必须是16B对齐的。

如果有超过6个INTEGER参数,则第7个INTEGER参数及更高版本参数将在堆栈上传递。 (来电者弹出,与x86-32相同。)

前8个浮点args在%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进入哪个寄存器的规则非常不同。


15
投票

也许您正在寻找x86_64 ABI?

如果这不是您所追求的,请在您首选的搜索引擎中使用“x86_64 abi”来查找替代参考。


11
投票

调用约定定义了在调用或被其他程序调用时如何在寄存器中传递参数。这些约定的最佳来源是为每种硬件定义的ABI标准。为了便于编译,用户空间和内核程序也使用相同的ABI。 Linux / Freebsd遵循相同的ABI for x86-64和另一组32-bit。但x86-64 ABI for Windows与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


5
投票

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系统调用,它包含一些有趣的代码,例如:

/* 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

快速提醒一下clobbers:

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

对于从头开始的显式最小可运行示例,请参阅以下答案:How to invoke a system call via sysenter in inline assembly?

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

不是很科学,但有趣:

  • 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 upstream

aarch64

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

© www.soinside.com 2019 - 2024. All rights reserved.