Linux x86 调用约定

最后更新: 2025-11-12 00:09:19 +0800 +0800
tags: Abi | Linux | X86
原文以英文撰写。您正在阅读AI翻译版本。

A calling convention 是 a binary contract 用于 参数 placement, results, preserved state, 和 stack layout. Linux x86-64 programs 使用 the System V AMD64 函数 ABI; 32-bit programs 使用 the System V i386 ABI. Linux 系统 调用 使用 separate entry ABIs 和 必须 不 be treated 作为 ordinary 函数 calls.

AMD64 函数调用 #

AMD64 ABI first classifies each argument. Integer-class values, including ordinary integers 和 pointers, 使用 rdi, rsi, rdx, rcx, r8, 和 r9. SSE-class values 使用 xmm0 通过 xmm7. Exhausting one class does 不 由 itself exhaust the other; arguments cannot be assigned according 到 the classification rules go in the stack input area.

Aggregates 是 divided 到 eightbyte classes. 一个 qualifying small structure can therefore occupy multiple integer 或 vector registers, 同时 a memory-class structure 是 passed 在 the stack. For a memory-class 返回 value, the caller allocates storage 和 passes its address in hidden 参数 register rdi; the callee returns address in rax.

Integer-class 返回 values 使用 rax 和 then rdx; SSE-class results 使用 xmm0 and then xmm1. Floating-point float 和 double results 使用 xmm0, 同时 x87 classes such 作为 long double 使用 the x87 stack 作为 specified 由 the ABI.

rbx, rbp, r12 通过 r15, 和 rsp 是 callee-saved. 一个 callee changes one 必须 restore it 在……之前 ret. Other general-purpose registers 和 all vector registers 是 caller-saved under the base System V ABI, so a caller preserves any live values it needs 在……之后 a call.

Immediately 在……之前 call, rsp 是 16-byte aligned. pushed eight-byte return address means an ordinary callee enters,使用 rsp % 16 == 8. Stack arguments are laid out,使用 the padding required 由 their alignment; they 是 不 simply pushed one 在 a time. ABI also reserves a 128-byte red zone below the current rsp. Leaf 用户空间 functions 可以 使用 it without adjusting the stack because normal signal delivery preserves it. Kernel 代码 和 代码 built,使用 -mno-red-zone must not rely 在 area.

可变参数函数 #

Fixed arguments 到 a variadic 函数 使用 the normal classification algorithm. A caller additionally sets %al 到 an upper bound, 来自 0 通过 8, 在 the number of vector 参数 registers used. Only the low byte 是 defined 用于 this purpose. A handwritten 调用 到 printf leaves %al unspecified therefore violates the ABI even if every visible 参数 appears correct.

A variadic callee 使用 va_start 提供 a register-save area in its own frame 和 saves the 参数 registers it may need. AMD64 va_list tracks:

  • gp_offset 和 fp_offset within save area;
  • overflow_arg_area 用于 arguments passed in memory; and
  • reg_save_area, the base 的 the saved register arguments.

va_arg applies type classification 和 alignment 同时 advancing the relevant location. Because default 参数 promotions apply, a caller passes float as double 和 narrow integer types 作为 int 或 unsigned int where required.

i386 函数调用 #

i386 System V ABI passes ordinary arguments in the stack input area in right-to-left order. At entry, 0(%esp) 包含 the 返回 address 和 4(%esp) contains the first argument. caller removes arguments 在……之后 返回 under the standard C convention.

ebx, esi, edi, ebp, 和 esp 是 callee-saved; eax, ecx, 和 edx are caller-saved. 一个 integer 或 pointer 结果 使用 eax, 和 a two-word integer 可以 use edx:eax; floating-point results 使用 st(0). Structure 返回 conventions depend on classification 和 可以 使用 a hidden destination pointer.

A conventional frame-pointer prologue is:

asm
push    ebp
mov     ebp, esp
sub     esp, local_size

ebp 是 不 intrinsically a frame pointer, so optimized 代码 可以 omit this sequence. Historical i386 代码 assumed four-byte stack alignment. current GNU/Linux i386 ABI 需要 16-byte alignment 在 a 调用 boundary, 和 compilers 可以 realign entry when interoperating,使用 legacy callers. Handwritten assembly 必须 follow the ABI selected 由 its toolchain rather than infer alignment 来自 old examples.

Linux 系统调用 #

x86-64 Linux syscall ABI is:

PurposeRegisters
System-call numberrax
Arguments 1–6rdi, rsi, rdx, r10, r8, r9
Resultrax
Unconditionally clobbered 由 syscallrcx, r11

Hardware saves the user 返回 address in rcx 和 user flags in r11; the fourth argument therefore 使用 r10, unlike an AMD64 函数 call. 内核 returns either a successful 值 或 a negative 错误 in rax; values 来自 -1 through -4095 encode -errno. 一个 libc wrapper converts such an 错误 到 -1 和 sets thread-local errno.

asm
; x86-64 Linux: write(1, message, message_length)
mov     eax, 1
mov     edi, 1
lea     rsi, [rel message]
mov     edx, message_length
syscall

For an i386 进程 using int $0x80, eax holds the number, arguments 使用 ebx, ecx, edx, esi, edi, 和 ebp, 和 eax holds the result. These registers are syscall operands even though ebx, esi, edi, 和 ebp 是 callee-saved in the i386 函数 ABI; a 函数 containing inline entry 代码 必须 still preserve the 函数 ABI 用于 its caller.

函数 ABI 与系统调用 ABI #

An AMD64 函数 调用 使用 rcx 用于 参数 four 和 preserves rbx, rbp, and r12–r15; an x86-64 syscall 使用 r10 用于 参数 four 和 has explicit rcx/r11 clobbers. 一个 函数 returns errors according 到 its declared API, whereas the raw 内核 boundary 使用 negative 错误 codes. syscall instruction also changes privilege level 和 does 不 push a 返回 address 或 switch stacks by itself; Linux entry 代码 performs the required kernel-stack transition.

Application 代码 应当 prefer libc wrappers because they implement both contracts, error translation, cancellation behavior where applicable, 和 vDSO selection. Inline assembly 必须 describe register, condition-code, 和 内存 effects 到 the compiler 和 是 appropriate primarily 用于 C libraries 和 runtimes.

System V AMD64 psABI, syscall(2), 和 Linux entry_64.S are the primary 参考资料用于 these contracts.

评论

在 GitHub 上阅读或评论(发表评论需要登录)