[インデックス 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
)がその情報を正しく処理できるようにすることです。
-
Addr
構造体の拡張:src/cmd/5g/gsubr.c
とsrc/libmach/5obj.c
で、Addr
構造体(アドレス情報を表現する内部構造体)にgotype
フィールドが追加されました。これは、特定のアドレスに関連付けられたGoの型情報を保持するためのものです。src/cmd/5a/lex.c
とsrc/cmd/5c/swt.c
では、zaddr
関数が呼び出される際に、新しいgotype
フィールドのためのゼロバイトが追加で書き込まれるようになっています。これは、オブジェクトファイルのフォーマット変更に対応するためです。
-
zaddr
関数の変更:src/cmd/5g/gg.h
とsrc/cmd/5g/gobj.c
で、zaddr
関数のシグネチャが変更され、gotype
を表す新しいint
パラメータが追加されました。この関数は、オブジェクトファイルにアドレス情報を書き込む際に使用されます。src/cmd/5l/obj.c
では、リンカ側のzaddr
関数も変更され、オブジェクトファイルからgotype
情報を読み取るようになりました。adrgotype
というグローバル変数が導入され、読み取ったgotype
シンボルを一時的に保持します。
-
gotype
シンボルの管理 (cmd/5g/gobj.c
):cmd/5g/gobj.c
に、zsymreset
,zsym
,zsymaddr
という新しい静的関数が導入されました。これらは、gotype
シンボルを効率的に管理するためのハッシュテーブルのようなメカニズムを提供します。zsym
関数は、Goの型シンボル(Sym*
)と型(int
)を受け取り、そのシンボルが既に登録されている場合はそのインデックスを返し、新しい場合は登録して新しいインデックスを返します。これにより、オブジェクトファイル内でgotype
情報をコンパクトに表現できます。dumpfuncs
関数(Go関数の情報をオブジェクトファイルにダンプする関数)が大幅に修正されました。以前は単純なシンボル管理を行っていましたが、新しいzsymaddr
とzsym
関数を使用して、p->from
とp->to
のアドレスに関連付けられたgotype
情報を取得し、オブジェクトファイルに書き込むようになりました。
-
リンカでの
gotype
情報の処理 (cmd/5l/obj.c
):- リンカは、コンパイラが生成したオブジェクトファイルから
gotype
情報を読み取ります。 zsym
という新しいヘルパー関数が導入され、オブジェクトファイルから読み取ったシンボルインデックスを実際のSym*
ポインタに変換します。zaddr
関数内で、読み取ったgotype
情報がadrgotype
グローバル変数に格納されます。- リンカは、
D_AUTO
(自動変数)やD_PARAM
(パラメータ)などの特定の型のアドレスに対して、関連するシンボル(s
)や自動変数情報(u
)にgotype
情報を関連付けるロジックが追加されました。これにより、デバッグ情報として正しい型が伝播されます。 loop
関数(オブジェクトファイルの命令を処理するメインループ)内で、p->from
のアドレスから読み取られたgotype
がfromgotype
に格納され、最終的に関数のシンボル(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->from
とp->to
のアドレスに対してzsymaddr
を呼び出してシンボルインデックス(sf
,st
)を取得し、さらにp->from.gotype
とp->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_AUTO
やD_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
アドレスに関連付けられたgotype
がfromgotype
グローバル変数に保存されます。- 関数の処理の最後に、
if(fromgotype) { ... s->gotype = fromgotype; }
というブロックが追加されました。これは、関数のシンボル(s
)に、その関数が持つgotype
情報を関連付けるためのものです。これにより、関数全体の型情報がリンカによって正しく処理され、DWARFデバッグ情報に反映されるようになります。
これらの変更は、Goのコンパイラとリンカが連携して、Go独自の型情報をオブジェクトファイルと最終的なバイナリのDWARFデバッグ情報に正確に埋め込むための基盤を構築しています。
関連リンク
- Go Issue 3747: https://github.com/golang/go/issues/3747
- Go Issue 4912: https://github.com/golang/go/issues/4912
- Gerrit Change-ID: https://golang.org/cl/7376054
参考にした情報源リンク
- 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.