Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 15225] ファイルの概要

このコミットは、Go言語のリンカツール cmd/5l における出力フォーマットのバグ修正に関するものです。具体的には、textsize の出力に使用されていた %d フォーマット指定子が、segtext.filelen の実際の型と合致していなかった問題を修正し、正しい %ulld に変更しています。これにより、リンカのデバッグ出力が正確に表示されるようになります。

コミット

commit a3855013a24c79b571f672fc031b78816802c3a3
Author: Lucio De Re <lucio.dere@gmail.com>
Date:   Wed Feb 13 16:47:33 2013 -0500

    cmd/5l: fix print format
    
    R=golang-dev, dave
    CC=golang-dev
    https://golang.org/cl/7304065

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a3855013a24c79b571f672fc031b78816802c3a3

元コミット内容

cmd/5l: fix print format

R=golang-dev, dave
CC=golang-dev
https://golang.org/cl/7304065

変更の背景

この変更は、Go言語のリンカ cmd/5l が生成するデバッグ出力において、textsize の値が正しく表示されないというバグを修正するために行われました。textsize はプログラムのテキストセクション(実行可能コード)のサイズを示す重要な情報であり、リンカの動作をデバッグする際に利用されます。

元のコードでは、textsize を出力するために print("textsize=%d\\n", segtext.filelen); という形式が使われていました。ここで %d は通常、符号付き整数(int)の出力に使用されるフォーマット指定子です。しかし、segtext.filelen の実際の型が unsigned long long のような、より大きな符号なし整数型であったため、フォーマット指定子と変数の型がミスマッチを起こしていました。このミスマッチにより、textsize の値が正しく表示されず、場合によっては予期しない大きな値や負の値として表示される可能性がありました。

この問題は、特に大きなバイナリをリンクする際に顕著になり、リンカのデバッグやパフォーマンス分析を妨げる要因となっていました。そのため、segtext.filelen の型に合わせた正しいフォーマット指定子を使用することで、この表示バグを修正する必要がありました。

前提知識の解説

Go言語のリンカ (cmd/5l)

Go言語のツールチェインには、プログラムをコンパイルし、最終的な実行可能バイナリを生成するための様々なツールが含まれています。その中の一つがリンカです。Go言語のリンカは、コンパイラによって生成されたオブジェクトファイル(.o ファイル)や、標準ライブラリ、サードパーティライブラリなどを結合し、単一の実行可能ファイルを作成する役割を担います。

cmd/5l は、Go言語の初期のリンカの一つで、Plan 9アーキテクチャの命名規則に由来しています。5 はARMアーキテクチャ(ARMv5)を指し、l はリンカ(linker)を意味します。Go言語はクロスコンパイルを強くサポートしており、異なるアーキテクチャ向けのリンカが存在します(例: 6l はamd64、8l は386など)。これらのリンカは、Goのランタイムと連携して、ガベージコレクション、スケジューリング、スタック管理などのGo特有の機能を持つバイナリを生成します。

C言語系のプログラミング言語(Goのツールチェインの一部はCで書かれている)では、printf やそれに類する print 関数が、変数の値を整形して出力するために広く使われます。これらの関数は、フォーマット文字列と呼ばれる特殊な文字列を引数に取り、その中に含まれるフォーマット指定子(format specifier)を使って、後続の引数の型と表示形式を指定します。

主要なフォーマット指定子には以下のようなものがあります。

  • %d: 符号付き10進整数 (int)
  • %u: 符号なし10進整数 (unsigned int)
  • %ld: long int
  • %lld: long long int
  • %lu: unsigned long int
  • %llu: unsigned long long int
  • %x, %X: 16進数
  • %f: 浮動小数点数 (float, double)
  • %s: 文字列 (char *)
  • %c: 文字 (char)
  • %p: ポインタ

フォーマット指定子と、それに対応する引数の実際の型が一致しない場合、未定義の動作(undefined behavior)を引き起こす可能性があります。これは、プログラムがクラッシュしたり、誤った値が表示されたり、セキュリティ上の脆弱性につながったりする可能性があることを意味します。今回のコミットは、まさにこの型ミスマッチの問題を修正しています。

segtext.filelen

Goリンカの内部では、プログラムの各セクション(テキスト、データ、BSSなど)に関する情報が構造体として管理されています。segtext はテキストセクション(実行可能コード)に関する情報を保持する構造体であり、filelen はそのセクションがファイル内で占めるバイト数を表すフィールドです。この filelen フィールドは、バイナリのサイズが大きくなる可能性を考慮して、通常は unsigned long long のような大きな符号なし整数型で定義されています。

技術的詳細

このコミットの技術的な核心は、C言語における printf ファミリー関数のフォーマット指定子の正確な使用にあります。

元のコード print("textsize=%d\\n", segtext.filelen); では、segtext.filelen の値を出力するために %d が使用されていました。前述の通り、%dint 型(通常32ビットの符号付き整数)の値を期待します。しかし、segtext.filelen は、リンカが処理するバイナリのサイズが非常に大きくなる可能性を考慮して、unsigned long long 型(通常64ビットの符号なし整数)として定義されていました。

この型ミスマッチが発生すると、print 関数は segtext.filelen の64ビットの値を、32ビットの int として解釈しようとします。これにより、以下の問題が発生する可能性があります。

  1. 値の切り捨て: segtext.filelen の値が int 型の最大値(約20億)を超える場合、上位ビットが切り捨てられ、誤った値が表示されます。例えば、非常に大きな値が負の数として表示されることもあります(符号ビットの誤解釈)。
  2. スタックの破損: printf 関数は可変引数(variadic arguments)を取るため、フォーマット文字列に基づいてスタックから引数を読み取ります。期待される引数のサイズ(%d なら4バイト)と実際の引数のサイズ(unsigned long long なら8バイト)が異なる場合、スタックポインタがずれてしまい、後続の引数が誤って読み取られたり、関数呼び出しの戻りアドレスが破壊されたりする可能性があります。これは、プログラムのクラッシュや予期しない動作につながります。

修正後のコード print("textsize=%ulld\\n", segtext.filelen); では、フォーマット指定子が %ulld に変更されています。

  • %u: 符号なし整数 (unsigned int)
  • %ll: long long を意味する修飾子
  • %ulld: unsigned long long 型の値を10進数で出力するための正しいフォーマット指定子です。

この変更により、print 関数は segtext.filelen の64ビットの符号なし整数値を正しく解釈し、正確な textsize を出力できるようになります。これにより、リンカのデバッグ出力の信頼性が向上し、開発者がバイナリのサイズに関する正確な情報を得られるようになります。

この修正は、Go言語のツールチェインの堅牢性を高めるための、細部への注意を示す良い例です。特に、低レベルのシステムプログラミングやツール開発においては、このような型とフォーマット指定子の厳密な一致が不可欠です。

コアとなるコードの変更箇所

