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

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

このコミットは、Go言語のツールチェインにおいて、Linux/ARMアーキテクチャ向けにDWARFデバッグ情報に型情報(type info)を含めるように拡張するものです。具体的には、Goのコンパイラ(cmd/5g)、アセンブラ(cmd/5a)、Cコンパイラ(cmd/5c)、リンカ(cmd/5l)、およびオブジェクトファイル解析ライブラリ(libmach)が変更され、Go固有の型情報(gotype)がオブジェクトファイルに埋め込まれるようになります。これにより、Linux/ARM環境でのGoプログラムのデバッグ体験が向上し、デバッガがより詳細な型情報を利用できるようになります。

コミット

commit 8cdee790634cd9b5596d33c15ce1a9b66055bac2
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Tue Feb 26 06:15:29 2013 +0800

    libmach, cmd/5a, cmd/5c, cmd/5g, cmd/5l: enable DWARF type info for Linux/ARM
    Fixes #3747.
    
    Update #4912
    This CL adds gotype into .5 object file.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/7376054

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

https://github.com/golang/go/commit/8cdee790634cd9b5596d33c15ce1a9b66055bac2

元コミット内容

このコミットの目的は、Go言語のツールチェインがLinux/ARMプラットフォームで生成するバイナリに、DWARFデバッグ情報の一部として型情報を含めることです。これにより、デバッガがGoプログラムの変数やデータ構造の型を正確に認識し、より効果的なデバッグが可能になります。特に、Goのランタイムが使用する内部的な型情報(gotype)をオブジェクトファイルに埋め込むための変更が中心となっています。

変更の背景

この変更は、以下のGoイシューに関連しています。

  • Fixes #3747: このイシューは、Goのデバッグ情報が不完全であること、特にDWARFデバッグ情報に型情報が欠けていることに関するものです。デバッガがGoの型を認識できないため、デバッグが困難になるという問題がありました。このコミットは、Linux/ARM環境におけるこの問題の解決を目指しています。
  • Update #4912: このイシューは、Goのオブジェクトファイルフォーマットに関するもので、gotype情報をオブジェクトファイルに含めることの必要性を示唆しています。gotypeはGoの型システムの中核をなす情報であり、デバッグだけでなく、リフレクションやガベージコレクションなど、Goランタイムの多くの側面で利用されます。

当時のGoのツールチェインは、特定のアーキテクチャ(この場合はARM)とOS(Linux)の組み合わせにおいて、DWARFデバッグ情報の生成が不十分でした。特に、Goの独自の型システムに関する情報が欠落しており、デバッガがGoの構造体、インターフェース、スライスなどの複雑な型を正しく解釈できませんでした。このコミットは、このギャップを埋め、Goプログラムのデバッグ体験を改善することを目的としています。

前提知識の解説

DWARF (Debugging With Attributed Record Formats)

DWARFは、ソースレベルデバッガがプログラムの実行を分析するために必要な情報を提供する標準的なデバッグデータフォーマットです。コンパイラやアセンブラによって生成された実行可能ファイルやライブラリに埋め込まれます。DWARF情報には、以下のようなものが含まれます。

  • ソースファイルと行番号のマッピング: 実行中のコードがどのソースファイルのどの行に対応するか。
  • 変数情報: 変数の名前、型、メモリ上の位置、スコープ。
  • 関数情報: 関数の名前、引数、戻り値、ローカル変数。
  • 型情報: 構造体、配列、ポインタ、列挙型などのデータ型の定義。

デバッガはこれらの情報を使用して、ソースコードレベルでのステップ実行、変数の検査、ブレークポイントの設定などを行います。

Goのツールチェイン (cmd/5a, cmd/5c, cmd/5g, cmd/5l)

