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

[インデックス 19508] ファイルの概要

このコミットは、Go言語のリンカ (cmd/ld) におけるDWARFデバッグ情報の行番号テーブルのPC (Program Counter) デルタの計算に関するバグ修正です。具体的には、putpclcdelta 関数がDWARF行番号のPCを正しく設定した後、次のデルタ計算の基準となるローカル変数 pc の更新が誤っていたために発生する問題に対処しています。

コミット

commit 507afa68c5a3ce37a824288d19d8b8b0918a1530
Author: Ian Lance Taylor <iant@golang.org>
Date:   Tue Jun 10 14:11:39 2014 -0700

    cmd/ld: fix PC deltas in DWARF line number table

    The putpclcdelta function set the DWARF line number PC to
    s->value + pcline->pc, which is correct, but the code then set
    the local variable pc to epc, which can be a different value.
    This caused the next delta in the DWARF table to be wrong.

    Fixes #8098.

    LGTM=rsc
    R=rsc
    CC=golang-codereviews
    https://golang.org/cl/104950045

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/507afa68c5a3ce37a824288d19d8b8b0918a1530

元コミット内容

cmd/ld: fix PC deltas in DWARF line number table

putpclcdelta 関数はDWARF行番号のPCを s->value + pcline->pc に設定しますが、これは正しいです。しかし、その後のコードでローカル変数 pcepc に設定されていました。epc は異なる値である可能性があり、これがDWARFテーブルにおける次のデルタを誤ったものにしていました。

Issue #8098 を修正します。

変更の背景

このコミットは、Goプログラムのデバッグ情報に含まれるDWARF行番号テーブルの生成におけるバグを修正するために行われました。DWARF (Debugging With Attributed Record Formats) は、コンパイルされたプログラムのソースコードレベルでのデバッグを可能にするための標準的なデバッグ情報フォーマットです。その中でも行番号テーブルは、実行中のプログラムカウンタ (PC) の値と、対応するソースコードのファイル名および行番号をマッピングするために非常に重要です。

