[インデックス 1598] ファイルの概要
このコミットは、Go言語のリンカである6l
における2つの重要な問題を修正します。一つは、ファイルの末尾にあるデッドコード(到達不能なコード)が削除される際に、関連する行番号の履歴が失われる問題です。もう一つは、Valgrindによって発見された、初期化されていないメモリ使用によるエラーの修正です。これにより、デバッグ情報の正確性が向上し、リンカの安定性が確保されます。
コミット
- コミットハッシュ:
47e27758dbe26e17aa0955780f37f41036151a2a
- Author: Russ Cox rsc@golang.org
- Date: Fri Jan 30 17:10:10 2009 -0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/47e27758dbe26e17aa0955780f37f41036151a2a
元コミット内容
keep line number history even when
throwing away dead code at end of file.
also fix an uninitialized memory error
found by valgrind.
R=r
DELTA=7 (5 added, 2 deleted, 0 changed)
OCL=23991
CL=23994
変更の背景
このコミットは、Go言語の初期開発段階におけるリンカの品質向上を目的としています。
-
行番号履歴の保持: Goプログラムのデバッグやプロファイリングにおいて、正確な行番号情報は不可欠です。リンカがデッドコードを削除する際に、そのコードに関連する行番号情報まで破棄してしまうと、デバッグ時にスタックトレースやエラーメッセージが行番号を正確に示せなくなる問題が発生します。特にファイルの末尾に存在するデッドコードの場合、この問題が顕著でした。このコミットは、デッドコードが削除されても、その行番号履歴が適切に保持されるようにリンカの挙動を修正します。
-
Valgrindによる未初期化メモリのエラー修正: Valgrindは、メモリ関連のバグ(メモリリーク、未初期化メモリの使用、無効なメモリアクセスなど)を検出するための強力なツールです。GoリンカのコードベースをValgrindで解析した結果、未初期化のメモリが使用されている箇所が発見されました。未初期化メモリの使用は、プログラムの予測不能な動作、クラッシュ、またはセキュリティ脆弱性につながる可能性があるため、早期に修正する必要がありました。
これらの問題は、Go言語の安定性とデバッグ体験に直接影響するため、重要な修正とされました。
前提知識の解説
Go 6l リンカ
6l
は、Go言語の初期バージョンで使用されていたリンカの一つで、特に64ビットx86アーキテクチャ(amd64)向けに設計されていました。Goツールチェーンにおけるリンカの主な役割は以下の通りです。
- コンパイル済みコードの結合: コンパイラによって生成されたオブジェクトファイル(
.o
ファイル)や、Go標準ライブラリ、その他の依存関係を結合し、単一の実行可能バイナリを生成します。 - シンボルの解決: 関数や変数の参照を、それらが定義されている実際のメモリ位置に解決します。
- デッドコード削除 (Dead Code Elimination - DCE): プログラムのエントリポイント(通常は
main
関数)から到達できない関数や変数などのコードを特定し、最終的なバイナリから削除します。これにより、実行ファイルのサイズが削減されます。 - 再配置 (Relocation): コンパイル時には不明だった、他のパッケージの関数呼び出しやデータへの参照の最終的なメモリ位置を決定し、アドレスを割り当てます。
- DWARF情報の生成: デバッグ情報(ソースコードの行番号、変数情報など)を生成し、バイナリに埋め込みます。これはデバッガがプログラムの実行を追跡するために使用します。
現在のGoでは、6l
のようなアーキテクチャ固有のリンカコマンドは、go build
コマンドを通じて間接的に呼び出される統合されたリンカに置き換えられています。
Valgrindと未初期化メモリのエラー
Valgrindは、主にC/C++プログラムのメモリ管理とスレッド関連のバグを検出するためのオープンソースのインストゥルメンテーションフレームワークです。その中でもMemcheck
ツールは、メモリリーク、無効なメモリアクセス、そして「未初期化メモリの使用」を検出するのに非常に優れています。
未初期化メモリのエラーとは、プログラムが明示的に値が割り当てられていないメモリ領域からデータを読み取ろうとしたときに発生する問題です。C言語では、ローカル変数やmalloc
で確保されたメモリは、初期化されない限り、そのメモリ位置に以前存在していた「ゴミ」データを含んでいます。このゴミデータを使用すると、プログラムの動作が予測不能になったり、クラッシュしたり、誤った結果を生成したりする可能性があります。
Valgrindが「Conditional jump or move depends on uninitialized value(s)」のようなエラーを報告する場合、それはプログラムの条件分岐(if
文、for
ループ、while
ループなど)が未初期化の値に基づいて行われていることを意味します。--track-origins=yes
オプションをValgrindに渡すことで、未初期化の値がどこで生成されたかを追跡し、デバッグを大幅に支援できます。
デッドコード削除と行番号情報
Goリンカのデッドコード削除は、最終的な実行ファイルのサイズを最適化するために不可欠なプロセスです。しかし、このプロセスはデバッグ情報、特に行番号情報に影響を与えます。リンカがコードを「デッド」と判断してバイナリから削除すると、その削除されたコードに関連する行番号データも当然ながらバイナリから取り除かれます。
通常、これは問題ありません。なぜなら、デッドコードは実行されないため、その行番号情報がデバッグ時に必要になることはないからです。しかし、リンカの実装によっては、デッドコードの削除が意図せず、生きているコードの行番号履歴に影響を与えたり、特定の状況下でデバッグ情報が不完全になったりする可能性があります。このコミットの目的は、リンカがデッドコードを削除する際にも、デバッグに必要な行番号履歴が適切に保持されるようにすることです。
技術的詳細
このコミットは、Goリンカの6l
における2つの異なる問題に対処しています。
-
行番号履歴の保持:
src/cmd/6l/span.c
の変更は、リンカがシンボル情報を処理する際のロジックを調整しています。以前のコードでは、STEXT
型(テキストセクション、つまり実行可能なコード)ではないシンボルを早期にスキップしていました。しかし、ファイルの末尾にあるデッドコードの場合、そのコード自体は実行されなくても、関連する行番号情報(ヒストリ)はデバッグのために保持されるべきでした。このコミットでは、if(s->type != STEXT) continue;
というチェックを、ファイル名やその他の自動生成シンボルを処理した後、かつputsymb
でシンボルを書き出す直前に移動しています。これにより、STEXT
ではないシンボル(例えば、デッドコードに関連するシンボル)であっても、ファイル名などのデバッグ関連情報が適切に処理された後でスキップされるようになり、行番号履歴が不必要に失われることを防ぎます。 -
未初期化メモリのエラー修正:
src/cmd/6l/obj.c
のaddhist
関数における変更は、Valgrindによって検出された未初期化メモリのエラーを修正します。addhist
関数は、リンカがソースコードの行番号履歴を構築する際に使用されます。s->name
はシンボルの名前を格納するためのバッファですが、このバッファが適切にヌル終端されていない場合、後続の処理で未初期化のメモリが読み取られる可能性があります。 追加されたs->name[0] = 0;
は、s->name
バッファの先頭を明示的にヌル文字で初期化します。また、s->name[j] = 0;
とs->name[j+1] = 0;
は、histfrog
配列から取得した値がs->name
にコピーされた後、バッファの末尾を確実にヌル終端します。これにより、s->name
が常に有効な文字列として扱われ、未初期化メモリの読み取りを防ぎます。
これらの修正は、リンカの内部的なデータ構造の整合性を保ち、デバッグ情報の正確性を高めるとともに、メモリ安全性を向上させるものです。
コアとなるコードの変更箇所
src/cmd/6l/obj.c
--- a/src/cmd/6l/obj.c
+++ b/src/cmd/6l/obj.c
@@ -757,6 +757,7 @@ addhist(int32 line, int type)
u->link = curhist;
curhist = u;
+ s->name[0] = 0;
j = 1;
for(i=0; i<histfrogp; i++) {
k = histfrog[i]->value;
@@ -764,6 +765,8 @@ addhist(int32 line, int type)
s->name[j+1] = k;
j += 2;
}
+ s->name[j] = 0;
+ s->name[j+1] = 0;
}
void
src/cmd/6l/span.c
--- a/src/cmd/6l/span.c
+++ b/src/cmd/6l/span.c
@@ -240,8 +240,6 @@ asmsym(void)
for(p=textp; p!=P; p=p->pcond) {
s = p->from.sym;
-\t\tif(s->type != STEXT)
-\t\t\tcontinue;
/* filenames first */
for(a=p->to.autom; a; a=a->link)
@@ -251,6 +249,8 @@ asmsym(void)
if(a->type == D_FILE1)
putsymb(a->asym->name, 'Z', a->aoffset, 0, nil);
+ if(s->type != STEXT)
+ continue;
putsymb(s->name, 'T', s->value, s->version, gotypefor(s->name));
/* frame, auto and param after */
コアとなるコードの解説
src/cmd/6l/obj.c
の変更
addhist
関数は、リンカがシンボルの行番号履歴を記録する際に呼び出されます。
-
s->name[0] = 0;
の追加: これは、s->name
というシンボル名を格納するバッファの最初のバイトをヌル文字(\0
)で初期化しています。これにより、s->name
が常に有効なC文字列として開始されることが保証され、Valgrindが検出した未初期化メモリの使用エラーを防ぎます。特に、histfrog
配列からデータがコピーされる前に、バッファがクリーンな状態であることを保証します。 -
s->name[j] = 0;
とs->name[j+1] = 0;
の追加:histfrog
配列からの値のコピーが完了した後、s->name
バッファの末尾にヌル文字を2つ追加しています。これは、s->name
が常に適切にヌル終端された文字列であることを保証するためのものです。j
はコピーされたデータの長さを追跡しており、j
とj+1
にヌル文字を置くことで、文字列の終端を明確にし、後続の文字列操作関数が未初期化メモリを読み込むことを防ぎます。
これらの変更は、s->name
バッファの初期化とヌル終端を徹底することで、メモリ安全性を向上させ、Valgrindのエラーを解消します。
src/cmd/6l/span.c
の変更
asmsym
関数は、リンカがアセンブリシンボルを処理する部分です。
if(s->type != STEXT) continue;
の移動: 元のコードでは、ループの早い段階でs->type
がSTEXT
(実行可能なコード)でない場合に、そのシンボルの処理をスキップしていました。 変更後のコードでは、このチェックがfilenames first
とコメントされたセクション(ファイル名などのデバッグ関連情報を処理する部分)の後に移動されています。 これにより、STEXT
ではないシンボル(例えば、デッドコードに関連するシンボルや、データセクションのシンボル)であっても、D_FILE1
のようなファイル名シンボルが先に処理されるようになります。これは、デッドコードであっても、そのコードが元々どのファイルに属していたかという情報(行番号履歴の一部)をリンカが保持できるようにするために重要です。putsymb
関数が呼び出される直前にチェックを移動することで、シンボル自体の出力はSTEXT
に限定しつつ、関連するデバッグ情報が適切に処理されることを保証します。
この変更は、リンカがデッドコードを削除する際にも、デバッグに必要な行番号履歴が適切に保持されるようにするためのロジックの調整です。
関連リンク
- Go言語のリンカに関する詳細な情報: https://go.dev/src/cmd/link/README (現在のリンカのREADMEですが、概念は共通しています)
参考にした情報源リンク
- Go 6l linker:
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHvO8g0APEp8ow7CYsWpc9wgR58sfrZspc_1T_h1Nio5yQDqZ3GJplKtt7ZU0p5M5vGe7-sWXR98rimxM0GmPuLJPFJb8su1hDEZdMkrPjYSsEq6zDJY0fvYvo-rb4hYfueqy0R-fxUZXDhVCVCnazF33RJCvyZnAAgGKb2
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFmYc6K1kf7Z_PX-c8j9yJDJAhl8r34O_GGbe0Ef-FtIowLIW3wEBpeguyqoXiGQFY-dm33isvrOWOyhxw6f-l-DgaOlM66b13oMWdRucRZxyTGrScjxDFWof69HEFLS2jTQfjVkB5-bFEZvuPOFTA=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHd4jRHbctkt-WwLw62nuWw4f_N0FRv9eP2QlhRzkZEZCpiL2MWcr5fudV-yLi2n2Y22_0hyj612hy8d7Me28TDSLHTE9Il3perlxhUvmKAAjOEaCa8PU4Sy91c4PK6iLu2CdCmYW6GqGihs71YmZlH_JVQ6Xzg7v52KNuMXrOSJCWm2sZYJA==
- Valgrind memory error C uninitialized:
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEWKjqXMGn-rn7t7yCJsxMzJuca_CNn0d_Ukzz1s1d6uUGgya9l8ZYaKEAHuY2vVTvLCIF48bxpaOHfIiMXN2HNXvsd7zVl36U1MCOGE_IVbCSZU-jeCEe_XgG4KHs5Pwwnf4ZWMhPCNZoJ
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGh5t_444Cc2NtEW49_C3cVLfp28iDnGp15EcrsiYaMFw0DDxNN0MMhOHDmjIt88Oays9elrMf6IIczN-2C8DcidxeY50ij6iC8y0Mj9jYhBRRmaSTzV14BxjDGDcEuStRH-JM6Leqe0UhQH_k5dOJkNj-f1qgidEmqHtPr73mCwyHp2tjVzzWW
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHoyIOim4kxQHeP5u8wWN_ljuUXbxy4jyy9ZaC9RTHx6A5yY0-2XxkP1l4HqiylL2kzUwNyOn2gp-nPOiwaCMMobP539RZYlCxJbgXlj2MDJDF6U1XP0Iao3M3tlTpN9gBqTxo=
- Go linker line number history dead code:
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHPC526b5IiAIsEWyWrFbE3A_xJ7y8ATycX10X6QJUJaP4Zo7jLMhNYI_HTfRFy2sYuzlXlXEKUCXnxjU7Rqd5Ndz5Pn4H00z1an3ajYy3qV4M23ssugr8ajhJbZv2sDfKOl_dM80pgaCXEJsTiaH7w4d1b-Wt76irwg-r85bM=
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQFGm89Btqky3zGnSpVhTcrRavV50ec97iqW_C43i2MmugaR_Xn8vVPSpQGxEyI01TebF8hEnC_Th_h6ZlysVlEFVEkJs1s_WPscA4GgXxt4-RALyNNjAu3MMB0cS1pUiTSBKNeJ4iJuyZqxLlS0pGLMbDMUerHKSD0kotcOwMsf_8nvq5GnUMF0LQ4ZvG8998bF
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEffIYt9XqQ_879JMm4xw7jjGdSeMVjEVt8a9nBhTYn_l0VPtqBeLgMyaEgQtFI4YwSTojz2mf4kJdrgdWv4cC8PlT1xUe-c6tvJQio_qnBVUz1dZ3LB9i2hyyufJeIqqJ18DfhrcJjRgif8X9pPye9zpQmi83Pmqb0i-2viTychYhdI1fVIf40KH7L9opVKwo=