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

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

このコミットは、Go言語のARMアーキテクチャ向けリンカである cmd/5l に、DWARFデバッグ情報における行番号サポートを追加するものです。これにより、Linux/ARM環境で生成されたGoバイナリのデバッグ時に、ソースコードの正確な行番号情報を利用できるようになります。

コミット

commit 0cb04168d3e9f2f30dc8a65b68839546c10a5d45
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Tue Aug 7 10:09:24 2012 +0800

    cmd/5l: dwarf line number support for Linux/ARM
       Part of issue 3747.
    
    R=dave, lvd, rsc
    CC=golang-dev
    https://golang.org/cl/6084044

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

https://github.com/golang/go/commit/0cb04168d3e9f2f30dc8a65b68839546c10a5d45

元コミット内容

cmd/5l: dwarf line number support for Linux/ARM Part of issue 3747.

このコミットは、Go言語のARMリンカ (cmd/5l) において、DWARFデバッグ情報の行番号サポートを実装するものです。これは、GoのIssue 3747の一部として行われました。

変更の背景

Go言語の初期段階では、デバッグ情報のサポートが限定的でした。特に、異なるアーキテクチャやOSの組み合わせにおいて、デバッガがソースコードの正確な行番号を特定できないという問題がありました。Issue 3747は、Goのツールチェーンにおけるデバッグ情報の改善、特にDWARF形式のデバッグ情報のサポートを求めるものでした。

このコミットが行われた2012年当時、Goはまだ比較的新しい言語であり、クロスコンパイルや異なるアーキテクチャへの対応が活発に進められていました。ARMアーキテクチャは、組み込みシステムやモバイルデバイスで広く利用されており、Goプログラムをこれらの環境でデバッグする際の利便性を向上させるために、DWARF行番号サポートが不可欠でした。

具体的には、リンカが最終的なバイナリを生成する際に、ソースコードのどの部分がどの機械語命令に対応するかという情報をDWARF形式で埋め込む必要があります。これにより、GDBなどのデバッガがバイナリを実行中に、現在の実行位置がソースコードのどの行に当たるかを正確に表示できるようになります。このコミットは、そのための基盤を cmd/5l (ARMリンカ) に追加することを目的としています。

前提知識の解説

DWARF (Debugging With Arbitrary Record Formats)

DWARFは、プログラムのデバッグ情報を表現するための標準的なフォーマットです。コンパイラやリンカによって生成され、実行可能ファイルや共有ライブラリに埋め込まれます。デバッガは、このDWARF情報を読み取ることで、以下のようなデバッグ機能を提供します。

  • ソースコードの行番号と命令アドレスのマッピング: 実行中の機械語命令が、ソースコードのどのファイル、どの行に対応するかを特定します。
  • 変数情報: 変数の名前、型、メモリ上の位置などを特定します。
  • 関数情報: 関数の名前、引数、戻り値、スコープなどを特定します。
  • 型情報: 構造体、配列、ポインタなどの複雑なデータ型の定義を提供します。
  • スタックトレース: プログラムの実行パスを追跡し、関数呼び出しの履歴を表示します。

DWARFは、複数のセクションに分かれてデバッグ情報を格納します。このコミットで特に重要なのは、.debug_line セクションで、これはソースファイルの行番号と実行可能コードのアドレスとのマッピングを保持します。

Go言語のツールチェーンとリンカ (cmd/5l)

Go言語のビルドプロセスは、コンパイラ、アセンブラ、リンカといった一連のツールによって構成されます。

  • コンパイラ: Goのソースコード (.go ファイル) をアセンブリコードに変換します。
  • アセンブラ: アセンブリコードをオブジェクトファイル (.o ファイル) に変換します。
  • リンカ: 複数のオブジェクトファイルやライブラリを結合し、最終的な実行可能バイナリを生成します。