Go言語の初期のツールチェインは、各アーキテクチャとOSの組み合わせに対して、専用のコマンド名を持っていました。5はARMアーキテクチャを指します。

  • cmd/5a (Assembler): ARMアセンブリコードをオブジェクトファイルに変換します。
  • cmd/5c (C Compiler): Goランタイムの一部など、C言語で書かれたコードをARMオブジェクトファイルにコンパイルします。
  • cmd/5g (Go Compiler): GoソースコードをARMアセンブリコードにコンパイルし、その後オブジェクトファイルに変換します。このコミットで最も重要な変更が行われた部分です。
  • cmd/5l (Linker): 複数のオブジェクトファイル(Go、C、アセンブリ)を結合し、最終的な実行可能バイナリを生成します。このプロセスでDWARF情報も統合されます。
  • libmach: Goのオブジェクトファイルフォーマットを解析するためのライブラリです。デバッガや他のツールがGoのバイナリを理解するために使用されます。

Goの型システムと gotype

Goは静的型付け言語であり、強力な型システムを持っています。Goのランタイムは、プログラムの実行中に型の情報を利用します。例えば、インターフェースの動的なディスパッチ、リフレクション、ガベージコレクションなどが型の情報に依存しています。

gotypeは、Goのコンパイラとランタイムが内部的に使用する、Goの型に関するメタデータです。これは、C言語の構造体やプリミティブ型とは異なる、Go独自の型(スライス、マップ、チャネル、インターフェース、構造体など)の詳細な定義を含みます。このgotype情報をDWARFデバッグ情報に含めることで、デバッガはGoのプログラムをより「Goらしく」理解し、表示できるようになります。

オブジェクトファイルフォーマット

コンパイラやアセンブラが生成する中間ファイルがオブジェクトファイルです。これには、コンパイルされた機械語コード、シンボルテーブル(関数名、変数名などとそれらのアドレスのマッピング)、そしてデバッグ情報などが含まれます。Goのツールチェインは独自のオブジェクトファイルフォーマットを使用しており、このコミットではそのフォーマットにgotype情報を埋め込むための変更が加えられています。

技術的詳細

このコミットの核心は、Goのコンパイラ(cmd/5g)がgotype情報を生成し、それをオブジェクトファイルに埋め込むメカニズムを導入し、リンカ(cmd/5l)とオブジェクトファイル解析ライブラリ(libmach)がその情報を正しく処理できるようにすることです。

  1. Addr構造体の拡張:

    • src/cmd/5g/gsubr.csrc/libmach/5obj.cで、Addr構造体(アドレス情報を表現する内部構造体)にgotypeフィールドが追加されました。これは、特定のアドレスに関連付けられたGoの型情報を保持するためのものです。
    • src/cmd/5a/lex.csrc/cmd/5c/swt.cでは、zaddr関数が呼び出される際に、新しいgotypeフィールドのためのゼロバイトが追加で書き込まれるようになっています。これは、オブジェクトファイルのフォーマット変更に対応するためです。
  2. zaddr関数の変更:

    • src/cmd/5g/gg.hsrc/cmd/5g/gobj.cで、zaddr関数のシグネチャが変更され、gotypeを表す新しいintパラメータが追加されました。この関数は、オブジェクトファイルにアドレス情報を書き込む際に使用されます。
    • src/cmd/5l/obj.cでは、リンカ側のzaddr関数も変更され、オブジェクトファイルからgotype情報を読み取るようになりました。adrgotypeというグローバル変数が導入され、読み取ったgotypeシンボルを一時的に保持します。
  3. gotypeシンボルの管理 (cmd/5g/gobj.c):

    • cmd/5g/gobj.cに、zsymreset, zsym, zsymaddrという新しい静的関数が導入されました。これらは、gotypeシンボルを効率的に管理するためのハッシュテーブルのようなメカニズムを提供します。
    • zsym関数は、Goの型シンボル(Sym*)と型(int)を受け取り、そのシンボルが既に登録されている場合はそのインデックスを返し、新しい場合は登録して新しいインデックスを返します。これにより、オブジェクトファイル内でgotype情報をコンパクトに表現できます。
    • dumpfuncs関数(Go関数の情報をオブジェクトファイルにダンプする関数)が大幅に修正されました。以前は単純なシンボル管理を行っていましたが、新しいzsymaddrzsym関数を使用して、p->fromp->toのアドレスに関連付けられたgotype情報を取得し、オブジェクトファイルに書き込むようになりました。
  4. リンカでのgotype情報の処理 (cmd/5l/obj.c):

    • リンカは、コンパイラが生成したオブジェクトファイルからgotype情報を読み取ります。
    • zsymという新しいヘルパー関数が導入され、オブジェクトファイルから読み取ったシンボルインデックスを実際のSym*ポインタに変換します。
    • zaddr関数内で、読み取ったgotype情報がadrgotypeグローバル変数に格納されます。
    • リンカは、D_AUTO(自動変数)やD_PARAM(パラメータ)などの特定の型のアドレスに対して、関連するシンボル(s)や自動変数情報(u)にgotype情報を関連付けるロジックが追加されました。これにより、デバッグ情報として正しい型が伝播されます。
    • loop関数(オブジェクトファイルの命令を処理するメインループ)内で、p->fromのアドレスから読み取られたgotypefromgotypeに格納され、最終的に関数のシンボル(s)にgotypeが設定されるようになりました。これにより、関数全体の型情報がリンカによって正しく処理されます。

