関数呼び出し規約と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特有のよくある間違い
-
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つの%dだが引数は1つ mov edi, OFFSET FLAT:"%d %d\n" mov esi, value1 ; mov edx, value2 が不足 call printf
-
浮動小数点引数での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引数(RDI)に配置
- 可変長引数対応: RAXにXMM使用数を必ず設定
- メモリレイアウト: ローカル変数はスタック上に適切に配置
- データサイズ指定: DWORD PTRなどで明示的にサイズ指定
- 返り値活用: printfの返り値(出力文字数)をRAXで受け取り
これらの知識により、C標準ライブラリ関数とアセンブリコードの効率的な連携が可能になります。