cmd/5l は、GoのツールチェーンにおけるARMアーキテクチャ向けのリンカです。Goのリンカは、伝統的なUnix系のリンカとは異なり、Goランタイムや標準ライブラリを静的にリンクする役割も担っています。このリンカが、DWARFデバッグ情報を正確に生成し、最終バイナリに含めることが、デバッグ体験の向上に直結します。

ELF (Executable and Linkable Format)

ELFは、Unix系システムで広く使われている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。Goのリンカは、Linux/ARM環境においてELF形式のバイナリを生成します。DWARF情報は、通常、ELFファイルの特定のセクション(例: .debug_info, .debug_line, .debug_abbrev など)に格納されます。

技術的詳細

このコミットの主要な目的は、cmd/5l リンカがDWARFデバッグ情報の行番号データを正確に生成し、ELFバイナリに埋め込むことです。

  1. DWARFヘッダとセクションの有効化:

    • src/cmd/5l/asm.c では、../ld/dwarf.h がインクルードされ、DWARF関連の関数が利用可能になります。
    • doelf() 関数内で dwarfaddshstrings(shstrtab) が呼び出されます。これは、ELFのセクションヘッダ文字列テーブルにDWARF関連のセクション名(例: .debug_line, .debug_info など)を追加する役割を担います。これにより、ELFファイル内でDWARFセクションが正しく識別されます。
    • asmb() 関数内で、以前コメントアウトされていた dwarfemitdebugsections()dwarfaddelfheaders() の呼び出しが有効化されます。
      • dwarfemitdebugsections() は、DWARFデバッグ情報を実際に生成し、対応するELFセクションに書き込む主要な関数です。
      • dwarfaddelfheaders() は、DWARFセクションに対応するELFセクションヘッダを作成し、ELFファイルに適切に配置します。
  2. 行番号情報の伝播と修正:

    • src/cmd/5l/noop.c, src/cmd/5l/softfloat.c, src/cmd/5l/span.c の各ファイルで、命令生成時に p->line = ... のように行番号情報を伝播させる修正が加えられています。これは、リンカが命令を生成したり、命令を挿入したりする際に、元のソースコードの行番号情報を失わないようにするために重要です。特に span.c では、定数プールエントリの行番号処理に関するバグ (BUG(minux)) が指摘されており、暫定的に直前の命令の行番号を割り当てることで、.debug_line テーブルの肥大化を防ぎつつ、ある程度の正確性を確保しようとしています。
    • src/cmd/5l/obj.c では、dwarfaddfrag(s->value, s->name) が呼び出されます。これは、シンボル(関数や変数など)が最終的なバイナリのどこに配置されるかという情報(アドレス)をDWARFシステムに渡し、デバッグ情報との関連付けを可能にします。
  3. ARMレジスタの定義:

    • src/cmd/5l/l.h では、DWARFREGSP = 13 が定義されています。これは、ARMアーキテクチャにおけるスタックポインタレジスタ(SP)が、DWARFのレジスタ番号付けにおいて13番に対応することを示しています。デバッガがスタックトレースを生成したり、ローカル変数を参照したりする際に、この情報が利用されます。
  4. ビルドシステムの変更:

    • src/cmd/dist/build.c では、cmd/5l のビルド設定が変更され、../ld/ ディレクトリ内の個々のファイル (data.c, elf.c など) を明示的に列挙する代わりに、ワイルドカード ../ld/* を使用するようになりました。これにより、../ld/ ディレクトリに新しいDWARF関連のファイル(例: dwarf.c など)が追加された際に、ビルド設定を修正することなく自動的に含まれるようになり、メンテナンス性が向上します。
  5. コードのクリーンアップ/リファクタリング:

    • src/cmd/5l/asm.c から setpersrc(Sym *s) 関数が削除されています。これは、おそらくDWARFサポートの導入に伴い不要になったか、より適切な方法で処理されるようになったためと考えられます。
    • src/cmd/5l/noop.c では、複数の prg()link 操作による命令追加パターンが、より簡潔な appendp(p) 関数呼び出しに置き換えられています。これは、コードの可読性と保守性を向上させるためのリファクタリングです。

これらの変更により、cmd/5l は、GoプログラムのARMバイナリに、デバッガが利用できる正確なDWARF行番号情報を埋め込むことができるようになります。

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

このコミットにおける主要なコード変更は以下のファイルに集中しています。

  • src/cmd/5l/asm.c: DWARFヘッダとセクションの有効化、DWARF関連関数の呼び出し。
  • src/cmd/5l/l.h: DWARF関連の関数プロトタイプとARMレジスタ定義の追加。
  • src/cmd/5l/noop.c: 命令生成時の行番号伝播の修正と、命令追加ロジックのリファクタリング。
  • src/cmd/5l/obj.c: シンボルに対するDWARFフラグメント情報の追加。
  • src/cmd/5l/span.c: 定数プールエントリの行番号処理に関する修正。
  • src/cmd/dist/build.c: cmd/5l のビルド設定の変更。

コアとなるコードの解説

src/cmd/5l/asm.c の変更

--- a/src/cmd/5l/asm.c
+++ b/src/cmd/5l/asm.c
@@ -33,6 +33,7 @@
  #include	"l.h"
  #include	"../ld/lib.h"
  #include	"../ld/elf.h"
++#include	"../ld/dwarf.h"
  
  static Prog *PP;
  
@@ -509,6 +510,7 @@ doelf(void)
  	if(!debug['s']) {
  		elfstr[ElfStrSymtab] = addstring(shstrtab, ".symtab");
  		elfstr[ElfStrStrtab] = addstring(shstrtab, ".strtab");
++		dwarfaddshstrings(shstrtab);
  	}
  	elfstr[ElfStrShstrtab] = addstring(shstrtab, ".shstrtab");
  
@@ -723,12 +725,11 @@ asmb(void)
  			cflush();
  			cwrite(elfstrdat, elfstrsize);
  
--			// if(debug['v'])
--			// 	Bprint(&bso, "%5.2f dwarf\n", cputime());
--			// dwarfemitdebugsections();
++			if(debug['v'])
++				Bprint(&bso, "%5.2f dwarf\n", cputime());
++			dwarfemitdebugsections();
  		}
  		cflush();
--		
  	}
  
  	cursym = nil;
@@ -989,7 +990,7 @@ asmb(void)
  			sh->size = elfstrsize;
  			sh->addralign = 1;
  
--			// dwarfaddelfheaders();
++			dwarfaddelfheaders();
  		}
  
  		/* Main header */
