← 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は引数、戻り値、保存状態、stack責任を定義する。
  • ABIはさらに型layout、object format、symbol、relocation、unwindを含む。

extern "C"はC++のlanguage linkageとname manglingを制御するが、Windows関数をSystem V規約に変えるものではない。

主要な比較

項目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 spaceなし32バイトshadow space
red zone条件を満たすuser-space leafに128バイトなし
主な非volatile GPRrbx,rbp,r12〜r15,rsp加えてrdi,rsi
非volatile XMMなしxmm6〜xmm15の下位128ビット

aggregate、vector、variadic、hidden return pointerは追加規則が必要である。

caller-savedとcallee-saved

volatileレジスタはcall後に変化してよく、必要ならcallerが保存する。nonvolatileレジスタをcalleeが使用した場合は元へ戻す。

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

保存は必ずpushするという意味ではない。使わなければ保存不要で、stack slotへmovしてもよい。

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バイトは復帰アドレスを考慮した整列の代表例である。引数が4個未満でもshadow spaceを予約する。

Cのlongは一般的なSystem V LP64では64ビット、Windows LLP64では32ビットである。「x64だからlongも64ビット」とは限らない。

スタック整列の時点

次の時点を分ける。

  1. callerのcall直前
  2. 復帰アドレスが積まれたcallee入口
  3. prologue後
  4. nested call直前

ある時点の剰余規則を別の時点へ適用すると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関数は例外処理とstack walkのため実際の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は実行命令の代わりではなく、それを記述するmetadataである。不一致は通常のretが成功しても例外やprofilerを壊す。

stack引数とFP引数

Windowsの5番目以降の位置は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はvector register引数に関する情報をalで伝える。
  • Microsoft x64はvariadic FP値を対応するinteger registerにも複製する。
  • Cのdefault argument promotionでfloatはdoubleになる。

System Vはaggregateをeightbyte単位で分類し、可能ならinteger/SSEレジスタへ分割する。Windowsは特定サイズを値として、それ以外をcaller側temporaryへのpointerで渡すことがある。handwritten ASM境界では固定幅scalar、単純なextern "C" free function、sizeof/alignof/offsetofのassert、callerとcallee両方の逆アセンブリを優先する。

大きな構造体戻り値にはhidden return pointerが追加され、明示引数が一つずつずれたように見えることがある。

tail call

wrapper:
    ; target用に引数を配置
    jmp target

ABI条件が合えばcompilerは最後の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. 変更したnonvolatileを全経路で復元するか。
  7. Windows frame directiveは実際のprologueと一致するか。
  8. 戻り値を正しい幅のraxまたはxmm0から読むか。

まとめ

callとretは骨格にすぎない。System Vは6本の主な整数引数レジスタとred zoneを使い、Microsoft x64は4つの位置ベースslot、32バイトshadow space、非volatile XMM、厳格なunwind規則を使う。表を暗記するだけでなく、caller/calleeの逆アセンブリ、境界テスト、stack walkで契約を証明する。

公式資料