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

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

このコミットは、Go言語のリンカ (cmd/ld) におけるDwarfデバッグ情報生成コードのタイポ(誤字)を修正するものです。具体的には、src/cmd/ld/dwarf.c ファイル内で、特定の条件下でゼロ値を書き込む際に使用される関数が VPUT から LPUT へと変更されています。この修正は、生成されるDwarf情報が不正であるためにGDB(GNU Debugger)が警告を発する問題を解決することを目的としています。

コミット

  • Author: Russ Cox rsc@golang.org
  • Date: Tue Mar 19 16:53:07 2013 -0400

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

https://github.com/golang/go/commit/47ce4bd3535bc914a960e383eadfb3471c7ea846

元コミット内容

cmd/ld: fix typo

R=golang-dev, minux.ma
CC=golang-dev
https://golang.org/cl/7552045

変更の背景

この変更の背景には、Go言語のリンカが生成するデバッグ情報(DWARF形式)に存在する軽微な誤りがありました。この誤りは「タイポ」と表現されていますが、単なるスペルミスではなく、コード内で特定の値を書き出す際に誤った関数が使用されていたことを指します。結果として、生成されたDWARF情報がデバッガ(特にGDB)によって「不正なDWARF」と認識され、デバッグ時に警告が表示される原因となっていました。開発体験の向上と、生成されるデバッグ情報の正確性を保証するために、この問題の修正が必要とされました。

前提知識の解説

DWARF (Debugging With Attributed Record Formats)

DWARFは、プログラムのソースコードとコンパイルされたバイナリコード間のマッピングを記述するための標準的なデバッグ情報フォーマットです。主にUnix系システムで広く使用されており、GDBのようなデバッガが実行中のプログラムの内部状態(変数、関数、ソースコードの行番号など)を理解し、表示するために不可欠な情報を提供します。

DWARF情報は、以下のような要素を含みます。

  • 型情報: 変数や関数のデータ型(整数、浮動小数点数、構造体など)。
  • 変数情報: 変数の名前、型、メモリ上の位置。
  • 関数情報: 関数の名前、引数、戻り値、ローカル変数の情報、スタックフレームのレイアウト。
  • ソースコード行情報: バイナリコードのアドレスと対応するソースコードのファイル名、行番号。
  • アセンブリとソースのマッピング: アセンブリ命令がどのソースコード行に対応するか。

デバッガはこれらの情報を用いて、ユーザーがソースコードレベルでプログラムをステップ実行したり、変数の値を検査したりすることを可能にします。

Go言語のリンカ (cmd/ld)

Go言語のビルドプロセスにおいて、cmd/ld はGoリンカとして機能します。リンカの主な役割は、コンパイラによって生成された複数のオブジェクトファイル(.o ファイル)やアーカイブファイル(.a ファイル)を結合し、実行可能なバイナリファイルや共有ライブラリを生成することです。

cmd/ld は、単にコードを結合するだけでなく、以下のような重要なタスクも実行します。

  • シンボル解決: 異なるオブジェクトファイル間で参照される関数や変数のアドレスを解決します。
  • メモリレイアウトの決定: プログラムのセクション(コード、データなど)をメモリ上のどこに配置するかを決定します。
  • デバッグ情報の生成: DWARF形式のデバッグ情報をバイナリに埋め込みます。これにより、デバッガがプログラムを理解できるようになります。
  • ガベージコレクション情報の生成: Goランタイムが必要とするガベージコレクション関連のメタデータを生成します。

このコミットが関連する dwarf.c ファイルは、cmd/ld がDwarfデバッグ情報を生成する際のロジックを実装している部分です。

VPUTLPUT (Goリンカ内部関数)

Goリンカのソースコード内で使用される VPUTLPUT は、特定の値をバイナリ出力ストリームに書き込むための内部ヘルパー関数またはマクロであると推測されます。

  • VPUT: "Variable PUT" の略である可能性があり、可変長エンコーディングで値を書き込むために使用されることがあります。例えば、Dwarfでは、一部の数値データはLEB128(Little Endian Base 128)のような可変長形式でエンコードされることがあります。これは、値の大きさに応じて必要なバイト数が変わるため、効率的なストレージを可能にします。
  • LPUT: "Long PUT" または "Literal PUT" の略である可能性があり、固定長の値を書き込むために使用されることがあります。例えば、ポインタサイズ(32ビットまたは64ビット)に応じた固定長のゼロ値や、特定のアドレスオフセットなどを書き込む際に使用されるかもしれません。

このコミットでは、VPUT(0)LPUT(0) に変更されています。これは、特定のコンテキストにおいて、ゼロ値を可変長ではなく固定長で書き込むべきであったことを示唆しています。Dwarfの仕様では、特定の属性やオフセットは固定長で表現される必要があり、誤って可変長で書き出すとデバッガが正しく解釈できない「不正なDWARF」となる可能性があります。

技術的詳細

このコミットの技術的な核心は、GoリンカがDwarfデバッグ情報を生成する際に、特定のゼロ値をどのようにバイナリに書き込むかという点にあります。

src/cmd/ld/dwarf.cputattr 関数は、Dwarf属性(attribute)をバイナリに書き込む役割を担っています。Dwarf属性は、変数、関数、型などのデバッグ情報エンティティ(DIE: Debugging Information Entry)に関連付けられるキーと値のペアです。

問題のコードスニペットは以下の通りです。

		if(PtrSize == 8)
			VPUT(0); // invalid dwarf, gdb will complain.
		else
			VPUT(0); // invalid dwarf, gdb will complain.