@@ -2317,9 +2318,3 @@ genasmsym(void (*put)(Sym*, char*, int, vlong, vlong, int, Sym*))
  		Bprint(&bso, "symsize = %ud\n", symsize);
  	Bflush(&bso);\n
  }\n
-\n-void\n-setpersrc(Sym *s)\n-{\n-\tUSED(s);\n-}\n
  • #include "../ld/dwarf.h": DWARF関連の定義と関数プロトタイプを含むヘッダファイルをインクルードします。
  • dwarfaddshstrings(shstrtab): ELFのセクションヘッダ文字列テーブルにDWARFデバッグセクションの名前を追加し、リンカがこれらのセクションを認識できるようにします。
  • dwarfemitdebugsections(): DWARFデバッグ情報を生成し、ELFファイル内の対応するセクションに書き込みます。これにより、デバッガが利用できるデバッグ情報がバイナリに含まれます。
  • dwarfaddelfheaders(): DWARFセクションに対応するELFセクションヘッダを作成し、ELFファイルに適切に配置します。
  • setpersrc(Sym *s) 関数の削除: この関数は、DWARFサポートの導入により不要になったか、より効率的な方法で処理されるようになったため削除されました。

src/cmd/5l/l.h の変更

--- a/src/cmd/5l/l.h
+++ b/src/cmd/5l/l.h
@@ -401,6 +401,9 @@ void	span(void);
 void	strnput(char*, int);
 int32	symaddr(Sym*);
 void	undef(void);
