[インデックス 18585] ファイルの概要
このコミットは、Go言語のリンカ(cmd/ld
)におけるDWARFデバッグ情報の生成に関するバグ修正です。具体的には、.debug_line
セクションのデータ生成において発生していたオフバイワンエラーを修正しています。
コミット
commit 15ec569ba952d47e6e3c96705c9ec7888fe90877
Author: Josh Bleecher Snyder <josharian@gmail.com>
Date: Thu Feb 20 09:06:32 2014 -0800
cmd/ld: fix off-by-one error in DWARF .debug_line transcription
The liblink refactor changed the DWARF .debug_line flow control. The mapping was off by one pcline entry. The fix here preserves pc until it can be compared to pcline.pc.
Sample dwarfdump .debug_line output for main.main from the program in issue 7351, before liblink (correct):
0x0000003c: 00 Extended: <9> 02 DW_LNE_set_address( 0x0000000000002000 )
0x00000047: 03 DW_LNS_advance_line( 6 )
0x00000049: 01 DW_LNS_copy
0x0000000000002000 1 7 0 is_stmt
0x0000004a: 8b address += 21, line += 1
0x0000000000002021 1 8 0 is_stmt
0x0000004b: 02 DW_LNS_advance_pc( 153 )
0x0000004e: 03 DW_LNS_advance_line( 1 )
0x00000050: 01 DW_LNS_copy
0x00000000000020ba 1 9 0 is_stmt
After liblink (off by one entry):
0x00001bbf: 00 Extended: <9> 02 DW_LNE_set_address( 0x0000000000002000 )
0x00001bca: 02 DW_LNS_advance_pc( 33 )
0x00001bcc: 03 DW_LNS_advance_line( 6 )
0x00001bce: 01 DW_LNS_copy
0x0000000000002021 1 7 0 is_stmt
0x00001bcf: 02 DW_LNS_advance_pc( 153 )
0x00001bd2: 03 DW_LNS_advance_line( 1 )
0x00001bd4: 01 DW_LNS_copy
0x00000000000020ba 1 8 0 is_stmt
0x00001bd5: 02 DW_LNS_advance_pc( 153 )
0x00001bd8: 03 DW_LNS_advance_line( 1 )\n 0x00001bda: 01 DW_LNS_copy
0x0000000000002153 1 9 0 is_stmt
After this CL (the line 9 pc offset changed due to intervening compiler changes):
0x00001d07: 00 Extended: <9> 02 DW_LNE_set_address( 0x0000000000002000 )
0x00001d12: 03 DW_LNS_advance_line( 6 )
0x00001d14: 01 DW_LNS_copy
0x0000000000002000 1 7 0 is_stmt
0x00001d15: 8b address += 21, line += 1
0x0000000000002021 1 8 0 is_stmt
0x00001d16: 02 DW_LNS_advance_pc( 189 )
0x00001d19: 03 DW_LNS_advance_line( 1 )
0x00001d1b: 01 DW_LNS_copy
0x00000000000020de 1 9 0 is_stmt
Fixes #7351.
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/66290043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/15ec569ba952d47e6e3c96705c9ec7888fe90877
元コミット内容
このコミットは、Goリンカ(cmd/ld
)におけるDWARFデバッグ情報の.debug_line
セクションの転記(transcription)におけるオフバイワンエラーを修正するものです。以前のliblink
のリファクタリングによって、DWARFの.debug_line
のフロー制御が変更され、その結果、pcline
エントリのマッピングが1つずれていました。この修正は、pc
(プログラムカウンタ)の値をpcline.pc
と比較できるまで保持することで、この問題を解決します。
コミットメッセージには、dwarfdump
ツールによる.debug_line
の出力例が示されており、liblink
リファクタリング前(正しい出力)、liblink
リファクタリング後(オフバイワンエラーのある出力)、そしてこのコミット適用後(修正された出力)の比較がなされています。これにより、問題がどのように現れ、どのように修正されたかが視覚的に示されています。
変更の背景
この変更の背景には、Goリンカの内部構造に対する大規模なリファクタリングであるliblink
の導入があります。liblink
は、Goのツールチェインにおけるリンカの共通部分を抽象化し、コードの再利用性と保守性を向上させることを目的としていました。しかし、このリファクタリングの過程で、DWARFデバッグ情報の生成ロジック、特にプログラムカウンタ(PC)とソースコードの行番号のマッピングを扱う.debug_line
セクションの処理に意図しない変更が加えられました。
具体的には、liblink
の変更により、リンカが.debug_line
テーブルを構築する際の内部的なPCの進め方と、pcline
(PC-lineテーブル)のエントリの処理順序にずれが生じました。これにより、デバッガがソースコードの行と実行中のマシンコードのアドレスを正確にマッピングできなくなり、デバッグ体験に悪影響を及ぼしていました。この問題は、Go issue #7351として報告され、このコミットはその問題を解決するために作成されました。
前提知識の解説
このコミットを理解するためには、以下の前提知識が必要です。
-
DWARF (Debugging With Attributed Record Formats): DWARFは、ソースレベルデバッガがプログラムの実行状態を理解するために必要な情報を提供する標準的なデバッグファイル形式です。コンパイルされたバイナリに埋め込まれるか、別のファイルとして提供されます。DWARFは、変数名、型情報、関数定義、ソースコードの行番号と実行可能コードのアドレスのマッピングなど、多岐にわたるデバッグ情報を格納します。
-
.debug_line セクション: DWARF情報の中で、
.debug_line
セクションは特に重要です。これは、コンパイルされたバイナリの特定のマシンコードアドレスが、元のソースファイルのどの行番号に対応するかをマッピングする情報を含んでいます。デバッガがブレークポイントを設定したり、ステップ実行したりする際に、この情報を用いてユーザーにソースコードの正確な位置を表示します。.debug_line
テーブルは、一連のステートメント(命令)で構成されるステートマシンとして機能します。これらのステートメントは、プログラムカウンタ(PC)の進捗とソースコードの行番号の変化を記述します。主なステートメントには以下のようなものがあります。DW_LNE_set_address
: 現在のPCアドレスを設定します。DW_LNS_advance_line
: 現在の行番号を進めます。DW_LNS_advance_pc
: 現在のPCアドレスを進めます。DW_LNS_copy
: 現在のPCと行番号のペアをテーブルに記録します。- 特殊なオペコード:
address += delta
,line += delta
のように、PCと行番号を同時に進める効率的なオペコード。
-
Goリンカ (
cmd/ld
): Go言語のビルドプロセスにおいて、リンカ(cmd/ld
)は、コンパイルされたオブジェクトファイル(.o
ファイル)を結合し、実行可能なバイナリを生成する役割を担います。この過程で、デバッグ情報もオブジェクトファイルから収集され、最終的なバイナリのDWARFセクションにまとめられます。 -
liblink
: Go 1.2のリリースサイクルで導入されたliblink
は、Goのリンカの内部構造を再設計したものです。それ以前は、各アーキテクチャ(amd64, armなど)ごとにリンカのコードが重複していましたが、liblink
は共通のリンキングロジックを共有ライブラリとして提供することで、コードベースの簡素化と保守性の向上を図りました。このリファクタリングは、リンカの内部動作、特にPCと行番号のマッピング処理に影響を与えました。 -
pciterinit
,pciternext
: これらはGoリンカ内部でPC-lineテーブル(pcln
)をイテレートするためのユーティリティ関数です。pcln
は、プログラムカウンタ(PC)とソースファイル、行番号のマッピングを効率的に格納するためのデータ構造です。pciterinit
はイテレータを初期化し、pciternext
は次のPC-lineエントリに進みます。
技術的詳細
このバグは、liblink
リファクタリング後のcmd/ld/dwarf.c
内のwritelines
関数で発生していました。この関数は、DWARFの.debug_line
セクションを生成する主要なロジックを含んでいます。
問題の核心は、pc
(現在のプログラムカウンタ)とepc
(次のエントリのプログラムカウンタ)の更新ロジックにありました。writelines
関数は、pcfile
(PC-ファイルマッピング)とpcline
(PC-行マッピング)という2つのイテレータを使用して、PCの範囲とそれに対応するファイルおよび行番号の情報を処理します。
修正前のコードでは、ループの冒頭でpc
がpcfile.nextpc
またはpcline.nextpc
と比較され、適切なイテレータが進められていました。しかし、putpclcdelta
関数が呼び出される前にpc
がepc
に更新されてしまうため、putpclcdelta
に渡されるpc
の値が、本来比較されるべきpcline.pc
との差を計算する際に、すでに次のPCに更新されてしまっていました。これにより、pcline
エントリのマッピングが1つずれる「オフバイワン」エラーが発生していました。
具体的には、putpclcdelta(epc - pc, pcline.value - line)
という行で、epc - pc
がPCの差分を計算していましたが、このpc
が既に更新された値であるため、正しい差分が計算されませんでした。
修正は、このpc
の更新タイミングを変更することにあります。
- ループの開始時に、
epc = pc;
として、現在のpc
の値をepc
に退避させます。これにより、pcfile.nextpc
やpcline.nextpc
との比較には、まだ更新されていないpc
の値が使われるようになります。 putpclcdelta
の呼び出しをputpclcdelta(s->value + pcline.pc - pc, pcline.value - line);
に変更します。ここで、s->value + pcline.pc
は、現在のpcline
エントリが指す絶対PCアドレスを表します。この値から、まだ更新されていないpc
を引くことで、正確なPCの差分を計算できるようになります。pc = epc;
という行をputpclcdelta
の呼び出しの後に移動させます。これにより、pc
の更新が、必要な計算がすべて行われた後に行われるようになります。
この変更により、dwarfdump
の出力例で示されているように、DW_LNS_advance_pc
のオペランドが正しくなり、PCと行番号の正確なマッピングが復元されました。
コアとなるコードの変更箇所
変更はsrc/cmd/ld/dwarf.c
ファイルに集中しています。
--- a/src/cmd/ld/dwarf.c
+++ b/src/cmd/ld/dwarf.c
@@ -1590,29 +1590,30 @@ writelines(void)
pciterinit(&pcfile, &s->pcln->pcfile);
pciterinit(&pcline, &s->pcln->pcline);
+ epc = pc; // 追加: 現在のpcをepcに退避
while(!pcfile.done && !pcline.done) {
- if(pc - s->value >= pcfile.nextpc) {
+ if(epc - s->value >= pcfile.nextpc) { // 変更: pcの代わりにepcを使用
pciternext(&pcfile);
continue;
}
- if(pc - s->value >= pcline.nextpc) {
+ if(epc - s->value >= pcline.nextpc) { // 変更: pcの代わりにepcを使用
pciternext(&pcline);
continue;
}
- if(pcfile.nextpc < pcline.nextpc)
- epc = pcfile.nextpc;
- else
- epc = pcline.nextpc;
- epc += s->value;
-
if(file != pcfile.value) {
cput(DW_LNS_set_file);
uleb128put(pcfile.value);
file = pcfile.value;
}
- putpclcdelta(epc - pc, pcline.value - line);
+ // 変更: putpclcdeltaの引数を修正し、pcの更新を後回しにする
+ putpclcdelta(s->value + pcline.pc - pc, pcline.value - line);
+
pc = epc;
+ // 移動: epcの計算をpc更新後に行う
+ if(pcfile.nextpc < pcline.nextpc)
+ epc = pcfile.nextpc;
+ else
+ epc = pcline.nextpc;
+ epc += s->value;
line = pcline.value;
}
コアとなるコードの解説
この修正の核心は、writelines
関数内のpc
とepc
という2つのプログラムカウンタ変数の役割と更新タイミングの調整にあります。
pc
: これは、現在処理中のDWARF.debug_line
エントリが参照するプログラムカウンタの「開始」アドレスを表します。つまり、前の.debug_line
ステートメントが適用された後のPCの値です。epc
: これは、次に処理すべきPC-lineエントリの「終了」アドレス、または次のファイル/行情報が適用されるべきプログラムカウンタの「次の」アドレスを表します。
修正前は、ループの冒頭でepc
が計算され、そのepc
とpc
の差分がputpclcdelta
に渡されていました。しかし、このpc
は、putpclcdelta
が呼び出される前に、既に次のPC値に更新されてしまっている可能性がありました。
修正後のロジックは以下のようになります。
epc = pc;
の追加: ループの各イテレーションの開始時に、現在のpc
の値をepc
にコピーします。これにより、pcfile.nextpc
やpcline.nextpc
との比較に使用されるepc
は、そのイテレーションの開始時点での正確なPC値(つまり、まだ進んでいないPC値)を反映するようになります。if(epc - s->value >= pcfile.nextpc)
とif(epc - s->value >= pcline.nextpc)
への変更: ここで、pc
の代わりにepc
を使用することで、イテレータを進めるべきかどうかの判断が、現在のPCの正確な位置に基づいて行われるようになります。putpclcdelta(s->value + pcline.pc - pc, pcline.value - line);
への変更:s->value + pcline.pc
: これは、現在のpcline
エントリが指す絶対的なプログラムカウンタアドレスです。s->value
はセクションのベースアドレスのようなオフセットで、pcline.pc
はpcln
テーブル内の相対PCです。pc
: これは、putpclcdelta
が呼び出される時点での、まだ更新されていない(つまり、前の.debug_line
ステートメントが適用された後の)pc
の値です。- この差分を計算することで、現在の
.debug_line
ステートメントがカバーすべきPCの正確な範囲が決定されます。
pc = epc;
の移動とepc
の再計算:putpclcdelta
の呼び出し後、pc
をepc
の値に更新します。そして、次のイテレーションのために、pcfile.nextpc
とpcline.nextpc
のどちらが小さいかに基づいて、新しいepc
の値を計算します。これにより、pc
とepc
の間の関係が、DWARFの.debug_line
ステートマシンの期待する動作と一致するようになります。
この修正により、リンカはPCと行番号の正確なマッピングを生成できるようになり、デバッガがGoプログラムの実行をソースコードレベルで正しく追跡できるようになりました。
関連リンク
- Go issue #7351: https://github.com/golang/go/issues/7351
- Go CL 66290043: https://golang.org/cl/66290043
参考にした情報源リンク
- DWARF Debugging Information Format Standard: https://dwarfstd.org/ (特にSection 6: Line Number Information)
- Go言語のリンカのソースコード (
src/cmd/ld/
) - Go言語のツールチェインに関するドキュメントやブログ記事 (特に
liblink
に関するもの) dwarfdump
ツールに関するドキュメント