これらの変更により、GoのコンパイラはGoの型情報をオブジェクトファイルに埋め込み、リンカはその情報を利用して最終的なバイナリにDWARFデバッグ情報として含めることができるようになります。

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

src/cmd/5g/gobj.c

このファイルはGoコンパイラのバックエンドの一部であり、Goのプログラム構造をオブジェクトファイルに書き出す役割を担っています。

--- a/src/cmd/5g/gobj.c
+++ b/src/cmd/5g/gobj.c
@@ -65,17 +65,17 @@ zhist(Biobuf *b, int line, vlong offset)
 	Bputc(b, line>>8);
 	Bputc(b, line>>16);
 	Bputc(b, line>>24);
-	zaddr(b, &zprog.from, 0);
+	zaddr(b, &zprog.from, 0, 0);
 	a = zprog.to;
 	if(offset != 0) {
 		a.offset = offset;
 		a.type = D_CONST;
 	}
-	zaddr(b, &a, 0);
+	zaddr(b, &a, 0, 0);
 }
 
 void
-zaddr(Biobuf *b, Addr *a, int s)
+zaddr(Biobuf *b, Addr *a, int s, int gotype)
 {
 	int32 l;
 	uint64 e;
@@ -95,6 +95,7 @@ zaddr(Biobuf *b, Addr *a, int s)
 		Bputc(b, a->reg);
 		Bputc(b, s);
 		Bputc(b, a->name);
+		Bputc(b, gotype);
 	}
 
 	switch(a->type) {
@@ -167,20 +168,66 @@ zaddr(Biobuf *b, Addr *a, int s)
 	}
 }
 
