簡體   English   中英

i386 和 x86-64 上的 UNIX 和 Linux 系統調用(和用戶空間函數)的調用約定是什么

[英]What are the calling conventions for UNIX & Linux system calls (and user-space functions) on i386 and x86-64

以下鏈接解釋了 UNIX(BSD 風格)和 Linux 的 x86-32 系統調用約定:

但是 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 所有其他寄存器(包括 EFLAGS)都保留在int $0x80

我從Linux 匯編教程中獲取了以下片段,但我對此表示懷疑。 如果有人能舉個例子,那就太好了。

如果有六個以上的參數, %ebx必須包含存儲參數列表的內存位置 - 但不要擔心這一點,因為您不太可能使用具有六個以上參數的系統調用。

有關示例和更多閱讀內容,請參閱http://www.int80h.org/bsdasm/#alternate-calling-convention 另一個使用int 0x80 i386 Linux Hello World 示例: Hello, world in assembly language with Linux system calls?

有一種更快的方法來進行 32 位系統調用:使用sysenter 內核的內存頁映射到每一個進程(VDSO),與用戶空間側sysenter舞,這與內核合作,它能夠找到返回地址。 注冊映射的 Arg 與int $0x80相同。 通常應調入VDSO而是采用sysenter直接。 (見權威指南到Linux系統調用有關鏈接和調用到VDSO,以及有關的詳細信息信息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 $0x80 ABI用於 64 位代碼(但強烈不推薦)。 如果在 64 位代碼中使用 32 位 int 0x80 Linux ABI,會發生什么? 它仍然將其輸入截斷為 32 位,因此它不適合指針,並將 r8-r11 置零。

用戶界面:函數調用

x86-32 函數調用約定:

在 x86-32 中,參數在堆棧上傳遞。 最后一個參數首先被壓入堆棧,直到所有參數都完成,然后call指令被執行。 這用於從匯編調用 Linux 上的 C 庫 (libc) 函數。

i386 System V ABI 的現代版本(在 Linux 上使用)在call之前需要%esp 16 字節對齊,就像 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 Function Calling Sequence”,部分內容如下:

一旦參數被分類,寄存器將被分配(按從左到右的順序)傳遞,如下所示:

  1. 如果類是 MEMORY,則在堆棧上傳遞參數。
  2. 如果類是整數,則使用序列 %rdi、%rsi、%rdx、%rcx、%r8 和 %r9 的下一個可用寄存器

所以%rdi, %rsi, %rdx, %rcx, %r8 and %r9是用於/指針(即INTEGER類)參數從組件傳遞給任何libc函數整數,以便在寄存器中。 %rdi 用於第一個 INTEGER 參數。 %rsi 表示第二個,%rdx 表示第三個,依此類推。 然后應給出call指令。 call執行時,堆棧 ( %rsp ) 必須是 16B 對齊的。

如果有超過 6 個 INTEGER 參數,則第 7 個 INTEGER 參數及之后的參數將在堆棧上傳遞。 (來電彈出,與 x86-32 相同。)

前 8 個浮點參數在 %xmm0-7 中傳遞,稍后在堆棧中。 沒有調用保留的向量寄存器。 (混合了 FP 和整數參數的函數可以有 8 個以上的寄存器參數。)

可變參數函數( printf )總是需要%al = FP 寄存器參數的數量。

關於何時將結構打包到寄存器(返回時為rdx:rax )與內存中的規則。 有關詳細信息,請參閱 ABI,並檢查編譯器輸出以確保您的代碼與編譯器就應如何傳遞/返回某些內容達成一致。


請注意, Windows x64 函數調用約定與 x86-64 System V 有多個顯着差異,例如調用者必須保留的陰影空間(而不是紅色區域)和調用保留的 xmm6-xmm15。 對於哪個 arg 進入哪個寄存器的非常不同的規則。

也許您正在尋找 x86_64 ABI?

如果這不是您想要的,請在您首選的搜索引擎中使用“x86_64 abi”來查找替代參考。

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

對於 32 位的arch/x86/entry/entry_32.S

/*
 * 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 評論說,這在這里是不必要的。
  • memory表示可以在匯編中傳遞指針並用於訪問內存

有關從頭開始的顯式最小可運行示例,請參閱以下答案: 如何在內聯匯編中通過 syscall 或 sysenter 調用系統調用?

手動在程序集中進行一些系統調用

不是很科學,但很有趣:

  • 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 上游.

從 C 進行系統調用

這是一個帶有寄存器約束的示例: 如何在內聯匯編中通過 syscall 或 sysenter 調用系統調用?

aarch64

我在此處展示了一個最小的可運行用戶空間示例: https: //reverseengineering.stackexchange.com/questions/16917/arm64-syscalls-table/18834#18834 TODO grep 內核代碼在這里,應該很容易。

調用約定定義了在調用或被其他程序調用時如何在寄存器中傳遞參數。 這些約定的最佳來源是為每個這些硬件定義的 ABI 標准。 為了便於編譯,用戶空間和內核程序也使用相同的 ABI。 Linux/Freebsd 對 x86-64 遵循相同的 ABI,對 32 位遵循相同的 ABI。 但是用於 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

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM