[インデックス 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特有の機能を持つバイナリを生成します。
print
関数とフォーマット指定子
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
が使用されていました。前述の通り、%d
は int
型(通常32ビットの符号付き整数)の値を期待します。しかし、segtext.filelen
は、リンカが処理するバイナリのサイズが非常に大きくなる可能性を考慮して、unsigned long long
型(通常64ビットの符号なし整数)として定義されていました。
この型ミスマッチが発生すると、print
関数は segtext.filelen
の64ビットの値を、32ビットの int
として解釈しようとします。これにより、以下の問題が発生する可能性があります。
- 値の切り捨て:
segtext.filelen
の値がint
型の最大値(約20億)を超える場合、上位ビットが切り捨てられ、誤った値が表示されます。例えば、非常に大きな値が負の数として表示されることもあります(符号ビットの誤解釈)。 - スタックの破損:
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.filelen
が unsigned long long
型であることを正しく認識し、その値を正確な10進数形式で出力できるようになりました。この修正により、リンカのデバッグ出力における textsize
の表示が信頼できるものとなり、開発者がバイナリのサイズに関する正確な情報を得られるようになりました。
他の print
文 (datsize=%ulld\\n
, bsssize=%ulld\\n
) は元々 %ulld
を使用しており、これらは正しかったため変更されていません。このことは、segtext.filelen
のみが誤ったフォーマット指定子を使用していたことを示唆しています。
関連リンク
- Go CL (Change List) 7304065: https://golang.org/cl/7304065
参考にした情報源リンク
- C言語
printf
フォーマット指定子: https://www.cplusplus.com/reference/cstdio/printf/ - Go言語のツールチェインとリンカに関する一般的な情報 (Goの公式ドキュメントやブログ記事など)
- A Tour of Go: https://go.dev/tour/
- Go Blog: https://go.dev/blog/
- The Go Programming Language Specification: https://go.dev/ref/spec
- Plan 9 from Bell Labs (Goのルーツ): https://9p.io/plan9/
- Goのリンカの内部構造に関する情報 (Goのソースコードや関連する技術記事)
- Goのソースコード: https://github.com/golang/go
- Goのリンカに関する議論やドキュメント (例:
src/cmd/link
ディレクトリ内のドキュメントやコメント)# [インデックス 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特有の機能を持つバイナリを生成します。
しかし、Goツールチェインの進化に伴い、これらのアーキテクチャ固有のリンカは go tool link
という単一のコマンドの下に統合されました。現在では、go build
コマンドがターゲットアーキテクチャに適した内部リンカを自動的に選択して使用するため、5l
などの特定のリンカを直接呼び出すことはありません。
Goリンカの主な役割は以下の通りです。
- ロードとシンボル収集: コンパイルされたすべてのパッケージと依存関係をロードし、関数や変数などのシンボルを収集して解析します。
- デッドコード削除: コンパイラはパッケージ単位で動作するため、グローバルな最適化は行えません。リンカは、最終的なバイナリを小さくするために、使用されていないコードを削除します。
- DWARF生成: デバッグ情報(DWARF)を生成し、バイナリに埋め込みます。
- 再配置: パッケージは独立してコンパイルされるため、リンカは最終的な実行可能ファイル内の関数呼び出しやデータ参照に正しいメモリアドレスを割り当てる責任があります。
print
関数とフォーマット指定子
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
が使用されていました。前述の通り、%d
は int
型(通常32ビットの符号付き整数)の値を期待します。しかし、segtext.filelen
は、リンカが処理するバイナリのサイズが非常に大きくなる可能性を考慮して、unsigned long long
型(通常64ビットの符号なし整数)として定義されていました。
この型ミスマッチが発生すると、print
関数は segtext.filelen
の64ビットの値を、32ビットの int
として解釈しようとします。これにより、以下の問題が発生する可能性があります。
- 値の切り捨て:
segtext.filelen
の値がint
型の最大値(約20億)を超える場合、上位ビットが切り捨てられ、誤った値が表示されます。例えば、非常に大きな値が負の数として表示されることもあります(符号ビットの誤解釈)。 - スタックの破損:
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.filelen
が unsigned long long
型であることを正しく認識し、その値を正確な10進数形式で出力できるようになりました。この修正により、リンカのデバッグ出力における textsize
の表示が信頼できるものとなり、開発者がバイナリのサイズに関する正確な情報を得られるようになりました。
他の print
文 (datsize=%ulld\\n
, bsssize=%ulld\\n
) は元々 %ulld
を使用しており、これらは正しかったため変更されていません。このことは、segtext.filelen
のみが誤ったフォーマット指定子を使用していたことを示唆しています。
関連リンク
- Go CL (Change List) 7304065: https://golang.org/cl/7304065
参考にした情報源リンク
- C言語
printf
フォーマット指定子: https://www.cplusplus.com/reference/cstdio/printf/ - Go言語のツールチェインとリンカに関する一般的な情報 (Goの公式ドキュメントやブログ記事など)
- A Tour of Go: https://go.dev/tour/
- Go Blog: https://go.dev/blog/
- The Go Programming Language Specification: https://go.dev/ref/spec
- Plan 9 from Bell Labs (Goのルーツ): https://9p.io/plan9/
- Goのリンカの内部構造に関する情報 (Goのソースコードや関連する技術記事)
- Goのソースコード: https://github.com/golang/go
- Goのリンカに関する議論やドキュメント (例:
src/cmd/link
ディレクトリ内のドキュメントやコメント)