+static struct {
+	struct { Sym *sym; short type; } h[NSYM];
+	int sym;
+} z;
+
+static void
+zsymreset(void)
+{
+	for(z.sym=0; z.sym<NSYM; z.sym++) {
+		z.h[z.sym].sym = S;
+		z.h[z.sym].type = 0;
+	}
+	z.sym = 1;
+}
+
+static int
+zsym(Sym *s, int t, int *new)
+{
+	int i;
+
+	*new = 0;
+	if(s == S)
+		return 0;
+
+	i = s->sym;
+	if(i < 0 || i >= NSYM)
+		i = 0;
+	if(z.h[i].type == t && z.h[i].sym == s)
+		return i;
+	i = z.sym;
+	s->sym = i;
+	zname(bout, s, t);
+	z.h[i].sym = s;
+	z.h[i].type = t;
+	if(++z.sym >= NSYM)
+		z.sym = 1;
+	*new = 1;
+	return i;
+}
+
+static int
+zsymaddr(Addr *a, int *new)
+{
+	int t;
+
+	t = a->name;
+	if(t == D_ADDR)
+		t = a->name;
+	return zsym(a->sym, t, new);
+}
+
 void
 dumpfuncs(void)
 {
 	Plist *pl;
-	int sf, st, t, sym;
-	struct { Sym *sym; short type; } h[NSYM];
+	int sf, st, gf, gt, new;
 	Sym *s;
 	Prog *p;
 
-	for(sym=0; sym<NSYM; sym++) {
-		h[sym].sym = S;
-		h[sym].type = 0;
-	}
-	sym = 1;
+	zsymreset();
 
 	// fix up pc
 	pcloc = 0;
@@ -210,53 +257,20 @@ dumpfuncs(void)
 		}
 
 		for(p=pl->firstpc; p!=P; p=p->link) {
-		jackpot:
-			sf = 0;
-			s = p->from.sym;
-			while(s != S) {
-				sf = s->sym;
-				if(sf < 0 || sf >= NSYM)
-					sf = 0;
-				t = p->from.name;
-				if(t == D_ADDR)
-					t = p->from.name;
-				if(h[sf].type == t)
-				if(h[sf].sym == s)
-					break;
-				s->sym = sym;
-				zname(bout, s, t);
-				h[sym].sym = s;
-				h[sym].type = t;
-				sf = sym;
-				sym++;
-				if(sym >= NSYM)
-					sym = 1;
-				break;
-			}
-			st = 0;
-			s = p->to.sym;
-			while(s != S) {
-				st = s->sym;
-				if(st < 0 || st >= NSYM)
-					st = 0;
-				t = p->to.name;
-				if(t == D_ADDR)
-					t = p->to.name;
-				if(h[st].type == t)
-				if(h[st].sym == s)
-					break;
-				s->sym = sym;
-				zname(bout, s, t);
-				h[sym].sym = s;
-				h[sym].type = t;
-				st = sym;
-				sym++;
-				if(sym >= NSYM)
-					sym = 1;
-				if(st == sf)
-					goto jackpot;
+			for(;;) {
+				sf = zsymaddr(&p->from, &new);
+				gf = zsym(p->from.gotype, D_EXTERN, &new);
+				if(new && sf == gf)
+					continue;
+				st = zsymaddr(&p->to, &new);
+				if(new && (st == sf || st == gf))
+					continue;
+				gt = zsym(p->to.gotype, D_EXTERN, &new);
+				if(new && (gt == sf || gt == gf || gt == st))
+					continue;
+				break;
+			}
+
 			Bputc(bout, p->as);
 			Bputc(bout, p->scond);
  			Bputc(bout, p->reg);
@@ -264,8 +278,8 @@ dumpfuncs(void)
 			Bputc(bout, p->lineno>>8);
 			Bputc(bout, p->lineno>>16);
 			Bputc(bout, p->lineno>>24);
-			zaddr(bout, &p->from, sf);
-			zaddr(bout, &p->to, st);
+			zaddr(bout, &p->from, sf, gf);
+			zaddr(bout, &p->to, st, gt);
 		}
 	}
 }

src/cmd/5l/obj.c

このファイルはGoリンカのバックエンドの一部であり、オブジェクトファイルを読み込み、最終的な実行可能ファイルを生成する役割を担っています。

--- a/src/cmd/5l/obj.c
+++ b/src/cmd/5l/obj.c
@@ -280,8 +280,21 @@ main(int argc, char *argv[])
 	errorexit();
 }
 
+static Sym*
+zsym(char *pn, Biobuf *f, Sym *h[])
+{	
+	int o;
+	
+	o = BGETC(f);
+	if(o == 0)
+		return S;
+	if(o < 0 || o >= NSYM || h[o] == nil)
+		mangle(pn);
+	return h[o];
+}
+
 static void
