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

时间:2010-03-29 05:48:44

标签: linux unix assembly x86-64 abi

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

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

4 个答案:

答案 0 :(得分:199)

答案 1 :(得分:15)

也许您正在寻找x86_64 ABI?

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

答案 2 :(得分: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

答案 3 :(得分:3)

Linux内核5.0源注释

我知道x86细节在arch/x86下,并且syscall内容在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

快速提醒您:

  • 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