関数呼び出し規約とprintf解析ガイド - GawinGowin/libasm GitHub Wiki

printf関数呼び出し解析とアセンブリ実装ガイド

このドキュメントは、具体的なprintf関数の呼び出し例を通して、アセンブリレベルでの関数呼び出しの実装と解析方法をまとめたものです。

可変長引数関数の特別ルール

可変長引数関数(printf, scanfなど)を呼び出す際は、System V ABIにおいて特別な準備が必要です:

  • RAXレジスタ: XMMレジスタを使用する浮動小数点引数の個数を設定
  • この値は実際に渡される浮動小数点引数の数と一致する必要があります
  • 整数のみの場合はmov eax, 0として0を設定

printf関数の呼び出し解析

実例のアセンブリコード解析

以下のCコード:

int main() {
    int a = 1;
    int b = 2;
    printf("%d\n", a + b);
    return 0;
}

対応するアセンブリコード:

.LC0:
  .string "%d\n"
main:
  push rbp
  mov rbp, rsp
  sub rsp, 16
  mov DWORD PTR [rbp-4], 1    ; a = 1
  mov DWORD PTR [rbp-8], 2    ; b = 2
  mov edx, DWORD PTR [rbp-4]  ; edx = a (1)
  mov eax, DWORD PTR [rbp-8]  ; eax = b (2)
  add eax, edx                ; eax = a + b (3)
  mov esi, eax                ; 第2引数: 計算結果
  mov edi, OFFSET FLAT:.LC0   ; 第1引数: フォーマット文字列
  mov eax, 0                  ; XMM使用数 = 0
  call printf
  mov eax, 0                  ; main の返り値
  leave
  ret

引数設定の詳細分析

1. フォーマット文字列の準備

.LC0:
  .string "%d\n"              ; 読み取り専用データセクションに文字列定義

2. 第1引数(フォーマット文字列)の設定

mov edi, OFFSET FLAT:.LC0     ; RDI = フォーマット文字列のアドレス
  • OFFSET FLAT:.LC0: ラベル.LC0の絶対アドレス
  • EDIレジスタ(RDIの32ビット部分)に格納

3. 第2引数(整数値)の設定

mov edx, DWORD PTR [rbp-4]    ; edx = 変数a (1)
mov eax, DWORD PTR [rbp-8]    ; eax = 変数b (2)
add eax, edx                  ; eax = a + b (3)
mov esi, eax                  ; RSI = 計算結果
  • 計算結果をESIレジスタ(RSIの32ビット部分)に格納

4. 可変長引数の準備

mov eax, 0                    ; RAX = XMM使用数 (0個)
  • 浮動小数点引数がないため0を設定

printf関数の実行と返り値

関数呼び出し

call printf                   ; printf("%d\n", 3) を実行

返り値の処理

  • printf の返り値: 出力した文字数(この場合は2: "3" + "\n")
  • 格納場所: RAXレジスタ
  • 後続処理:
    mov eax, 0                  ; main関数の返り値として0を設定(printf返り値を上書き)
    

アドレッシングモードの活用

メモリアクセスパターンの実例

直接アドレッシング

mov edi, OFFSET FLAT:.LC0     ; 直接アドレス指定

レジスタ間接アドレッシング

mov eax, DWORD PTR [rbp-4]    ; [rbp-4] のメモリ内容をeaxに読み込み

ベース+変位アドレッシング

mov DWORD PTR [rbp-8], 2      ; rbp-8 の位置に値2を格納

DWORD PTR の意味と使用法

mov DWORD PTR [rbp-4], 1      ; 32ビット(4バイト)データの格納
  • DWORD PTR: 32ビット(Double Word Pointer)サイズ指定
  • メモリ操作時のデータサイズを明示
  • C言語のint型に対応

関数フレーム管理の実装

プロローグ(関数開始時)

push rbp                      ; 呼び出し元のベースポインタ保存
mov rbp, rsp                  ; 新しいベースポインタ設定
sub rsp, 16                   ; ローカル変数用スペース確保

エピローグ(関数終了時)

leave                         ; mov rsp, rbp; pop rbp と同等
ret                           ; 呼び出し元に復帰

ローカル変数の配置

mov DWORD PTR [rbp-4], 1      ; int a = 1; (4バイト)
mov DWORD PTR [rbp-8], 2      ; int b = 2; (4バイト)
  • スタックは下向きに成長(アドレスが減少)
  • ローカル変数はRBPからの負のオフセットで配置

複数引数とprintf応用例

複数整数を表示するprintf

printf("%d + %d = %d\n", a, b, a+b);

対応するアセンブリ:

mov edi, OFFSET FLAT:.format  ; 第1引数: フォーマット文字列
mov esi, [a]                  ; 第2引数: a の値
mov edx, [b]                  ; 第3引数: b の値
mov ecx, [result]             ; 第4引数: a+b の値
mov eax, 0                    ; XMM使用数 = 0
call printf

浮動小数点を含むprintf

printf("値: %.2f\n", 3.14);

対応するアセンブリ:

mov edi, OFFSET FLAT:.format  ; 第1引数: フォーマット文字列
movsd xmm0, [pi_value]        ; 第1浮動小数点引数: 3.14
mov eax, 1                    ; XMM使用数 = 1
call printf

デバッグとトラブルシューティング

printf特有のよくある間違い

  1. RAX(XMM使用数)設定忘れ

    ; 間違い - RAX設定なし
    mov edi, format_string
    mov esi, value
    call printf                 ; 予期しない動作
    
    ; 正しい
    mov edi, format_string
    mov esi, value
    mov eax, 0                  ; 必須: XMM使用数設定
    call printf
    
  2. フォーマット文字列と引数の不一致

    ; 間違い - フォーマットは2つの%dだが引数は1つ
    mov edi, OFFSET FLAT:"%d %d\n"
    mov esi, value1
    ; mov edx, value2 が不足
    call printf
    
  3. 浮動小数点引数でのRAX設定間違い

    ; 間違い
    movsd xmm0, [float_val]
    mov eax, 0                  ; XMM0を使っているのに0
    
    ; 正しい
    movsd xmm0, [float_val] 
    mov eax, 1                  ; XMM0を1つ使用
    

デバッグ時の確認ポイント

printf呼び出し直前の状態

  • RDI: フォーマット文字列のアドレス
  • RSI, RDX, RCX, R8, R9: 整数引数
  • XMM0-XMM7: 浮動小数点引数
  • RAX: XMMレジスタ使用数

printf実行後の状態

  • RAX: 出力文字数(返り値)

まとめ

printf関数の呼び出しを通してアセンブリレベルでの関数呼び出し実装の重要なポイント:

  1. フォーマット文字列: 必ず第1引数(RDI)に配置
  2. 可変長引数対応: RAXにXMM使用数を必ず設定
  3. メモリレイアウト: ローカル変数はスタック上に適切に配置
  4. データサイズ指定: DWORD PTRなどで明示的にサイズ指定
  5. 返り値活用: printfの返り値(出力文字数)をRAXで受け取り

これらの知識により、C標準ライブラリ関数とアセンブリコードの効率的な連携が可能になります。