報告されたバグ (#8098) は、Goリンカ (cmd/ld) がDWARF行番号テーブルを生成する際に、PCのデルタ(差分)計算に誤りがあるというものでした。この誤りにより、デバッガがプログラムの実行位置をソースコード上の正しい行にマッピングできなくなり、デバッグ体験が損なわれる可能性がありました。特に、ステップ実行やブレークポイントの設定において、デバッガが誤った行にジャンプしたり、情報が表示されなかったりする問題を引き起こすことが考えられます。

前提知識の解説

DWARF (Debugging With Attributed Record Formats)

DWARFは、コンパイラやリンカによって生成される実行可能ファイルに埋め込まれるデバッグ情報の標準フォーマットです。主にUnix系システムで広く利用されており、GDBなどのデバッガがこの情報を読み取って、ソースコードレベルでのデバッグ機能(変数検査、スタックトレース、ステップ実行など)を提供します。

DWARFの主要な構成要素には以下のようなものがあります。

  • コンパイル単位 (Compilation Unit): ソースファイルに対応し、そのファイル内で定義された関数、変数、型などの情報を含みます。
  • デバッグ情報エントリ (DIE: Debugging Information Entry): プログラムの各要素(関数、変数、型、ブロックなど)に関する詳細な情報を提供します。
  • 属性 (Attributes): DIEに付随するキーと値のペアで、要素の特性(名前、型、メモリ位置、スコープなど)を記述します。
  • ロケーションリスト (Location Lists): 変数などのメモリ位置が実行中に変化する場合に、その変化を記述します。
  • 行番号テーブル (Line Number Table): 最も重要な要素の一つで、コンパイルされた機械語命令のアドレス(PC)と、対応するソースコードのファイル名、行番号、列番号をマッピングします。これにより、デバッガは実行中のPCからソースコード上の正確な位置を特定できます。

DWARF 行番号テーブルの構造とデルタエンコーディング

DWARFの行番号テーブルは、通常、ステートマシンとして動作し、命令アドレスとソースコード行のマッピングを効率的に表現します。テーブル内の各エントリは、前のエントリからの「デルタ」(差分)として表現されることが多いです。これにより、絶対アドレスや行番号を毎回記述するよりも、データサイズを大幅に削減できます。

  • PCデルタ: 前の命令アドレスからの差分。
  • 行番号デルタ: 前の行番号からの差分。

デバッガは、これらのデルタを累積的に適用することで、現在のPCに対応する正確なソースコードの行番号を計算します。このデルタ計算が誤っていると、デバッガは正しいソースコード行を特定できなくなります。

Goリンカ (cmd/ld)

Go言語のビルドプロセスにおいて、cmd/ld はGoコンパイラ (cmd/compile) によって生成されたオブジェクトファイル(.o ファイル)を結合し、最終的な実行可能ファイルを生成する役割を担うリンカです。リンカは、複数のオブジェクトファイルを結合するだけでなく、標準ライブラリのコードをリンクしたり、デバッグ情報を埋め込んだり、シンボル解決を行ったりします。このコミットが修正しているのは、このリンカがDWARFデバッグ情報を生成する部分です。

PC (Program Counter)

プログラムカウンタ (PC) は、CPUが次に実行する命令のアドレスを保持するレジスタです。デバッグにおいては、このPCの値が現在の実行位置を示し、デバッガはPCの値に基づいてソースコードの対応する行をハイライトします。

技術的詳細

このバグは、src/cmd/ld/dwarf.c ファイル内の writelines 関数で発生していました。この関数は、DWARF行番号テーブルを生成する主要なロジックを含んでいます。

問題の核心は、putpclcdelta 関数がPCと行番号のデルタを計算して出力した後、次のデルタ計算の基準となる pc 変数(現在の命令アドレスを追跡するローカル変数)の更新方法にありました。

元のコードでは、putpclcdelta が呼び出された後、pc 変数は epc (end PC) の値に設定されていました。

putpclcdelta(s->value + pcline.pc - pc, pcline.value - line);

pc = epc; // ここが問題

ここで、s->value + pcline.pc は、現在の行番号エントリが指す実際のPCアドレスです。putpclcdelta はこの値と、前回のPC (pc 変数) との差分を計算して出力します。しかし、その直後に pc 変数を epc に設定していました。

epc は、現在の行番号エントリがカバーするアドレス範囲の「終端」を示すPCであり、必ずしも s->value + pcline.pc と同じではありません。特に、ある行が複数の命令にまたがる場合、pcline.pc はその行の開始アドレスを示し、epc はその行の命令が終了するアドレスを示す可能性があります。

したがって、pc = epc; とすることで、次のデルタ計算の基準となる pc が、実際にデルタが計算されたPC (s->value + pcline.pc) ではなく、異なる値 (epc) に設定されてしまいました。これにより、次の putpclcdelta の呼び出しで計算されるPCデルタが誤ったものとなり、その後のすべてのデルタ計算に誤差が累積していく結果となりました。

また、line 変数(現在の行番号を追跡するローカル変数)の更新も、epc の更新後に行われており、これもタイミングが適切ではありませんでした。

コアとなるコードの変更箇所

--- a/src/cmd/ld/dwarf.c
+++ b/src/cmd/ld/dwarf.c
@@ -1624,13 +1624,13 @@ writelines(void)\n \t\t\t}\n \t\t\tputpclcdelta(s->value + pcline.pc - pc, pcline.value - line);\n \n-\t\t\tpc = epc;\n+\t\t\tpc = s->value + pcline.pc;\n+\t\t\tline = pcline.value;\n \t\t\tif(pcfile.nextpc < pcline.nextpc)\n \t\t\t\tepc = pcfile.nextpc;\n \t\t\telse\n \t\t\t\tepc = pcline.nextpc;\n \t\t\tepc += s->value;\
-\t\t\tline = pcline.value;\
 \t\t}\n \n \t\tda = 0;\

コアとなるコードの解説

変更は writelines 関数内の以下の2行に集約されます。

  1. pc 変数の更新の修正:

    • 変更前: pc = epc;
    • 変更後: pc = s->value + pcline.pc;

    この変更により、pc 変数は、putpclcdelta 関数が実際にデルタを計算するために使用したPCアドレス (s->value + pcline.pc) に正確に更新されるようになりました。これにより、次の行番号エントリのPCデルタが、前のエントリの正確な終了PCからの差分として計算されることが保証され、デルタの累積誤差が解消されます。

  2. line 変数の更新位置の修正:

    • 変更前: line = pcline.value; (epc += s->value; の後)
    • 変更後: line = pcline.value; (pc = s->value + pcline.pc; の直後)

    line 変数の更新が pc 変数の更新と同時に行われるように移動されました。これにより、pcline の両方が、現在の行番号エントリの正確な情報に同期して更新され、次のデルタ計算の基準が正しく設定されるようになります。元の位置では、epc の計算後に line が更新されており、pc の更新との間に不整合が生じる可能性がありました。

これらの修正により、DWARF行番号テーブルのPCデルタが正確に計算されるようになり、デバッガがGoプログラムの実行位置をソースコード上の正しい行に正確にマッピングできるようになりました。

関連リンク

参考にした情報源リンク

  • DWARF Debugging Information Format: https://dwarfstd.org/
  • Go言語のソースコード (src/cmd/ld/dwarf.c): https://github.com/golang/go/blob/master/src/cmd/ld/dwarf.c
  • Go言語のリンカに関するドキュメント (Goの公式ドキュメントや関連するブログ記事など)
  • Program Counter (PC) の概念に関する一般的なコンピュータアーキテクチャの知識
  • デルタエンコーディングに関する一般的なデータ圧縮の知識