[インデックス 16642] ファイルの概要
このコミットは、Go言語のリンカ (cmd/ld
) における、fieldtrack
という実験的な機能を使用した場合の行番号の生成に関するバグ修正を扱っています。具体的には、USEFIELD
という特殊な命令が、プログラムカウンタと行番号のマッピングテーブル (pc-ln table
) の生成時に正しく扱われていなかった問題を解決します。
コミット
commit a14e143c2173e106b1155905a41f5144e1a864b7
Author: Russ Cox <rsc@golang.org>
Date: Tue Jun 25 17:23:33 2013 -0400
cmd/ld: fix line numbers when using fieldtrack
USEFIELD is a special kind of NOP, so treat it like a NOP
when generating the pc-ln table.
There are more invasive fixes that could be applied here.
I am going for minimum number of lines changed.
The smallest test case we know of is five distinct Go files
in four packages, and the bug only happens with
GOEXPERIMENT=fieldtrack enabled, which we don't
normally build with, so the test would never run
meaningfully anyway.
Fixes #5762.
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/10495044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a14e143c2173e106b1155905a41f5144e1a864b7
元コミット内容
cmd/ld: fix line numbers when using fieldtrack
USEFIELD
は特殊な種類の NOP
であるため、pc-ln
テーブルを生成する際に NOP
と同様に扱う。
より侵襲的な修正も可能だが、ここでは変更行数を最小限に抑えることを目指す。
既知の最小のテストケースは、4つのパッケージにまたがる5つの異なるGoファイルで構成されており、このバグは GOEXPERIMENT=fieldtrack
が有効な場合にのみ発生する。この設定は通常ビルドでは使用されないため、テストは意味のある実行をしないだろう。
Fixes #5762.
変更の背景
このコミットは、Go言語のリンカ (cmd/ld
) が、GOEXPERIMENT=fieldtrack
という実験的なビルドフラグが有効な場合に、生成されるバイナリのデバッグ情報(特にプログラムカウンタとソースコードの行番号のマッピング)に誤りが生じるバグを修正するために導入されました。
Goのバイナリには、デバッガがソースコードの行と実行中のマシンコードのアドレスを関連付けるために使用する「PC-Line (Program Counter-Line Number) テーブル」が含まれています。このテーブルが正しく生成されないと、デバッガが誤った行番号を表示したり、ブレークポイントが意図しない場所でヒットしたりするなどの問題が発生します。
問題の根本原因は、fieldtrack
機能が導入する USEFIELD
という特殊な命令が、リンカの行番号生成ロジックにおいて、通常の実行可能な命令とは異なる、しかし行番号の進行に影響を与えない「NOP(No Operation)」のような命令として認識されていなかったことにあります。その結果、USEFIELD
命令が存在する場所で、PC-Line テーブルの更新が適切に行われず、行番号がずれてしまう現象が発生していました。
このバグは、GOEXPERIMENT=fieldtrack
という実験的なフラグが有効な場合にのみ顕在化するため、通常のGoのビルドプロセスでは発見されにくいものでした。しかし、デバッグ情報の正確性は、開発者がプログラムの挙動を理解し、問題を特定する上で非常に重要であるため、この修正はデバッグ体験の向上に寄与します。
前提知識の解説
このコミットを理解するためには、以下のGo言語およびコンパイラ/リンカに関する基本的な知識が必要です。
-
Go言語のビルドプロセス:
- Goのソースコードは、まずコンパイラ (
go tool compile
) によってアセンブリコードに変換されます。 - 次に、アセンブラ (
go tool asm
) によってオブジェクトファイル(マシンコード)に変換されます。 - 最後に、リンカ (
go tool link
またはcmd/ld
) によって、複数のオブジェクトファイルやライブラリが結合され、実行可能なバイナリが生成されます。
- Goのソースコードは、まずコンパイラ (
-
cmd/ld
(Goリンカ):cmd/ld
はGo言語の標準リンカであり、Goプログラムのビルドプロセスの最終段階を担います。- その主要な役割の一つは、生成されるバイナリにデバッグ情報を埋め込むことです。これには、関数名、変数情報、そして特に重要な「PC-Line (Program Counter-Line Number) テーブル」が含まれます。
-
PC-Line (Program Counter-Line Number) テーブル:
- これは、実行可能なマシンコードの各アドレス(プログラムカウンタ、PC)が、対応するソースコードのどのファイル、どの行に由来するかをマッピングするデータ構造です。
- デバッガは、このテーブルを使用して、プログラムの実行中に現在のPCからソースコード上の位置を特定し、開発者に表示します。
- このテーブルは、通常、命令の実行フローに基づいて更新されます。新しいソースコードの行に到達したり、特定の種類の命令(例えば、関数の開始を示す命令)に遭遇したりすると、リンカはPC-Line テーブルに新しいエントリを追加します。
-
NOP
(No Operation) 命令:NOP
は「No Operation」の略で、CPUが何も実行しない命令です。- 通常、アセンブリコードやマシンコードにおいて、命令のアラインメント調整、タイミング調整、または将来のパッチ適用のための一時的なプレースホルダーとして使用されます。
NOP
命令はプログラムの実行フローや状態に影響を与えないため、PC-Line テーブルの生成においては、通常、行番号の更新トリガーとはなりません。つまり、NOP
命令をスキップして、次の意味のある命令の行番号を記録します。
-
GOEXPERIMENT
環境変数:- Go言語には、開発中の実験的な機能や、まだ安定版に組み込まれていない新機能を試すためのメカニズムとして
GOEXPERIMENT
環境変数があります。 - この変数を設定することで、特定の実験的な機能がビルド時に有効になります。このコミットで言及されている
fieldtrack
もその一つです。
- Go言語には、開発中の実験的な機能や、まだ安定版に組み込まれていない新機能を試すためのメカニズムとして
-
fieldtrack
(実験的機能):- コミットメッセージから推測されるに、
fieldtrack
はGoの構造体フィールドへのアクセスを追跡するような実験的な機能であると考えられます。 - このような機能は、コンパイラやリンカが追加の命令(例えば、フィールドアクセスを記録するための命令)を挿入する必要がある場合があります。このコミットでは、その追加命令が
USEFIELD
として表現されています。
- コミットメッセージから推測されるに、
-
USEFIELD
命令:fieldtrack
機能によって導入される特殊な命令(または擬似命令)です。- コミットメッセージによると、これは「特殊な種類の
NOP
」として扱われるべき命令です。つまり、プログラムの実行には直接的な影響を与えないが、リンカがPC-Line テーブルを生成する際には、その存在を考慮する必要がある命令です。
技術的詳細
このバグは、Goリンカの pclntab
関数(src/cmd/ld/lib.c
内)に存在していました。この関数は、Goバイナリに埋め込まれるPC-Lineテーブルを生成する役割を担っています。
PC-Lineテーブルの生成ロジックでは、リンカは生成されるマシンコードの命令を順に走査し、ソースコードの行番号が変更された場合や、特定の種類の命令(例えば、関数の開始を示す ATEXT
や、意味のない ANOP
)に遭遇した場合に、PC-Lineテーブルのエントリを更新します。
問題は、GOEXPERIMENT=fieldtrack
が有効な場合に、コンパイラが生成するアセンブリコードに AUSEFIELD
という新しい命令(または擬似命令)が挿入されることにありました。この AUSEFIELD
命令は、その性質上、プログラムの実行フローには影響を与えない「NOP」のような振る舞いをします。しかし、リンカの pclntab
関数は、AUSEFIELD
を NOP
と同様に扱っていませんでした。
具体的には、pclntab
関数内の以下の条件分岐が問題でした。
if(p->line == oldlc || p->as == ATEXT || p->as == ANOP) {
// ... 行番号の更新をスキップまたは特殊処理 ...
}
この条件は、「現在の命令の行番号が前の命令と同じである場合」(p->line == oldlc
)、または「命令が ATEXT
(関数の開始)である場合」、または「命令が ANOP
(NOP)である場合」に真となります。これらのケースでは、PC-Lineテーブルの更新ロジックが特殊な振る舞いをします。
AUSEFIELD
命令は、p->line == oldlc
でない場合でも、ATEXT
や ANOP
と同様に、行番号の進行に影響を与えない命令として扱われるべきでした。しかし、上記の条件に p->as == AUSEFIELD
が含まれていなかったため、リンカは AUSEFIELD
を通常の実行可能な命令として扱い、その場所で不適切なPC-Lineテーブルのエントリを生成しようとしました。これにより、後続の命令の行番号がずれてしまうというバグが発生しました。
このコミットは、この条件に p->as == AUSEFIELD
を追加することで、AUSEFIELD
命令が NOP
と同様に、行番号の進行に影響を与えない命令として正しく扱われるように修正しました。これにより、fieldtrack
が有効な場合でも、PC-Lineテーブルが正確に生成され、デバッグ情報が正しくなります。
コミットメッセージにある「より侵襲的な修正」とは、おそらく AUSEFIELD
をリンカの命令セット定義において、より明示的に NOP
の一種として分類したり、fieldtrack
の実装自体を見直したりするような変更を指していると考えられます。しかし、このコミットでは、影響範囲を最小限に抑えるために、pclntab
関数内の条件分岐を単純に拡張するという、最も直接的で影響の少ない修正が選択されました。
コアとなるコードの変更箇所
変更は src/cmd/ld/lib.c
ファイルの pclntab
関数内の一行です。
--- a/src/cmd/ld/lib.c
+++ b/src/cmd/ld/lib.c
@@ -1355,7 +1355,7 @@ pclntab(void)
oldlc = 0;
for(cursym = textp; cursym != nil; cursym = cursym->next) {
for(p = cursym->text; p != P; p = p->link) {
- if(p->line == oldlc || p->as == ATEXT || p->as == ANOP) {
+ if(p->line == oldlc || p->as == ATEXT || p->as == ANOP || p->as == AUSEFIELD) {
if(debug['O'])
Bprint(&bso, "%6llux %P\n",
(vlong)p->pc, p);
コアとなるコードの解説
変更された行は、pclntab
関数内のループの中にあります。このループは、Goプログラムのテキストセクション(実行可能なコード)を構成する各命令 (p
) を順に処理し、PC-Lineテーブルを構築しています。
元のコード:
if(p->line == oldlc || p->as == ATEXT || p->as == ANOP) {
この if
文は、現在の命令 p
が、PC-Lineテーブルに新しいエントリを追加する必要があるかどうか、または既存のエントリを更新する必要があるかどうかを判断するための条件です。
p->line == oldlc
: 現在の命令のソースコード行番号が、直前の命令の行番号と同じである場合。この場合、通常は新しいPC-Lineエントリは不要です。p->as == ATEXT
: 命令がATEXT
である場合。これはGoのアセンブリにおける関数の開始を示す命令であり、新しい関数の開始点としてPC-Lineテーブルに記録されるべき特殊な命令です。p->as == ANOP
: 命令がANOP
である場合。これは「No Operation」命令であり、プログラムの実行には影響を与えません。PC-Lineテーブルの生成においては、この命令はスキップされ、行番号の進行には影響を与えないものとして扱われます。
修正後のコード:
if(p->line == oldlc || p->as == ATEXT || p->as == ANOP || p->as == AUSEFIELD) {
この修正では、既存の条件に || p->as == AUSEFIELD
が追加されました。
これにより、AUSEFIELD
命令も ANOP
と同様に、PC-Lineテーブルの生成において特殊な扱いを受けるようになりました。つまり、AUSEFIELD
命令に遭遇しても、リンカは行番号の進行を妨げず、デバッグ情報の正確性を保つことができるようになりました。
この変更は、AUSEFIELD
が実質的に NOP
の一種であるというコミットメッセージの主張をコードレベルで反映したものです。これにより、fieldtrack
が有効な場合でも、リンカが生成するデバッグ情報が正確になり、デバッガがソースコードの行番号を正しく表示できるようになります。
関連リンク
- Go Issue #5762: https://github.com/golang/go/issues/5762
- Go CL 10495044: https://golang.org/cl/10495044
参考にした情報源リンク
- Go言語のソースコード (特に
src/cmd/ld/lib.c
) - Go言語のIssueトラッカー (GitHub)
- Go言語のCode Review (Gerrit)
- Go言語のコンパイラ/リンカに関する一般的な知識
- アセンブリ言語におけるNOP命令の概念