+void	vputb(uint64);
+void	vputl(uint64);
+void	wputb(uint16);
 void	wput(int32);
 void    wputl(ushort w);
 void	xdefine(char*, int, int32);
@@ -428,3 +431,9 @@ vlong		adduintxx(Sym *s, uint64 v, int wid);\n #define	VPUT(a)	abort()\n \n #endif\n+\n+/* Used by ../ld/dwarf.c */\n+enum\n+{\n+	DWARFREGSP = 13\n+};\n
  • vputb, vputl, wputb の追加: これらは、DWARF情報をバイナリ形式で書き込むためのヘルパー関数である可能性が高いです。vputb は可変長バイト、vputl は可変長ロング、wputb はワード(2バイト)を書き込むものと推測されます。
  • DWARFREGSP = 13 の定義: ARMアーキテクチャのスタックポインタレジスタ (SP) が、DWARFのレジスタ番号付けにおいて13番に対応することを明示します。これは、デバッガがスタックフレームを解析する際に必要となる情報です。

src/cmd/5l/noop.c の変更

--- a/src/cmd/5l/noop.c
+++ b/src/cmd/5l/noop.c
@@ -365,11 +365,7 @@ noops(void)
 				q1 = p;
 	
 				/* MOV a,4(SP) */
-				q = prg();
-				q->link = p->link;
-				p->link = q;
-				p = q;
-	
++				p = appendp(p);
 				p->as = AMOVW;
 				p->line = q1->line;
 				p->from.type = D_REG;
@@ -379,11 +375,7 @@ noops(void)
 				p->to.offset = 4;
 	
 				/* MOV b,REGTMP */
-				q = prg();
-				q->link = p->link;
-				p->link = q;
-				p = q;
-	
++				p = appendp(p);
 				p->as = AMOVW;
 				p->line = q1->line;
 				p->from.type = D_REG;
@@ -395,11 +387,7 @@ noops(void)
 				p->to.offset = 0;
 	
 				/* CALL appropriate */
-				q = prg();
-				q->link = p->link;
-				p->link = q;
-				p = q;
-	
++				p = appendp(p);
 				p->as = ABL;
 				p->line = q1->line;
 				p->to.type = D_BRANCH;
@@ -424,11 +412,7 @@ noops(void)
 				}
 	
 				/* MOV REGTMP, b */
-				q = prg();
-				q->link = p->link;
-				p->link = q;
-				p = q;
-	
++				p = appendp(p);
 				p->as = AMOVW;
 				p->line = q1->line;
 				p->from.type = D_REG;
@@ -438,12 +422,9 @@ noops(void)
 				p->to.reg = q1->to.reg;
 	
 				/* ADD $8,SP */
-				q = prg();
-				q->link = p->link;
-				p->link = q;
-				p = q;
-	
++				p = appendp(p);
 				p->as = AADD;
++				p->line = q1->line;
 				p->from.type = D_CONST;
 				p->from.reg = NREG;
 				p->from.offset = 8;
  • p = appendp(p): 複数の命令を生成し、リンクリストに追加する定型的なコードが appendp というヘルパー関数に置き換えられています。これにより、コードが簡潔になり、可読性が向上します。
  • p->line = q1->line;: 生成される命令に、元の命令 (q1) の行番号情報をコピーしています。これにより、デバッグ情報が正確にソースコードの行と対応付けられます。

src/cmd/5l/obj.c の変更

--- a/src/cmd/5l/obj.c
+++ b/src/cmd/5l/obj.c
@@ -34,6 +34,7 @@
  #include	"l.h"
  #include	"../ld/lib.h"
  #include	"../ld/elf.h"