-zaddr(Biobuf *f, Adr *a, Sym *h[])
+zaddr(char *pn, Biobuf *f, Adr *a, Sym *h[])
 {
 	int i, c;
 	int32 l;
@@ -298,6 +311,7 @@ zaddr(Biobuf *f, Adr *a, Sym *h[])
 	}
 	a->sym = h[c];
 	a->name = BGETC(f);
+	adrgotype = zsym(pn, f, h);
 
 	if((schar)a->reg < 0 || a->reg > NREG) {
 		print("register out of range %d\n", a->reg);
@@ -358,8 +372,11 @@ zaddr(Biobuf *f, Adr *a, Sym *h[])
 	if(s == S)
 		return;
 	i = a->name;
-	if(i != D_AUTO && i != D_PARAM)
+	if(i != D_AUTO && i != D_PARAM) {
+		if(s && adrgotype)
+			s->gotype = adrgotype;
 		return;
+	}
 
 	l = a->offset;
 	for(u=curauto; u; u=u->link)
@@ -367,6 +384,8 @@ zaddr(Biobuf *f, Adr *a, Sym *h[])
 		if(u->type == i) {
 			if(u->aoffset > l)
 				u->aoffset = l;
+			if(adrgotype)
+				u->gotype = adrgotype;
 			return;
 		}
 
@@ -376,6 +395,7 @@ zaddr(Biobuf *f, Adr *a, Sym *h[])
 	u->asym = s;
 	u->aoffset = l;
 	u->type = i;
+	u->gotype = adrgotype;
 }
 
 void
@@ -484,8 +504,9 @@ loop:
 	p->reg = BGETC(f);
 	p->line = Bget4(f);
 
-	zaddr(f, &p->from, h);
-	zaddr(f, &p->to, h);
+	zaddr(pn, f, &p->from, h);
+	fromgotype = adrgotype;
+	zaddr(pn, f, &p->to, h);
 
 	if(p->as != ATEXT && p->as != AGLOBL && p->reg > NREG)
 		diag("register out of range %A %d", p->as, p->reg);
@@ -611,6 +632,11 @@ loop:
 		etextp->next = s;
 	else
 		textp = s;
+	if(fromgotype) {
+		if(s->gotype && s->gotype != fromgotype)
+			diag("%s: type mismatch for %s", pn, s->name);
+		s->gotype = fromgotype;
+	}
 	etextp = s;
 	p->align = 4;
 	autosize = (p->to.offset+3L) & ~3L;

コアとなるコードの解説