--- a/src/cmd/5l/asm.c
+++ b/src/cmd/5l/asm.c
@@ -686,7 +686,7 @@ asmb(void)\n 	}\n 	cflush();\n 	if(debug['c']){\n-\t\tprint("textsize=%d\\n", segtext.filelen);\n+\t\tprint("textsize=%ulld\\n", segtext.filelen);\
 \t\tprint("datsize=%ulld\\n", segdata.filelen);\
 \t\tprint("bsssize=%ulld\\n", segdata.len - segdata.filelen);\
 \t\tprint("symsize=%d\\n", symsize);\

コアとなるコードの解説

変更は src/cmd/5l/asm.c ファイルの asmb 関数内で行われています。

asmb 関数は、Goリンカの主要な処理の一部であり、アセンブリコードの生成やセクションの配置など、バイナリの最終的な構造を決定する役割を担っています。この関数内には、デバッグ目的で様々なセクションのサイズを出力する部分があります。

元のコード: print("textsize=%d\\n", segtext.filelen);

この行では、textsize という文字列に続いて、segtext.filelen の値を %d フォーマット指定子を使って出力していました。前述の通り、%d は符号付き整数を想定しているため、unsigned long long 型である segtext.filelen との間に型ミスマッチが生じていました。

修正後のコード: print("textsize=%ulld\\n", segtext.filelen);

この行では、フォーマット指定子が %d から %ulld に変更されています。

  • %u: 符号なし整数
  • %ll: long long 型の修飾子

これにより、print 関数は segtext.filelenunsigned long long 型であることを正しく認識し、その値を正確な10進数形式で出力できるようになりました。この修正により、リンカのデバッグ出力における textsize の表示が信頼できるものとなり、開発者がバイナリのサイズに関する正確な情報を得られるようになりました。

他の print 文 (datsize=%ulld\\n, bsssize=%ulld\\n) は元々 %ulld を使用しており、これらは正しかったため変更されていません。このことは、segtext.filelen のみが誤ったフォーマット指定子を使用していたことを示唆しています。

関連リンク

参考にした情報源リンク

このコミットは、Go言語のリンカツール cmd/5l における出力フォーマットのバグ修正に関するものです。具体的には、textsize の出力に使用されていた %d フォーマット指定子が、segtext.filelen の実際の型と合致していなかった問題を修正し、正しい %ulld に変更しています。これにより、リンカのデバッグ出力が正確に表示されるようになります。

コミット

commit a3855013a24c79b571f672fc031b78816802c3a3
Author: Lucio De Re <lucio.dere@gmail.com>
Date:   Wed Feb 13 16:47:33 2013 -0500

    cmd/5l: fix print format
    
    R=golang-dev, dave
    CC=golang-dev
    https://golang.org/cl/7304065

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/a3855013a24c79b571f672fc031b78816802c3a3

元コミット内容

cmd/5l: fix print format

R=golang-dev, dave
CC=golang-dev
https://golang.org/cl/7304065

変更の背景

この変更は、Go言語のリンカ cmd/5l が生成するデバッグ出力において、textsize の値が正しく表示されないというバグを修正するために行われました。textsize はプログラムのテキストセクション(実行可能コード)のサイズを示す重要な情報であり、リンカの動作をデバッグする際に利用されます。

元のコードでは、textsize を出力するために print("textsize=%d\\n", segtext.filelen); という形式が使われていました。ここで %d は通常、符号付き整数(int)の出力に使用されるフォーマット指定子です。しかし、segtext.filelen の実際の型が unsigned long long のような、より大きな符号なし整数型であったため、フォーマット指定子と変数の型がミスマッチを起こしていました。このミスマッチにより、textsize の値が正しく表示されず、場合によっては予期しない大きな値や負の値として表示される可能性がありました。

この問題は、特に大きなバイナリをリンクする際に顕著になり、リンカのデバッグやパフォーマンス分析を妨げる要因となっていました。そのため、segtext.filelen の型に合わせた正しいフォーマット指定子を使用することで、この表示バグを修正する必要がありました。

前提知識の解説

Go言語のリンカ (cmd/5l)

Go言語のツールチェインには、プログラムをコンパイルし、最終的な実行可能バイナリを生成するための様々なツールが含まれています。その中の一つがリンカです。Go言語のリンカは、コンパイラによって生成されたオブジェクトファイル(.o ファイル)や、標準ライブラリ、サードパーティライブラリなどを結合し、単一の実行可能ファイルを作成する役割を担います。

cmd/5l は、Go言語の初期のリンカの一つで、Plan 9アーキテクチャの命名規則に由来しています。5 はARMアーキテクチャ(ARMv5)を指し、l はリンカ(linker)を意味します。Go言語はクロスコンパイルを強くサポートしており、異なるアーキテクチャ向けのリンカが存在します(例: 6l はamd64、8l は386など)。これらのリンカは、Goのランタイムと連携して、ガベージコレクション、スケジューリング、スタック管理などのGo特有の機能を持つバイナリを生成します。

しかし、Goツールチェインの進化に伴い、これらのアーキテクチャ固有のリンカは go tool link という単一のコマンドの下に統合されました。現在では、go build コマンドがターゲットアーキテクチャに適した内部リンカを自動的に選択して使用するため、5l などの特定のリンカを直接呼び出すことはありません。

Goリンカの主な役割は以下の通りです。

  • ロードとシンボル収集: コンパイルされたすべてのパッケージと依存関係をロードし、関数や変数などのシンボルを収集して解析します。
  • デッドコード削除: コンパイラはパッケージ単位で動作するため、グローバルな最適化は行えません。リンカは、最終的なバイナリを小さくするために、使用されていないコードを削除します。
  • DWARF生成: デバッグ情報(DWARF)を生成し、バイナリに埋め込みます。
  • 再配置: パッケージは独立してコンパイルされるため、リンカは最終的な実行可能ファイル内の関数呼び出しやデータ参照に正しいメモリアドレスを割り当てる責任があります。

C言語系のプログラミング言語(Goのツールチェインの一部はCで書かれている)では、printf やそれに類する print 関数が、変数の値を整形して出力するために広く使われます。これらの関数は、フォーマット文字列と呼ばれる特殊な文字列を引数に取り、その中に含まれるフォーマット指定子(format specifier)を使って、後続の引数の型と表示形式を指定します。

主要なフォーマット指定子には以下のようなものがあります。

  • %d: 符号付き10進整数 (int)
  • %u: 符号なし10進整数 (unsigned int)
  • %ld: long int
  • %lld: long long int
  • %lu: unsigned long int
  • %llu: unsigned long long int
  • %x, %X: 16進数
  • %f: 浮動小数点数 (float, double)
  • %s: 文字列 (char *)
  • %c: 文字 (char)
  • %p: ポインタ

フォーマット指定子と、それに対応する引数の実際の型が一致しない場合、未定義の動作(undefined behavior)を引き起こす可能性があります。これは、プログラムがクラッシュしたり、誤った値が表示されたり、セキュリティ上の脆弱性につながったりする可能性があることを意味します。今回のコミットは、まさにこの型ミスマッチの問題を修正しています。

segtext.filelen

Goリンカの内部では、プログラムの各セクション(テキスト、データ、BSSなど)に関する情報が構造体として管理されています。segtext はテキストセクション(実行可能コード)に関する情報を保持する構造体であり、filelen はそのセクションがファイル内で占めるバイト数を表すフィールドです。この filelen フィールドは、バイナリのサイズが大きくなる可能性を考慮して、通常は unsigned long long のような大きな符号なし整数型で定義されています。

技術的詳細

このコミットの技術的な核心は、C言語における printf ファミリー関数のフォーマット指定子の正確な使用にあります。

元のコード print("textsize=%d\\n", segtext.filelen); では、segtext.filelen の値を出力するために %d が使用されていました。前述の通り、%dint 型(通常32ビットの符号付き整数)の値を期待します。しかし、segtext.filelen は、リンカが処理するバイナリのサイズが非常に大きくなる可能性を考慮して、unsigned long long 型(通常64ビットの符号なし整数)として定義されていました。

この型ミスマッチが発生すると、print 関数は segtext.filelen の64ビットの値を、32ビットの int として解釈しようとします。これにより、以下の問題が発生する可能性があります。

  1. 値の切り捨て: segtext.filelen の値が int 型の最大値(約20億)を超える場合、上位ビットが切り捨てられ、誤った値が表示されます。例えば、非常に大きな値が負の数として表示されることもあります(符号ビットの誤解釈)。
  2. スタックの破損: printf 関数は可変引数(variadic arguments)を取るため、フォーマット文字列に基づいてスタックから引数を読み取ります。期待される引数のサイズ(%d なら4バイト)と実際の引数のサイズ(unsigned long long なら8バイト)が異なる場合、スタックポインタがずれてしまい、後続の引数が誤って読み取られたり、関数呼び出しの戻りアドレスが破壊されたりする可能性があります。これは、プログラムのクラッシュや予期しない動作につながります。

修正後のコード print("textsize=%ulld\\n", segtext.filelen); では、フォーマット指定子が %ulld に変更されています。

  • %u: 符号なし整数 (unsigned int)
  • %ll: long long を意味する修飾子
  • %ulld: unsigned long long 型の値を10進数で出力するための正しいフォーマット指定子です。

この変更により、print 関数は segtext.filelen の64ビットの符号なし整数値を正しく解釈し、正確な textsize を出力できるようになります。これにより、リンカのデバッグ出力の信頼性が向上し、開発者がバイナリのサイズに関する正確な情報を得られるようになります。

この修正は、Go言語のツールチェインの堅牢性を高めるための、細部への注意を示す良い例です。特に、低レベルのシステムプログラミングやツール開発においては、このような型とフォーマット指定子の厳密な一致が不可欠です。

コアとなるコードの変更箇所

--- a/src/cmd/5l/asm.c
+++ b/src/cmd/5l/asm.c
@@ -686,7 +686,7 @@ asmb(void)\n 	}\n 	cflush();\n 	if(debug['c']){\n-\t\tprint("textsize=%d\\n", segtext.filelen);\n+\t\tprint("textsize=%ulld\\n", segtext.filelen);\
 \t\tprint("datsize=%ulld\\n", segdata.filelen);\
 \t\tprint("bsssize=%ulld\\n", segdata.len - segdata.filelen);\
 \t\tprint("symsize=%d\\n", symsize);\

コアとなるコードの解説

変更は src/cmd/5l/asm.c ファイルの asmb 関数内で行われています。

asmb 関数は、Goリンカの主要な処理の一部であり、アセンブリコードの生成やセクションの配置など、バイナリの最終的な構造を決定する役割を担っています。この関数内には、デバッグ目的で様々なセクションのサイズを出力する部分があります。

元のコード: print("textsize=%d\\n", segtext.filelen);

この行では、textsize という文字列に続いて、segtext.filelen の値を %d フォーマット指定子を使って出力していました。前述の通り、%d は符号付き整数を想定しているため、unsigned long long 型である segtext.filelen との間に型ミスマッチが生じていました。

修正後のコード: print("textsize=%ulld\\n", segtext.filelen);

この行では、フォーマット指定子が %d から %ulld に変更されています。

  • %u: 符号なし整数
  • %ll: long long 型の修飾子

これにより、print 関数は segtext.filelenunsigned long long 型であることを正しく認識し、その値を正確な10進数形式で出力できるようになりました。この修正により、リンカのデバッグ出力における textsize の表示が信頼できるものとなり、開発者がバイナリのサイズに関する正確な情報を得られるようになりました。

他の print 文 (datsize=%ulld\\n, bsssize=%ulld\\n) は元々 %ulld を使用しており、これらは正しかったため変更されていません。このことは、segtext.filelen のみが誤ったフォーマット指定子を使用していたことを示唆しています。

関連リンク

参考にした情報源リンク