[インデックス 1210] ファイルの概要
このコミットは、Go言語のツールチェインにおけるシンボルテーブルの管理と利用方法に重要な変更を加えています。具体的には、シンボルテーブルにGo言語の型情報(gotype
)を追加し、リンカ(6l
)がこの型情報をシンボルに紐付けるように修正しています。さらに、シンボルテーブルとPC/行番号テーブルを特定のメモリ領域にロードし、Goランタイムからこれらの生データにアクセスするための新しいシステムコールsys.symdat()
を導入しています。これにより、デバッグやプロファイリング、リフレクションといった高度なランタイム操作のための基盤が強化されています。
コミット
commit 67addd4e11f147125952b0d4b50c1ed2563129e9
Author: Russ Cox <rsc@golang.org>
Date: Thu Nov 20 17:32:18 2008 -0800
symbol table changes
* add gotype string to symbol table
* fill in gotype in 6l for known funcs/vars
* print gotype with nm -t
* load symbol and pc/ln tables into memory at magic address 0x99<<32.
* add sys.symdat() to retrieve raw bytes of symbol table
and pc/ln table.
most of this should be considered experimental
and subject to change.
R=r
DELTA=157 (128 added, 0 deleted, 29 changed)
OCL=19746
CL=19750
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/67addd4e11f147125952b0d4b50c1ed2563129e9
元コミット内容
symbol table changes
* add gotype string to symbol table
* fill in gotype in 6l for known funcs/vars
* print gotype with nm -t
* load symbol and pc/ln tables into memory at magic address 0x99<<32.
* add sys.symdat() to retrieve raw bytes of symbol table
and pc/ln table.
most of this should be considered experimental
and subject to change.
R=r
DELTA=157 (128 added, 0 deleted, 29 changed)
OCL=19746
CL=19750
変更の背景
このコミットが行われた2008年当時、Go言語はまだ初期開発段階にありました。Goの設計目標の一つに、効率的なコンパイルと実行、そして強力なデバッグ・プロファイリング機能の提供がありました。シンボルテーブルは、コンパイルされたバイナリ内の関数、変数、その他のエンティティに関する情報(名前、アドレス、型など)を格納する重要なデータ構造です。
この変更の主な背景は以下の点にあると考えられます。
- 型情報の強化: 従来のシンボルテーブルには、Go言語特有の豊富な型情報が十分に格納されていませんでした。
gotype
の導入により、リンカがGoの関数や変数の正確な型情報をシンボルテーブルに埋め込むことが可能になり、デバッガやプロファイラ、あるいは将来的なリフレクション機能がより詳細な型情報にアクセスできるようになります。 - ランタイムからのシンボル情報アクセス: デバッグやプロファイリング、あるいは高度なランタイム機能(例: スタックトレースの生成、ガベージコレクションの最適化)を実現するためには、実行中のプログラムが自身のシンボル情報やPC(プログラムカウンタ)とソースコードの行番号のマッピング情報(PC/行番号テーブル)にアクセスできる必要があります。このコミットは、これらのテーブルをメモリにロードし、
sys.symdat()
というシステムコールを通じてランタイムに公開することで、その基盤を構築しています。 - 実験的な機能の導入: コミットメッセージに「most of this should be considered experimental and subject to change.」とあるように、これはGo言語の初期段階における実験的な試みであり、将来の設計変更を視野に入れたものでした。Goのランタイムとツールチェインの連携を深めるための重要な一歩でした。
前提知識の解説
このコミットを理解するためには、以下の概念について知っておく必要があります。
-
シンボルテーブル (Symbol Table): コンパイラやリンカが生成するデータ構造で、プログラム内の識別子(変数名、関数名など)とそれに対応する情報(メモリ上のアドレス、型、スコープなど)をマッピングします。デバッガはシンボルテーブルを利用して、実行中のプログラムの変数値を表示したり、関数呼び出しのスタックトレースを解決したりします。
-
PC/行番号テーブル (PC/Line Table): プログラムカウンタ(PC、現在実行中の命令のアドレス)とソースコードのファイル名および行番号をマッピングするテーブルです。これにより、実行中のプログラムのどの部分がソースコードのどの行に対応するのかを特定できます。デバッグ時のスタックトレース表示や、プロファイリング時のホットスポット特定に不可欠です。
-
Goリンカ (
6l
): Go言語のコンパイルプロセスにおいて、コンパイラが生成したオブジェクトファイル(.o
ファイル)を結合し、実行可能なバイナリを生成するツールです。このコミットでは、6l
がシンボルテーブルにgotype
情報を追加し、シンボルテーブルとPC/行番号テーブルをバイナリに埋め込む処理が変更されています。 -
nm
コマンド: Unix系のシステムで利用されるツールで、オブジェクトファイルや実行可能ファイル内のシンボル(関数名、変数名など)をリスト表示します。このコミットでは、nm
コマンドに-t
オプションが追加され、シンボルに関連付けられたgotype
を表示できるようになっています。 -
Goランタイム (Go Runtime): Goプログラムの実行を管理するシステムです。ガベージコレクション、スケジューリング、システムコールインターフェースなどを担当します。このコミットでは、ランタイムがシンボルテーブルとPC/行番号テーブルにアクセスするための
sys.symdat()
関数が追加されています。 -
ELF (Executable and Linkable Format): Unix系システムで広く使われている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準フォーマットです。GoのバイナリもELF形式で生成されます。このコミットでは、ELFヘッダの構造やセグメントの配置に関する変更が含まれています。
技術的詳細
このコミットは、Go言語のツールチェインとランタイムの複数のコンポーネントにわたる協調的な変更を含んでいます。
-
Sym
構造体へのgotype
の追加:include/mach_amd64.h
において、シンボルを表すSym
構造体にchar *gotype;
フィールドが追加されました。これにより、各シンボルがGo言語の型情報を文字列として保持できるようになります。 -
リンカ(
6l
)によるgotype
の埋め込み:src/cmd/6l/go.c
にgotypefor(char *name)
関数が追加されました。この関数は、シンボル名からGoの型情報を抽出するロジックを含んでいます。具体的には、シンボル名に含まれる特定のパターン(例:.
や·
)を解析し、それが関数や変数の型情報である場合にその型文字列を返します。src/cmd/6l/span.c
のputsymb
関数が変更され、新しい引数char *go
(gotype
)を受け取るようになりました。この関数は、シンボルテーブルにシンボル情報を書き込む際に、gotype
も一緒に書き込みます。src/cmd/6l/span.c
のasmsym
関数では、SCONST
(定数)、SDATA
(データ)、SBSS
(BSSセクション)のシンボルに対してgotypefor(s->name)
を呼び出し、その結果をputsymb
に渡すように変更されました。これにより、既知の関数や変数に対してGoの型情報がシンボルテーブルに埋め込まれるようになります。
-
シンボルテーブルとPC/行番号テーブルのメモリロード:
src/cmd/6l/asm.c
において、シンボルテーブルとPC/行番号テーブルを特定の「マジックアドレス」0x99LL<<32
(0x9900000000
)にロードするロジックが追加されました。これは、Goのバイナリが実行時にこれらのテーブルを特定の固定アドレスから参照できるようにするためのものです。- ELFヘッダの生成ロジックも変更され、
__SYMDAT
という新しいセグメントが追加されました。このセグメントは、シンボルテーブルとPC/行番号テーブルの生データを格納し、実行時に0x99LL<<32
にマッピングされます。
-
sys.symdat()
システムコールの導入:src/cmd/gc/sys.go
とsrc/cmd/gc/sysimport.c
において、export func sys.symdat() (symtab *[]byte, pclntab *[][]byte)
という新しいシステムコールが宣言・追加されました。src/runtime/runtime.c
にsys.symdat
関数の実装が追加されました。この関数は、マジックアドレス0x99LL<<32
からシンボルテーブルとPC/行番号テーブルの生データを読み取り、それぞれをバイトスライスとしてGoランタイムに返します。これにより、Goプログラムは実行時に自身のシンボル情報やデバッグ情報にアクセスできるようになります。
-
nm
ツールでのgotype
表示:src/cmd/nm/nm.c
において、nm
コマンドに新しいオプション-t
が追加されました。printsyms
関数が変更され、-t
オプションが指定された場合、シンボルのgotype
フィールドが存在すればそれを表示するように修正されました。
これらの変更により、Goのバイナリはよりリッチなデバッグ情報を持ち、ランタイムがその情報にアクセスできる基盤が構築されました。
コアとなるコードの変更箇所
include/mach_amd64.h
(Sym構造体への gotype
追加)
--- a/include/mach_amd64.h
+++ b/include/mach_amd64.h
@@ -84,6 +84,7 @@ struct Sym
uint sig;
char type;
char *name;
+ char *gotype;
};
/*
* End of Plan 9 a.out.h
src/cmd/6l/go.c
( gotypefor
関数の追加)
--- a/src/cmd/6l/go.c
+++ b/src/cmd/6l/go.c
@@ -70,6 +70,27 @@ ilookup(char *name)
return x;
}
+char*
+gotypefor(char *name)
+{
+ Import *x;
+ char *s, *p;
+
+ s = strdup(name);
+ p = utfrune(s, 0xB7); // center dot
+ if(p == nil)
+ return nil;
+ *p++ = '.';
+ memmove(p, p+1, strlen(p));
+ x = ilookup(s);
+ free(s);
+ if(x == nil || x->prefix == nil)
+ return nil;
+ if(strcmp(x->prefix, "var") != 0 && strcmp(x->prefix, "func") != 0)
+ return nil;
+ return x->def;
+}
+
static void loadpkgdata(char*, char*, int);
static int parsemethod(char**, char*, char**);
static int parsepkgdata(char*, char**, char*, int*, char**, char**, char**);
src/cmd/6l/span.c
( putsymb
関数の変更と asmsym
での利用)
--- a/src/cmd/6l/span.c
+++ b/src/cmd/6l/span.c
@@ -150,9 +150,9 @@ xdefine(char *p, int t, vlong v)
}
void
-putsymb(char *s, int t, vlong v, int ver)
+putsymb(char *s, int t, vlong v, int ver, char *go)
{
- int i, f, l;
+ int i, j, f, l;
if(t == 'f')
s++;
@@ -181,7 +181,13 @@ putsymb(char *s, int t, vlong v, int ver)
cput(s[i]);
cput(0);
}
- symsize += l + 1 + i + 1;
+ j = 0;
+ if(go) {
+ for(j=0; go[j]; j++)
+ cput(go[j]);
+ }
+ cput(0);
+ symsize += l + 1 + i + 1 + j + 1;
if(debug['n']) {
if(t == 'z' || t == 'Z') {
@@ -194,9 +200,9 @@ putsymb(char *s, int t, vlong v, int ver)
return;
}
if(ver)
- Bprint(&bso, "%c %.8llux %s<%d>\n", t, v, s, ver);
+ Bprint(&bso, "%c %.8llux %s<%d> %s\n", t, v, s, ver, go);
else
- Bprint(&bso, "%c %.8llux %s\n", t, v, s);
+ Bprint(&bso, "%c %.8llux %s %s\n", t, v, s, go);
}
}
@@ -210,25 +216,25 @@ asmsym(void)
s = lookup("etext", 0);
if(s->type == STEXT)
- putsymb(s->name, 'T', s->value, s->version);
+ putsymb(s->name, 'T', s->value, s->version, nil);
for(h=0; h<NHASH; h++)
for(s=hash[h]; s!=S; s=s->link)
switch(s->type) {
case SCONST:
- putsymb(s->name, 'D', s->value, s->version);
+ putsymb(s->name, 'D', s->value, s->version, gotypefor(s->name));
continue;
case SDATA:
- putsymb(s->name, 'D', s->value+INITDAT, s->version);
+ putsymb(s->name, 'D', s->value+INITDAT, s->version, gotypefor(s->name));
continue;
case SBSS:
- putsymb(s->name, 'B', s->value+INITDAT, s->version);
+ putsymb(s->name, 'B', s->value+INITDAT, s->version, gotypefor(s->name));
continue;
case SFILE:
- putsymb(s->name, 'f', s->value, s->version);
+ putsymb(s->name, 'f', s->value, s->version, nil);
continue;
}
@@ -240,22 +246,23 @@ asmsym(void)
/* filenames first */
for(a=p->to.autom; a; a=a->link)
if(a->type == D_FILE)
- putsymb(a->asym->name, 'z', a->aoffset, 0);
+ putsymb(a->asym->name, 'z', a->aoffset, 0, nil);
else
if(a->type == D_FILE1)
- putsymb(a->asym->name, 'Z', a->aoffset, 0);
+ putsymb(a->asym->name, 'Z', a->aoffset, 0, nil);
- putsymb(s->name, 'T', s->value, s->version);
+ putsymb(s->name, 'T', s->value, s->version, gotypefor(s->name));
/* frame, auto and param after */
- putsymb(".frame", 'm', p->to.offset+8, 0);
+ putsymb(".frame", 'm', p->to.offset+8, 0, nil);
+ /* TODO(rsc): Add types for D_AUTO and D_PARAM */
for(a=p->to.autom; a; a=a->link)
if(a->type == D_AUTO)
- putsymb(a->asym->name, 'a', -a->aoffset, 0);
+ putsymb(a->asym->name, 'a', -a->aoffset, 0, nil);
else
if(a->type == D_PARAM)
- putsymb(a->asym->name, 'p', a->aoffset, 0);
+ putsymb(a->asym->name, 'p', a->aoffset, 0, nil);
}
if(debug['v'] || debug['n'])
Bprint(&bso, "symsize = %lud\n", symsize);
src/runtime/runtime.c
( sys.symdat
の実装)
--- a/src/runtime/runtime.c
+++ b/src/runtime/runtime.c
@@ -743,3 +743,34 @@ algarray[3] =
// { pointerhash, pointerequal, pointerprint, pointercopy }, // 2
{ memhash, memequal, memprint, memcopy }, // 2 - treat pointers as ints
};
+
+
+// Return a pointer to a byte array containing the symbol table segment.
+//
+// NOTE(rsc): I expect that we will clean up both the method of getting
+// at the symbol table and the exact format of the symbol table at some
+// point in the future. It probably needs to be better integrated with
+// the type strings table too. This is just a quick way to get started
+// and figure out what we want from/can do with it.
+void
+sys·symdat(Array *symtab, Array *pclntab)
+{
+ Array *a;
+ int32 *v;
+
+ v = (int32*)(0x99LL<<32); /* known to 6l */
+
+ a = mal(sizeof *a);
+ a->nel = v[0];
+ a->cap = a->nel;
+ a->array = (byte*)&v[2];
+ symtab = a;
+ FLUSH(&symtab);
+
+ a = mal(sizeof *a);
+ a->nel = v[1];
+ a->cap = a->nel;
+ a->array = (byte*)&v[2] + v[0];
+ pclntab = a;
+ FLUSH(&pclntab);
+}
src/cmd/nm/nm.c
( nm -t
オプションの追加)
--- a/src/cmd/nm/nm.c
+++ b/src/cmd/nm/nm.c
@@ -52,6 +52,7 @@ int nflag;
int sflag;
int uflag;
int Tflag;
+int tflag;
Sym **fnames; /* file path translation table */
Sym **symptr;
@@ -90,6 +91,7 @@ main(int argc, char *argv[])
case 'n': nflag = 1; break;
case 's': sflag = 1; break;
case 'u': uflag = 1; break;
+ case 't': tflag = 1; break;
case 'T': Tflag = 1; break;
} ARGEND
if (argc == 0)
@@ -298,7 +300,7 @@ printsyms(Sym **symptr, long nsym)
if(!sflag)
qsort(symptr, nsym, sizeof(*symptr), (void*)cmp);
-
+
wid = 0;
for (i=0; i<nsym; i++) {
s = symptr[i];
@@ -306,7 +308,7 @@ printsyms(Sym **symptr, long nsym)
if (s->value >= 0x100000000LL && wid == 8)
wid = 16;
}
+ }
for (i=0; i<nsym; i++) {
s = symptr[i];
if (multifile && !hflag)
@@ -322,7 +324,10 @@ printsyms(Sym **symptr, long nsym)
Bprint(&bout, "%*llux ", wid, s->value);
else
Bprint(&bout, "%*s ", wid, "");
- Bprint(&bout, "%c %s\n", s->type, cp);
+ Bprint(&bout, "%c %s", s->type, cp);
+ if(tflag && s->gotype && s->gotype[0])
+ Bprint(&bout, " %s", s->gotype);
+ Bprint(&bout, "\n");
}
}
コアとなるコードの解説
Sym
構造体へのgotype
追加
include/mach_amd64.h
のSym
構造体にchar *gotype;
が追加されたことで、各シンボルがGo言語の型情報を指すポインタを持つことができるようになりました。これにより、シンボルテーブルが単なる名前とアドレスのマッピングだけでなく、よりセマンティックな型情報も保持できるようになります。
gotypefor
関数 (src/cmd/6l/go.c
)
この関数は、Goのリンカがシンボル名からGoの型情報を推測するために使用されます。Goの内部的なシンボル名には、型情報を示す特定のパターンが含まれることがあります(例: main.init·f
のような関数名)。gotypefor
は、これらのパターンを解析し、対応する型文字列を返します。この関数は、リンカがシンボルテーブルに正確な型情報を埋め込むための重要な役割を担っています。
putsymb
関数の変更とasmsym
での利用 (src/cmd/6l/span.c
)
putsymb
関数は、シンボル情報をバイナリのシンボルテーブルに書き込む役割を担っています。この変更により、gotype
引数が追加され、シンボル名だけでなく型情報もシンボルテーブルに格納されるようになりました。
asmsym
関数は、リンカがシンボルテーブルを構築する主要な場所です。この関数内で、SCONST
, SDATA
, SBSS
といったデータシンボルや、STEXT
(関数)シンボルに対してgotypefor
を呼び出し、その結果をputsymb
に渡しています。これにより、Goのコンパイル済みバイナリのシンボルテーブルには、関数や変数のGo型情報が埋め込まれるようになります。
sys.symdat
の実装 (src/runtime/runtime.c
)
sys.symdat
は、Goランタイムが自身のシンボルテーブルとPC/行番号テーブルにアクセスするためのシステムコールです。この関数は、リンカによって特定の「マジックアドレス」0x99LL<<32
に配置されたシンボルデータとPC/行番号データを指すポインタを取得します。そして、これらの生データをGoのバイトスライス(Array
構造体)としてラップし、呼び出し元に返します。
この機能は、Goのデバッガ(gdb
やdlv
など)がGoプログラムの内部状態をより深く理解するために利用されたり、プロファイリングツールが実行中のコードのどの部分がボトルネックになっているかを特定するために利用されたりします。また、リフレクション機能の基盤としても機能し、実行時に型情報や関数情報を動的に取得することを可能にします。
nm -t
オプションの追加 (src/cmd/nm/nm.c
)
nm
コマンドは、バイナリ内のシンボルを検査するための標準的なツールです。-t
オプションの追加により、nm
はシンボル名だけでなく、シンボルに関連付けられたGoの型情報も表示できるようになりました。これは、開発者がバイナリのデバッグ情報をより詳細に確認する際に役立ちます。
関連リンク
- Go言語の初期開発に関する情報: https://go.dev/doc/history
- Go言語のツールチェインに関するドキュメント: https://go.dev/doc/ (特に
cmd/go
やcmd/link
に関する部分)
参考にした情報源リンク
- Go言語のソースコード (GitHub): https://github.com/golang/go
- ELFファイルフォーマットに関する一般的な情報 (Wikipediaなど)
- シンボルテーブルとデバッグ情報に関する一般的な情報 (コンパイラ理論やリンカの書籍など)
- Go言語の初期のコミットログとデザインドキュメント (GoプロジェクトのメーリングリストやIssueトラッカー)