[インデックス 1345] ファイルの概要
このコミットは、Go言語のランタイムにおけるシンボルテーブル処理に関連する src/runtime/symtab.c
ファイルに対する変更です。具体的には、ソースファイルと関数を関連付ける際のオフバイワンエラーを修正しています。
コミット
commit 7df571aef71eaf5cd1e52ed74b7226a71ca20866
Author: Russ Cox <rsc@golang.org>
Date: Mon Dec 15 10:50:41 2008 -0800
off-by-one error assigning src files to functions
R=r
DELTA=2 (2 added, 0 deleted, 0 changed)
OCL=21178
CL=21187
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/7df571aef71eaf5cd1e52ed74b7226a71ca20866
元コミット内容
off-by-one error assigning src files to functions
R=r
DELTA=2 (2 added, 0 deleted, 0 changed)
OCL=21178
CL=21187
変更の背景
このコミットは、Go言語の初期開発段階(2008年)におけるランタイムのバグ修正です。当時、GoのランタイムはC言語で記述されており、プログラムのシンボル情報(関数名、変数名など)を管理する symtab.c
ファイルが存在しました。
問題は、ソースファイル情報を関数に割り当てる dosrcline
関数において、オフバイワンエラーが発生していたことです。特に、プログラムの実行可能コードセグメントの終端を示す etext
シンボルが誤って関数として扱われ、その結果、後続の関数へのソースファイル情報の割り当てがずれてしまう可能性がありました。
etext
は、プログラムのコード領域の終わりを示す特殊なシンボルであり、通常の関数とは異なる性質を持っています。これを通常の関数として処理しようとすると、シンボルテーブルのインデックス計算や、ソースファイルと関数のマッピングにおいて予期せぬずれが生じ、デバッグ情報やスタックトレースの正確性に影響を与える可能性がありました。このコミットは、この etext
シンボルに対する特別な処理を追加することで、このオフバイワンエラーを解消することを目的としています。
前提知識の解説
シンボルテーブル (Symbol Table)
シンボルテーブルは、コンパイラやリンカがプログラム内の識別子(変数名、関数名、クラス名など)とその属性(型、スコープ、アドレスなど)を管理するために使用するデータ構造です。実行時には、デバッガなどがシンボルテーブルを利用して、実行中のプログラムのメモリ上のアドレスとソースコード上のシンボルを対応付け、デバッグ情報を提供します。
Goランタイム (Go Runtime)
Goランタイムは、Goプログラムの実行を管理する低レベルのシステムです。これには、ガベージコレクション、スケジューリング、スタック管理、I/O処理などが含まれます。Goの初期段階では、ランタイムの一部はC言語で記述されており、symtab.c
のようなファイルがその一部でした。
etext
シンボル
etext
は、Unix系システムにおける慣習的なシンボルで、プログラムの「テキストセグメント」(実行可能コード)の終端を示すアドレスを指します。リンカによって設定される特殊なシンボルであり、プログラムのコード領域の境界を特定するために使用されます。Goランタイムでは、スタックのアンワインドやガベージコレクションなど、メモリレイアウトに依存する内部操作でこの etext
の値が利用されることがあります。
オフバイワンエラー (Off-by-one Error)
オフバイワンエラーは、プログラミングにおいて、ループの反復回数や配列のインデックス計算などで、期待される値よりも1つ多い、または少ない値が使用されることによって発生する一般的なバグです。これにより、配列の境界外アクセスや、処理の漏れ・重複などが引き起こされます。
技術的詳細
このコミットで修正された dosrcline
関数は、Goランタイムがプログラムのシンボル情報を処理し、ソースファイルと関数を関連付ける役割を担っていました。この関数は、シンボルテーブルを走査し、各シンボルが関数であるかどうかを判断し、その関数に適切なソースファイル情報を割り当てていました。
問題は、etext
シンボルが通常の関数シンボルと同様に処理されていた点にありました。etext
はコードの終端を示すマーカーであり、実際の実行可能な関数ではありません。しかし、dosrcline
関数内で sym->symtype
が 't'
または 'T'
(テキストセグメント内のシンボル、つまり関数やコードラベル) である場合に、無条件に func
配列に新しい関数エントリを追加し、nf
(関数カウンタ) をインクリメントしていました。
これにより、etext
が関数としてカウントされ、func
配列のインデックスが1つずれてしまうオフバイワンエラーが発生していました。このずれは、etext
の後に続く実際の関数に対して、誤ったソースファイル情報が割り当てられる原因となっていました。
修正は、dosrcline
関数内で sym->symtype
が 't'
または 'T'
である場合に、まず sym->name
が etext
と等しいかどうかをチェックすることです。もし etext
であれば、そのシンボルは関数として処理せず、break
ステートメントで switch
文を抜けるように変更されました。これにより、etext
が func
配列に追加されるのを防ぎ、nf
のインクリメントも行われないため、後続の関数のソースファイル割り当てが正しく行われるようになります。
コアとなるコードの変更箇所
--- a/src/runtime/symtab.c
+++ b/src/runtime/symtab.c
@@ -195,6 +195,8 @@ dosrcline(Sym *sym)
switch(sym->symtype) {
case 't':
case 'T':
+ if(strcmp(sym->name, (byte*)"etext") == 0)
+ break;
f = &func[nf++];
f->src = srcstring;
f->ln0 += lno;
コアとなるコードの解説
変更は src/runtime/symtab.c
ファイルの dosrcline
関数内で行われています。
switch(sym->symtype) {
case 't':
case 'T':
// 追加された行: etext シンボルであるかどうかのチェック
if(strcmp(sym->name, (byte*)"etext") == 0)
break; // etext であれば、このシンボルを関数として処理せず、switch文を抜ける
f = &func[nf++]; // 新しい関数エントリを割り当て、関数カウンタをインクリメント
f->src = srcstring;
f->ln0 += lno;
このコードスニペットは、シンボル sym
のタイプが 't'
または 'T'
(テキストセグメント内のシンボル、つまり関数やコードラベル) である場合の処理を示しています。
追加された if(strcmp(sym->name, (byte*)"etext") == 0)
の行は、現在のシンボルの名前が etext
であるかどうかをチェックしています。strcmp
関数は2つの文字列を比較し、等しければ0を返します。
もしシンボルが etext
であった場合、break;
ステートメントが実行されます。これにより、switch
文の現在の case
ブロックの残りの処理(つまり、f = &func[nf++];
以下の行)がスキップされ、etext
が通常の関数として func
配列に追加されるのを防ぎます。
この修正により、nf
(関数カウンタ) は etext
シンボルによって誤ってインクリメントされることがなくなり、結果として、後続の実際の関数に対して正しいソースファイル情報が割り当てられるようになります。これは、シンボルテーブルの整合性を保ち、デバッグ情報の正確性を確保するために重要な修正です。
関連リンク
- Go言語の初期のランタイムに関する議論やドキュメントは、当時のGoのメーリングリストやGoの公式リポジトリの履歴に存在します。