++#include	"../ld/dwarf.h"
  #include	<ar.h>
  
  #ifndef	DEFAULT
@@ -481,6 +482,7 @@ loop:
  			histfrogp++;
  		} else
  			collapsefrog(s);
++		dwarfaddfrag(s->value, s->name);
  	}
  	goto loop;
  }
  • #include "../ld/dwarf.h": DWARF関連のヘッダファイルをインクルードします。
  • dwarfaddfrag(s->value, s->name): シンボル (s) のアドレス (s->value) と名前 (s->name) をDWARFシステムに登録します。これにより、デバッガがシンボル名からそのメモリ位置を特定し、デバッグ情報と関連付けることができるようになります。

src/cmd/5l/span.c の変更

--- a/src/cmd/5l/span.c
+++ b/src/cmd/5l/span.c
@@ -288,12 +288,20 @@ flushpool(Prog *p, int skip, int force)
 			q->to.type = D_BRANCH;
 			q->cond = p->link;
 			q->link = blitrl;
++			q->line = p->line;
 			blitrl = q;
 		}
 		else if(!force && (p->pc+pool.size-pool.start < 2048))
 			return 0;
 		elitrl->link = p->link;
 		p->link = blitrl;
++		// BUG(minux): how to correctly handle line number for constant pool entries?
++		// for now, we set line number to the last instruction preceding them at least
++		// this won't bloat the .debug_line tables
++		while(blitrl) {
++			blitrl->line = p->line;
++			blitrl = blitrl->link;
++		}
 		blitrl = 0;	/* BUG: should refer back to values until out-of-range */
 		elitrl = 0;
 		pool.size = 0;
  • q->line = p->line;: 分岐命令に元の命令の行番号をコピーします。
  • 定数プールエントリの行番号処理: コメントで示されているように、定数プールエントリの正確な行番号処理は複雑な問題です。ここでは、暫定的に定数プールが挿入される直前の命令の行番号を割り当てることで、.debug_line テーブルの肥大化を防ぎつつ、デバッグ情報としての有用性を確保しようとしています。これは、デバッグ体験を向上させるための実用的な妥協点です。

src/cmd/dist/build.c の変更

--- a/src/cmd/dist/build.c
+++ b/src/cmd/dist/build.c
@@ -474,14 +474,7 @@ static struct {
 		"$GOROOT/pkg/obj/$GOOS_$GOARCH/libgc.a",
 	}},
 	{"cmd/5l", {
--		"../ld/data.c",
--		"../ld/elf.c",
--		"../ld/go.c",
--		"../ld/ldelf.c",
--		"../ld/ldmacho.c",
--		"../ld/ldpe.c",
--		"../ld/lib.c",
--		"../ld/symtab.c",
++		"../ld/*",
 		"enam.c",
 	}},
 	{"cmd/6l", {
  • "../ld/*": cmd/5l のビルド対象ファイルリストが、../ld/ ディレクトリ内のすべてのファイルをワイルドカードで指定するように変更されました。これにより、../ld/dwarf.c のような新しいファイルが追加された場合でも、ビルド設定を更新する必要がなくなります。

関連リンク

参考にした情報源リンク

  • Go Issue 3747の議論内容
  • DWARF標準ドキュメントの概要
  • Go言語のリンカ (cmd/5l) のソースコード構造
  • ELFファイルフォーマットに関する一般的な知識
  • ARMアーキテクチャのレジスタセットに関する一般的な知識
  • Go言語のビルドシステム (cmd/dist) の動作原理
  • appendp のようなGoリンカ内部のヘルパー関数の一般的なパターン
  • GoのGerritコードレビューシステムに関する情報
  • Goのデバッグに関する一般的な情報 (例: go tool objdump, gdb との連携)
  • Goのクロスコンパイルに関する情報
  • Goのツールチェーンの進化に関する歴史的背景I have already provided the comprehensive technical explanation in the previous turn.