← SEARCHABLE ARCHIVE
Assembly · 2026-08-15

x86-64 函式呼叫與 ABI 實戰

System V 與 Windows x64 的參數、保存暫存器、堆疊對齊與展開

INTERACTIVE READING MODE →

x86-64 函式呼叫與 ABI 實戰

本部落格文章以教學為目的。 本文比較常見的 System V AMD64 與 Microsoft x64 ABI;kernel、syscall、vectorcall 等必須另外查閱目標環境文件。

CPU 的 call 只儲存回傳位址並轉移控制。參數和回傳值放哪裡、誰保存暫存器、堆疊如何對齊、框架如何 unwind,都是 ABI 的契約。

ISA、calling convention、ABI

  • ISA 定義指令與暫存器的硬體行為。
  • calling convention 定義參數、回傳值、保存狀態與堆疊責任。
  • ABI 還包含型別配置、object format、symbol、relocation、unwind。

extern "C" 控制 C++ language linkage 與 name mangling,不會把 Windows 函式改成 System V 規則。

兩種常見 ABI 比較

項目System V AMD64Microsoft x64
整數參數rdi,rsi,rdx,rcx,r8,r9位置 1~4 用 rcx,rdx,r8,r9
FP 參數主要為 xmm0~xmm7位置 1~4 用 xmm0~xmm3
整數回傳raxrax
FP 回傳xmm0xmm0
caller 區域無 shadow space32 位元組 shadow space
red zone合格 user-space leaf 有 128 位元組無
主要 nonvolatile GPRrbx,rbp,r12~r15,rsp另含 rdi,rsi
nonvolatile XMM無xmm6~xmm15 低 128 位元

aggregate、vector、variadic、hidden return pointer 需要更詳細分類。

caller-saved 與 callee-saved

volatile 暫存器可在 call 後改變,必要時由 caller 保存。callee 若使用 nonvolatile 暫存器,必須在回傳前恢復。

push rbx
mov rbx, rcx
; use rbx
pop rbx
ret

保存不代表一定用 push;未使用就不用保存,也可用 mov 寫入 stack slot。重點是函式邊界可觀察到的狀態。

System V 與 Windows 呼叫

System V:

mov edi, 10
mov esi, 20
mov edx, 30
call combine
; rax = result

Windows x64:

sub rsp, 40
mov ecx, 10
mov edx, 20
mov r8d, 30
call combine
add rsp, 40

32 位元組是 caller 準備的 shadow space,額外 8 位元組是考慮 call 推入回傳位址後的常見對齊方式。參數少於四個時仍要預留 shadow space。

一般 System V LP64 的 C long 是 64 位元,Windows LLP64 則是 32 位元;「x64」不表示每個語言型別都是 64 位元。

堆疊對齊要標明時點

必須分開:

  1. caller 的 call 前;
  2. 回傳位址已推入的 callee 入口;
  3. prologue 後;
  4. nested call 前。

把某時點的 modulo 規則套到另一時點,就會產生常見的 8 位元組混亂。Windows x64 要求 prologue/epilogue 外的 rsp 保持 16 位元組對齊。

red zone 與 unwind

System V user-space leaf 可在符合條件時使用 rsp 下方 128 位元組 red zone。nested call、Windows、kernel 或停用 red zone 的建置不能套用此假設。

Windows non-leaf 函式必須讓真實 prologue 與 unwind metadata 一致。

Work PROC FRAME
    push rbx
    .pushreg rbx
    sub rsp, 32
    .allocstack 32
    .endprolog
    ; body
    add rsp, 32
    pop rbx
    ret
Work ENDP

directive 不會代替指令執行,只描述 unwind。兩者不一致時,一般 ret 可能成功,但例外處理、profiler 與 stack walk 仍會失敗。

stack 與浮點參數

Windows 第五個之後的位置使用 stack,但 offset 會隨「callee 入口」或「prologue 後」而變。不能脫離時點把 [rsp+40] 背成第五個參數。

void f(int a, double b, int c, float d);

Windows 約為 a -> rcx、b -> xmm1、c -> r8、d -> xmm3。它按參數位置分配,不是只計算 FP 參數後把 b 放 xmm0。

variadic、結構與隱藏參數

printf 等函式有額外規則:

  • System V 用 al 傳達與 vector register 參數相關的資訊。
  • Microsoft x64 會把 variadic FP 值複製到對應 integer register。
  • C default argument promotion 會把 float 提升成 double。

System V 以 eightbyte 分類 aggregate,可能拆到 integer/SSE 暫存器;Windows 對特定大小以值傳遞,其他情況可能傳 caller temporary 的指標。手寫 ASM 邊界應優先使用固定寬度 scalar、簡單 extern "C" free function,並 assert sizeof、alignof、offsetof,同時檢查 caller 與 callee 反組譯。

大型結構回傳可能加入 hidden return pointer,使所有明示參數看起來往後移一格。

tail call

wrapper:
    ; 安排 target 所需參數
    jmp target

若 ABI 條件允許,編譯器可把最後的 call 加 ret 改成 jmp,讓 target 直接回到原 caller。

除錯順序

  1. caller 與 callee 是否使用相同 architecture/ABI?
  2. symbol 與 language linkage 是否一致?
  3. call 前各參數的位置、位元與型別是否正確?
  4. stack offset 以哪個時點的 rsp 為基準?
  5. alignment 與 Windows shadow space 是否正確?
  6. callee 是否在所有路徑恢復 nonvolatile 暫存器?
  7. Windows frame directive 是否吻合真實 prologue?
  8. 回傳值是否從正確寬度的 rax 或 xmm0 讀取?

總結

call 與 ret 只是硬體骨架。System V 使用六個主要整數參數暫存器並提供 red zone;Microsoft x64 使用四個依位置分配的 register slot、32 位元組 shadow space、nonvolatile XMM 與嚴格 unwind 規則。不要只背表格,要用 caller/callee 反組譯、邊界測試與 stack walk 證明契約。

官方資料