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

[インデックス 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->nameetext と等しいかどうかをチェックすることです。もし etext であれば、そのシンボルは関数として処理せず、break ステートメントで switch 文を抜けるように変更されました。これにより、etextfunc 配列に追加されるのを防ぎ、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の公式リポジトリの履歴に存在します。

参考にした情報源リンク