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

[インデックス 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言語およびコンパイラ/リンカに関する基本的な知識が必要です。

  1. Go言語のビルドプロセス:

    • Goのソースコードは、まずコンパイラ (go tool compile) によってアセンブリコードに変換されます。
    • 次に、アセンブラ (go tool asm) によってオブジェクトファイル(マシンコード)に変換されます。
    • 最後に、リンカ (go tool link または cmd/ld) によって、複数のオブジェクトファイルやライブラリが結合され、実行可能なバイナリが生成されます。
  2. cmd/ld (Goリンカ):

    • cmd/ld はGo言語の標準リンカであり、Goプログラムのビルドプロセスの最終段階を担います。
    • その主要な役割の一つは、生成されるバイナリにデバッグ情報を埋め込むことです。これには、関数名、変数情報、そして特に重要な「PC-Line (Program Counter-Line Number) テーブル」が含まれます。
  3. PC-Line (Program Counter-Line Number) テーブル:

    • これは、実行可能なマシンコードの各アドレス(プログラムカウンタ、PC)が、対応するソースコードのどのファイル、どの行に由来するかをマッピングするデータ構造です。
    • デバッガは、このテーブルを使用して、プログラムの実行中に現在のPCからソースコード上の位置を特定し、開発者に表示します。
    • このテーブルは、通常、命令の実行フローに基づいて更新されます。新しいソースコードの行に到達したり、特定の種類の命令(例えば、関数の開始を示す命令)に遭遇したりすると、リンカはPC-Line テーブルに新しいエントリを追加します。
  4. NOP (No Operation) 命令:

    • NOP は「No Operation」の略で、CPUが何も実行しない命令です。
    • 通常、アセンブリコードやマシンコードにおいて、命令のアラインメント調整、タイミング調整、または将来のパッチ適用のための一時的なプレースホルダーとして使用されます。
    • NOP 命令はプログラムの実行フローや状態に影響を与えないため、PC-Line テーブルの生成においては、通常、行番号の更新トリガーとはなりません。つまり、NOP 命令をスキップして、次の意味のある命令の行番号を記録します。
  5. GOEXPERIMENT 環境変数:

    • Go言語には、開発中の実験的な機能や、まだ安定版に組み込まれていない新機能を試すためのメカニズムとして GOEXPERIMENT 環境変数があります。
    • この変数を設定することで、特定の実験的な機能がビルド時に有効になります。このコミットで言及されている fieldtrack もその一つです。
  6. fieldtrack (実験的機能):

    • コミットメッセージから推測されるに、fieldtrack はGoの構造体フィールドへのアクセスを追跡するような実験的な機能であると考えられます。
    • このような機能は、コンパイラやリンカが追加の命令(例えば、フィールドアクセスを記録するための命令)を挿入する必要がある場合があります。このコミットでは、その追加命令が USEFIELD として表現されています。
  7. 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 関数は、AUSEFIELDNOP と同様に扱っていませんでした。

具体的には、pclntab 関数内の以下の条件分岐が問題でした。

if(p->line == oldlc || p->as == ATEXT || p->as == ANOP) {
    // ... 行番号の更新をスキップまたは特殊処理 ...
}

この条件は、「現在の命令の行番号が前の命令と同じである場合」(p->line == oldlc)、または「命令が ATEXT(関数の開始)である場合」、または「命令が ANOP(NOP)である場合」に真となります。これらのケースでは、PC-Lineテーブルの更新ロジックが特殊な振る舞いをします。

AUSEFIELD 命令は、p->line == oldlc でない場合でも、ATEXTANOP と同様に、行番号の進行に影響を与えない命令として扱われるべきでした。しかし、上記の条件に 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言語のソースコード (特に src/cmd/ld/lib.c)
  • Go言語のIssueトラッカー (GitHub)
  • Go言語のCode Review (Gerrit)
  • Go言語のコンパイラ/リンカに関する一般的な知識
  • アセンブリ言語におけるNOP命令の概念