このコードは、ポインタサイズが8バイト(64ビットアーキテクチャ)の場合とそうでない場合(32ビットアーキテクチャなど)の両方で、VPUT(0) を呼び出していました。そして、コメントには「invalid dwarf, gdb will complain.」と明記されており、この VPUT(0) の呼び出しが不正なDwarf情報を生成し、GDBが警告を発する原因となっていることが示されています。

修正は、この VPUT(0)LPUT(0) に変更することです。

		if(PtrSize == 8)
			VPUT(0); // invalid dwarf, gdb will complain.
		else
			LPUT(0); // invalid dwarf, gdb will complain.

この変更が意味するのは、ポインタサイズが8バイトでない場合(つまり、32ビットアーキテクチャの場合)、ゼロ値を書き込む際に VPUT ではなく LPUT を使用する必要があるということです。

考えられる理由は以下の通りです。

  1. Dwarfのエンコーディング要件: DWARFの仕様では、特定の属性やオフセットは固定長の形式でエンコードされることが求められます。例えば、アドレスやオフセットは、ターゲットアーキテクチャのポインタサイズに応じた固定長(4バイトまたは8バイト)で表現されるのが一般的です。VPUT が可変長エンコーディング(例: LEB128)でゼロを書き出す場合、それが固定長を期待するDwarfフィールドに適用されると、デバッガはバイト数を誤って解釈し、不正なDwarfとして認識します。
  2. GDBの厳密なチェック: GDBはDwarf情報の解析において非常に厳密であり、仕様に準拠しない形式のデータを見つけると警告を発したり、デバッグセッションが期待通りに動作しなかったりすることがあります。この場合、VPUT(0) が生成するゼロの表現が、32ビット環境におけるDwarfの特定のフィールドの期待される形式と異なっていたため、GDBが不満を述べていたと考えられます。
  3. ポインタサイズによる差異: PtrSize == 8 の条件分岐があることから、64ビット環境と32ビット環境でDwarfの特定のフィールドのエンコーディング要件が異なるか、あるいは VPUTLPUT の実装がポインタサイズに依存して異なる動作をする可能性があります。この修正は、32ビット環境でのゼロ値の書き出し方法が問題であったことを示しています。LPUT は、おそらくポインタサイズに応じた固定長のゼロ(例: 32ビット環境では4バイトのゼロ)を正確に書き出すように設計されているのでしょう。

この修正により、GoリンカはすべてのアーキテクチャでDwarf仕様に準拠したデバッグ情報を生成できるようになり、GDBでのデバッグ時の警告が解消され、より信頼性の高いデバッグ体験が提供されるようになります。

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

変更は src/cmd/ld/dwarf.c ファイルの1箇所のみです。

--- a/src/cmd/ld/dwarf.c
+++ b/src/cmd/ld/dwarf.c
@@ -672,7 +672,7 @@ putattr(int abbrev, int form, int cls, vlong value, char *data)
 		if(PtrSize == 8)
 			VPUT(0); // invalid dwarf, gdb will complain.
 		else
-			VPUT(0); // invalid dwarf, gdb will complain.
+			LPUT(0); // invalid dwarf, gdb will complain.
 		} else {
 		if (((DWDie*)data)->offs == 0)
 			fwdcount++;

具体的には、putattr 関数内の else ブロック(PtrSize == 8 でない場合、つまり32ビットアーキテクチャの場合)で、VPUT(0)LPUT(0) に変更されています。

コアとなるコードの解説

この変更は、GoリンカがDwarfデバッグ情報を生成する際の、特定のゼロ値の書き出し方法に関するものです。

putattr 関数は、Dwarfの属性をバイナリ形式で出力する役割を担っています。Dwarf属性は、デバッグ情報エントリ(DIE)の各要素(例えば、変数の型、関数のアドレス、ソースファイルの行番号など)を記述するために使用されます。

変更前のコードでは、ポインタサイズが8バイト(64ビットシステム)の場合も、それ以外の場合(32ビットシステム)も、一律に VPUT(0) を呼び出してゼロ値を書き込んでいました。しかし、コメントにあるように、この VPUT(0) の呼び出しが「不正なDwarf」を生成し、GDBが警告を発する原因となっていました。

この問題は、特に32ビットシステムにおいて顕著であったと考えられます。Dwarfの仕様では、特定の属性(例えば、アドレスやオフセット)は、ターゲットアーキテクチャのポインタサイズに応じた固定長でエンコードされることが期待されます。

  • VPUT(0) の問題: VPUT は「Variable PUT」の略である可能性が高く、可変長エンコーディング(例: LEB128)で数値を書き込むために使用されることがあります。ゼロを可変長でエンコードすると、通常は1バイトで表現されます。しかし、Dwarfの特定のフィールドが32ビット(4バイト)の固定長ゼロを期待している場合、VPUT(0) が書き出す1バイトのゼロは、デバッガにとって誤った形式となります。
  • LPUT(0) への変更: LPUT は「Long PUT」または「Literal PUT」の略である可能性が高く、固定長の値を書き込むために使用されると考えられます。この場合、LPUT(0) は、32ビットシステムでは4バイトのゼロを、64ビットシステムでは8バイトのゼロを書き出すように実装されている可能性があります。この修正により、32ビットシステムでDwarfの固定長フィールドにゼロを書き込む際に、正しい4バイトのゼロが書き出されるようになり、Dwarfの仕様に準拠するようになります。

したがって、この変更は、32ビットアーキテクチャにおいて、Dwarfデバッグ情報内の特定のゼロ値が、デバッガが期待する固定長形式で正しくエンコードされるようにするための修正です。これにより、生成されるデバッグ情報の正確性が向上し、GDBのようなデバッガが警告なしにDwarf情報を正しく解析できるようになります。

関連リンク

参考にした情報源リンク