src/cmd/5g/gobj.c の変更点

  • zaddr関数のシグネチャ変更とgotypeの書き込み:

    • zaddr関数は、Goコンパイラがオブジェクトファイルにアドレス情報を書き込むための関数です。変更前はvoid zaddr(Biobuf *b, Addr *a, int s)でしたが、変更後はvoid zaddr(Biobuf *b, Addr *a, int s, int gotype)となり、gotypeという新しい整数パラメータが追加されました。
    • このgotypeパラメータの値は、Bputc(b, gotype);によってオブジェクトファイルに書き込まれます。これにより、各アドレスに関連付けられたGoの型情報がオブジェクトファイルに永続化されるようになります。
  • zsymreset, zsym, zsymaddr関数の導入:

    • これらの関数は、Goの型シンボルを効率的に管理するための新しいメカニズムです。
    • zsymresetは、シンボルキャッシュをリセットします。
    • zsym(Sym *s, int t, int *new)は、与えられたシンボルsと型tに対して、オブジェクトファイル内で使用する一意のインデックスを割り当てます。newポインタは、新しいシンボルが割り当てられたかどうかを示します。この関数は、シンボルテーブルz.hを使用して、既に登録されているシンボルを再利用し、オブジェクトファイルのサイズを削減します。
    • zsymaddr(Addr *a, int *new)は、Addr構造体からシンボル情報を抽出し、zsymを呼び出してそのシンボルに対応するインデックスを取得するヘルパー関数です。
  • dumpfuncs関数の大幅な変更:

    • dumpfuncsは、Goの関数に関する情報をオブジェクトファイルにダンプする主要な関数です。
    • 変更前は、hというローカルなハッシュテーブルを使用してシンボルを管理していましたが、これは新しいzsym関連の関数に置き換えられました。
    • 新しいコードでは、p->fromp->toのアドレスに対してzsymaddrを呼び出してシンボルインデックス(sf, st)を取得し、さらにp->from.gotypep->to.gotypeからgotypeシンボルインデックス(gf, gt)を取得しています。
    • これらのシンボルインデックス(sf, st, gf, gt)は、最終的にzaddr関数に渡され、オブジェクトファイルに書き込まれます。これにより、命令のオペランドだけでなく、そのオペランドが参照するGoの型情報もオブジェクトファイルに正確に記録されるようになります。

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

  • リンカ側のzsym関数の導入:

    • リンカ側にもstatic Sym* zsym(char *pn, Biobuf *f, Sym *h[])という同名の関数が導入されました。これは、オブジェクトファイルから読み取ったシンボルインデックス(バイト値)を、リンカが内部で管理するSym*ポインタにマッピングするためのものです。これにより、リンカはコンパイラが書き込んだgotypeシンボルを正しく解釈できます。
  • リンカ側のzaddr関数のシグネチャ変更とgotypeの読み込み:

    • リンカ側のzaddr関数もstatic void zaddr(char *pn, Biobuf *f, Adr *a, Sym *h[])にシグネチャが変更されました。
    • この関数内で、adrgotype = zsym(pn, f, h);という行が追加され、オブジェクトファイルからgotype情報を読み取り、グローバル変数adrgotypeに格納するようになりました。
    • さらに、D_AUTOD_PARAMといった自動変数やパラメータのアドレスを処理する際に、if(s && adrgotype) s->gotype = adrgotype;if(adrgotype) u->gotype = adrgotype;といったロジックが追加されました。これは、読み取ったgotype情報を、リンカが管理するシンボル(s)や自動変数情報(u)に伝播させるためのものです。これにより、最終的なデバッグ情報に正しい型が関連付けられます。
  • loop関数でのfromgotypeの処理:

    • loop関数は、オブジェクトファイル内の各命令を読み込み、処理するリンカの主要なループです。
    • zaddr(pn, f, &p->from, h);の呼び出し後、fromgotype = adrgotype;によって、p->fromアドレスに関連付けられたgotypefromgotypeグローバル変数に保存されます。
    • 関数の処理の最後に、if(fromgotype) { ... s->gotype = fromgotype; }というブロックが追加されました。これは、関数のシンボル(s)に、その関数が持つgotype情報を関連付けるためのものです。これにより、関数全体の型情報がリンカによって正しく処理され、DWARFデバッグ情報に反映されるようになります。

これらの変更は、Goのコンパイラとリンカが連携して、Go独自の型情報をオブジェクトファイルと最終的なバイナリのDWARFデバッグ情報に正確に埋め込むための基盤を構築しています。

関連リンク

参考にした情報源リンク

  • DWARF Debugging Standard: https://dwarfstd.org/
  • Go Toolchain Documentation (General): https://go.dev/doc/
  • Understanding Go's Type System (General Concept): https://go.dev/blog/go-type-system (これは一般的なGoの型システムに関するブログ記事であり、直接このコミットの技術詳細を説明するものではありませんが、gotypeの背景理解に役立ちます。)
  • Go Source Code (for context of cmd/5g, cmd/5l, libmach): https://github.com/golang/go
  • Go Assembly Language (for context of cmd/5a): https://go.dev/doc/asm
  • Go Object File Format (historical context, as this commit modifies it): Specific documentation on Go's internal object file format is scarce and often requires reading the source code. This commit itself is a part of defining that format.