[インデックス 1482] ファイルの概要
このコミットは、Go言語の初期のツールチェイン、特に6lリンカにおける重要な改善を導入しています。6lは、当時のGo言語のx86-64アーキテクチャ向けリンカでした。この変更は、リンカが未解決のシンボル(プログラム内で参照されているが定義が見つからない要素)に遭遇した際に、サイレントに誤ったコードを生成するのではなく、明示的な診断メッセージを出力するように修正するものです。
具体的には、以下のファイルが変更されています。
src/cmd/6l/asm.c: リンカのデータブロック処理に関連するアセンブラコード生成部分。ここで新しいシンボルタイプSxxxに対する診断ロジックが追加されました。src/cmd/6l/go.c: リンカのGo言語固有の処理部分。デバッグ出力の文字列が微修正されています。src/cmd/6l/l.h: リンカで使用される定数や構造体の定義が含まれるヘッダファイル。ここで新しいシンボルタイプSxxxが列挙型に追加されました。
コミット
commit 8559e3ad542b96ab9de267912e194fee8df04206
Author: Rob Pike <r@golang.org>
Date: Thu Jan 15 15:21:12 2009 -0800
diagnose missing symbols instead of
silently miscompiling.
R=rsc
OCL=22872
CL=22872
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/8559e3ad542b96ab9de267912e194fee8df04206
元コミット内容
diagnose missing symbols instead of
silently miscompiling.
R=rsc
OCL=22872
CL=22872
変更の背景
このコミットが行われた2009年1月は、Go言語がまだ一般に公開される前の、初期開発段階にありました。当時のGoコンパイラとリンカは、現在とは異なる設計思想や実装の詳細を持っていました。
このコミットの主な背景にある問題は、「サイレントな誤コンパイル (silently miscompiling)」です。これは、プログラムのコンパイルまたはリンクの過程で、本来エラーとして報告されるべき問題(この場合は未解決のシンボル)が発生したにもかかわらず、ツールチェインがエラーを出さずに処理を続行し、結果として誤った実行可能ファイルを生成してしまう現象を指します。
未解決のシンボルは、通常、リンカがプログラム内の参照(例えば、関数呼び出しや変数アクセス)に対応する定義を見つけられない場合に発生します。例えば、foo()という関数を呼び出しているが、そのfoo()の定義がどこにも見つからない、といったケースです。このような場合、リンカは通常エラーを報告し、実行可能ファイルの生成を停止すべきです。しかし、初期の6lリンカでは、特定の条件下でこのような未解決シンボルを適切に診断せず、代わりに不適切な値(例えばゼロアドレス)を割り当ててしまうことがありました。これにより、生成されたプログラムは実行時にクラッシュしたり、予期せぬ動作をしたりする可能性があり、デバッグが非常に困難になります。
このコミットは、このようなサイレントな失敗を防ぎ、リンカが未解決のシンボルを明示的にdiag(診断、エラー報告)することで、開発者が問題を早期に発見し、修正できるようにすることを目的としています。既存のSUNDEF(Undefined Symbol)タイプは、外部ライブラリなど、後で解決されることが期待されるシンボルを扱うためのものでしたが、Sxxxは、より深刻な、本当に見つからないシンボルを区別するために導入されたと考えられます。
前提知識の解説
Go言語の初期ツールチェイン (6g, 6l, 6a)
Go言語の初期のコンパイラとツールチェインは、Plan 9オペレーティングシステムのツールチェインに強く影響を受けていました。当時のGoのビルドシステムでは、ターゲットアーキテクチャごとに異なるプレフィックスを持つツールが使用されていました。
6g: x86-64 (64-bit Intel/AMD) アーキテクチャ向けのGoコンパイラ。Goのソースコードをアセンブリコードに変換します。6l: x86-64 アーキテクチャ向けのGoリンカ。コンパイラによって生成されたオブジェクトファイルやライブラリを結合し、実行可能ファイルを生成します。このコミットの主要な変更対象です。6a: x86-64 アーキテクチャ向けのアセンブラ。アセンブリコードをオブジェクトファイルに変換します。
これらのツールは、現在のGoツールチェイン(go buildコマンドなど)に統合されており、ユーザーが直接意識することは少なくなっていますが、Goの内部動作を理解する上で重要な歴史的背景です。
シンボルとリンカの役割
- シンボル: プログラミングにおいて、シンボルとは関数名、変数名、ラベルなど、プログラム内の特定のメモリ位置やコードブロックを識別するための名前です。コンパイラはソースコードを機械語に変換する際に、これらのシンボルを生成し、その定義と参照を管理します。
- リンカ: リンカは、コンパイラによって生成された一つ以上のオブジェクトファイル(コンパイルされたコードとデータを含む)と、必要なライブラリファイルを結合して、最終的な実行可能ファイルを生成するプログラムです。リンカの主要な役割の一つは、シンボル解決です。これは、あるオブジェクトファイルで参照されているシンボル(例えば、別のファイルで定義されている関数)の実際のメモリアドレスを特定し、その参照を正しいアドレスに置き換えるプロセスです。
シンボルタイプ (SUNDEF, Sxxx)
リンカは、シンボルをその性質に応じて異なるタイプで分類します。
SUNDEF(Symbol Undefined): これは「未定義シンボル」を意味します。リンカが現在のオブジェクトファイル内で参照されているが、その定義がまだ見つかっていないシンボルに対して割り当てるタイプです。通常、SUNDEFシンボルは、別のオブジェクトファイルやライブラリファイルにその定義が存在すると期待されており、リンカはそれらのファイルを検索して定義を解決しようとします。解決できれば、SUNDEFの状態は解消されます。Sxxx: このコミットで新しく導入されたシンボルタイプです。コミットメッセージから推測すると、これはSUNDEFとは異なり、本当に見つからない、または予期せぬ形で欠落しているシンボルを示すために使用されます。SUNDEFが「後で解決されるかもしれない」という期待を含むのに対し、Sxxxは「これは問題であり、解決できない」という状態を示すマーカーとして機能します。これにより、リンカはこのようなシンボルに遭遇した際に、サイレントに処理を続行するのではなく、明示的なエラーを報告できるようになります。
サイレントな誤コンパイル (Silent Miscompilation)
サイレントな誤コンパイルとは、コンパイラやリンカが、本来エラーとして報告すべき状況(例えば、構文エラー、型不一致、未解決シンボルなど)を検出したにもかかわらず、エラーメッセージを出力せずに、誤った、または意図しない動作をする実行可能ファイルを生成してしまう現象です。
これは非常に危険な問題です。なぜなら、ビルドプロセスは成功したように見えるため、開発者は問題が発生していることに気づきません。しかし、生成されたプログラムは実行時にクラッシュしたり、間違った計算結果を出したり、セキュリティ上の脆弱性を引き起こしたりする可能性があります。このような問題は、エラーメッセージがないため、デバッグが極めて困難になります。このコミットは、まさにこの「サイレントな誤コンパイル」を防ぐために、未解決シンボルに対する明示的な診断を追加しています。
技術的詳細
このコミットの技術的な核心は、Goリンカ(6l)が未解決のシンボルをどのように扱うかを改善し、サイレントな失敗を防ぐ点にあります。
-
Sxxxシンボルタイプの導入:src/cmd/6l/l.hファイルに、新しい列挙型メンバーSxxxが追加されました。これは、リンカが処理中に遭遇するシンボルの状態を分類するためのものです。enum { Sxxx, // 新しく追加されたシンボルタイプ STEXT = 1, SDATA, SBSS, // ... その他のシンボルタイプ };この
Sxxxは、特定の条件下で「本当に見つからない」と判断されたシンボルに割り当てられることを意図しています。 -
src/cmd/6l/asm.cにおける診断ロジックの追加:asm.c内のdatblk関数は、データブロックの処理中にシンボル参照を解決する役割を担っています。この関数内で、シンボルp->to.symのタイプがSxxxであるかどうかのチェックが追加されました。if(p->to.sym) { if(p->to.sym->type == SUNDEF) ckoff(p->to.sym, o); if(p->to.sym->type == Sxxx) // ここが新しい追加 diag("missing symbol %s", p->to.sym->name); // 診断メッセージを出力 o += p->to.sym->value; if(p->to.sym->type != STEXT && p->to.sym->type != SUNDEF) o += INITDAT;- 既存の
if(p->to.sym->type == SUNDEF)チェックは、未定義だが解決される可能性のあるシンボルに対する処理(ckoffはおそらくオフセットのチェックや調整)です。 - 新しく追加された
if(p->to.sym->type == Sxxx)のブロックは、シンボルがSxxxタイプである場合にdiag関数を呼び出します。diag関数は、Goツールチェイン内でエラーや警告メッセージを出力するための標準的なメカニズムです。これにより、「missing symbol %s」(%sにはシンボル名が入る)という明確なエラーメッセージがユーザーに表示されるようになります。 - この変更により、リンカが
Sxxxタイプのシンボルに遭遇した場合、サイレントに処理を続行するのではなく、ビルドプロセスを停止し、問題の原因を開発者に通知するようになります。
- 既存の
-
src/cmd/6l/go.cの微修正:go.cファイルでは、デバッグ出力の文字列がtypestringsからtypesigsに修正されています。if(debug['v']) Bprint(&bso, "%5.2f typestrings %d\n", cputime(), n); // 変更前 Bprint(&bso, "%5.2f typesigs %d\n", cputime(), n); // 変更後これは機能的な変更ではなく、おそらく用語の統一やタイポ修正といった軽微な修正であり、主要な「サイレントな誤コンパイル」の診断とは直接関係ありませんが、コミットに含まれています。
これらの変更は、Go言語のツールチェインの堅牢性を高め、開発者がより信頼性の高いプログラムを構築できるようにするための重要なステップでした。特に、サイレントなエラーはデバッグコストが非常に高いため、早期に診断メッセージを出すことの価値は計り知れません。
コアとなるコードの変更箇所
src/cmd/6l/asm.c
--- a/src/cmd/6l/asm.c
+++ b/src/cmd/6l/asm.c
@@ -708,6 +708,8 @@ datblk(int32 s, int32 n)
if(p->to.sym) {
if(p->to.sym->type == SUNDEF)
ckoff(p->to.sym, o);
+ if(p->to.sym->type == Sxxx)
+ diag("missing symbol %s", p->to.sym->name);
o += p->to.sym->value;
if(p->to.sym->type != STEXT && p->to.sym->type != SUNDEF)
o += INITDAT;
src/cmd/6l/go.c
--- a/src/cmd/6l/go.c
+++ b/src/cmd/6l/go.c
@@ -487,6 +487,6 @@ definetypesigs(void)
prog->to.offset = n;
if(debug['v'])
- Bprint(&bso, "%5.2f typestrings %d\n", cputime(), n);
+ Bprint(&bso, "%5.2f typesigs %d\n", cputime(), n);
}
src/cmd/6l/l.h
--- a/src/cmd/6l/l.h
+++ b/src/cmd/6l/l.h
@@ -137,6 +137,7 @@ struct Movtab
enum
{
+ Sxxx,
STEXT = 1,
SDATA,
SBSS,
コアとなるコードの解説
src/cmd/6l/l.h の変更
l.hファイルでは、リンカが内部的に使用するシンボルタイプを定義するenumに、Sxxxという新しいエントリが追加されました。
enum
{
Sxxx, // この行が追加された
STEXT = 1,
SDATA,
SBSS,
// ...
};
このSxxxは、他のシンボルタイプ(STEXT:コードセクション、SDATA:初期化済みデータセクション、SBSS:初期化されていないデータセクション、SUNDEF:未定義シンボル)とは異なる、特定の「問題のある未解決シンボル」の状態を示すために導入されました。これにより、リンカはシンボルをより細かく分類し、適切なエラー処理を行うための基盤ができました。
src/cmd/6l/asm.c の変更
asm.cのdatblk関数は、データブロックを処理する際にシンボル参照を解決する部分です。この関数内に、以下の2行が追加されました。
if(p->to.sym->type == Sxxx)
diag("missing symbol %s", p->to.sym->name);
このコードブロックは、p->to.sym(現在処理しているシンボルへのポインタ)が存在する場合に実行されます。
if(p->to.sym->type == Sxxx): これは、現在のシンボルのタイプが新しく定義されたSxxxであるかどうかをチェックします。diag("missing symbol %s", p->to.sym->name);: もしシンボルタイプがSxxxであれば、diag関数が呼び出されます。diagはGoツールチェインの診断メッセージ出力関数であり、指定されたフォーマット文字列と引数(ここではシンボル名)を使ってエラーメッセージを標準エラー出力に表示します。この場合、「missing symbol [シンボル名]」というメッセージが出力され、リンカは通常、この時点で処理を停止し、ビルドを失敗させます。
この変更により、リンカは、以前はサイレントに誤った処理をしていた可能性のある「本当に見つからないシンボル」を明示的に検出し、開発者にその問題を通知できるようになりました。これは、デバッグの労力を大幅に削減し、生成されるバイナリの信頼性を向上させる上で非常に重要です。
src/cmd/6l/go.c の変更
go.cファイルでは、デバッグ出力の文字列が変更されました。
if(debug['v'])
- Bprint(&bso, "%5.2f typestrings %d\n", cputime(), n); // 変更前
+ Bprint(&bso, "%5.2f typesigs %d\n", cputime(), n); // 変更後
これは、typestringsという用語がtypesigsに修正されたものです。typesigsは「型シグネチャ」を意味し、Go言語の型システムにおけるより正確な用語です。この変更は、コードの機能には影響を与えませんが、デバッグメッセージの正確性と一貫性を向上させるためのものです。
関連リンク
- Go言語の初期開発に関する情報: https://go.dev/doc/history
- Go言語のツールチェインの概要: https://go.dev/doc/toolchain
- リンカの基本的な概念 (Wikipedia): https://ja.wikipedia.org/wiki/%E3%83%AA%E3%83%B3%E3%82%AB_(%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF%E3%82%B5%E3%82%A4%E3%82%A8%E3%83%B3%E3%82%B9)
参考にした情報源リンク
- Go言語の公式ドキュメント (History, Toolchain)
- リンカ、コンパイラに関する一般的な知識
- コミットメッセージとコード差分からの直接的な分析
- Rob Pike氏の初期Go言語に関する講演や記事 (一般的な背景知識として)