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

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

このコミットは、Go言語のランタイムとリンカにおいて、Func構造体から不要なフィールドを削除する変更です。これにより、Goバイナリのサイズ削減と、シンボルテーブルの効率化が図られています。

コミット

commit c75884185332458242c03b17014bc3801977f684
Author: Russ Cox <rsc@golang.org>
Date:   Fri Jul 19 18:52:35 2013 -0400

    cmd/ld, runtime: remove unused fields from Func
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/11604043

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

https://github.com/golang/go/commit/c75884185332458242c03b17014bc3801977f684

元コミット内容

cmd/ld, runtime: remove unused fields from Func

R=golang-dev, r
CC=golang-dev
https://golang.org/cl/11604043

変更の背景

この変更は、Go 1.2のシンボルテーブル(pclntab)の構造変更に関連しています。Go 1.2では、プログラムカウンタ(PC)と行番号、ファイル名、スタックポインタ(SP)などの情報をマッピングするための新しい形式が導入されました。これに伴い、以前のバージョンで使用されていたFunc構造体の一部のフィールド(x1, x2, x3)が不要になりました。これらのフィールドは、将来的な使用のために予約されていたり、古い情報を含んでいたりしましたが、新しいpclntabの設計により、その役割が置き換えられたため、削除することが可能になりました。

不要なフィールドを削除することで、Goバイナリのサイズを削減し、メモリ使用量を最適化することができます。また、シンボルテーブルの構造がよりシンプルになり、デバッグ情報の処理効率も向上します。

前提知識の解説

Go言語のシンボルテーブル (pclntab)

Go言語の実行可能ファイルには、デバッグ情報やプロファイリング情報を提供するための「PC-line table (pclntab)」と呼ばれるセクションが含まれています。このテーブルは、特定のプログラムカウンタ(PC、つまり実行中の命令のアドレス)がどのソースファイルのどの行に対応するか、どの関数に属するか、スタックフレームのサイズはどのくらいか、といった情報を提供します。

pclntabは、主に以下の目的で使用されます。

  • スタックトレースの生成: パニック発生時やデバッガが停止した際に、現在の実行パスを人間が読める形式で表示するため。
  • プロファイリング: どのコード行がCPU時間を消費しているかを特定するため。
  • デバッグ: デバッガがソースコードと実行中のバイナリを関連付けるため。

Func構造体

Func構造体は、Goのランタイム内部で個々のGo関数に関するメタデータを保持するために使用されます。この構造体はpclntabの一部として格納され、関数のエントリポイント、名前、引数のサイズ、スタックフレーム情報、PC-lineテーブルへのオフセットなど、関数に関する重要な情報を含んでいます。

Go 1.2以前のFunc構造体には、将来の拡張や互換性のために予約されたり、古い情報が残っていたりするフィールドが存在していました。これらのフィールドは、新しいpclntabの設計によってその役割が不要になったため、削除の対象となりました。

Goリンカ (cmd/ld)

Goリンカ(cmd/ld)は、Goのソースコードをコンパイルして生成されたオブジェクトファイル(.oファイル)を結合し、最終的な実行可能ファイルを生成するツールです。リンカの役割の一つに、pclntabを含む実行可能ファイルの構造を構築することがあります。Func構造体のレイアウト変更は、リンカがpclntabを生成するロジックに直接影響を与えます。

技術的詳細

このコミットの技術的な核心は、Func構造体からx1, x2, x3という3つのint32フィールドを削除し、それに伴いpclntabのレイアウトと、それを読み書きするコードを更新することです。

Func構造体の変更 (src/pkg/runtime/runtime.h)

変更前:

struct	Func
{
	uintptr	entry;	// start pc
	int32	nameoff;	// function name
	
	// TODO: Remove these fields.
	int32	args;	// in/out args size
	int32	x1;	// locals size
	int32	frame;	// legacy frame size; use pcsp if possible
	int32	x2;
	int32	x3;

	int32	pcsp;
	int32	pcfile;
	int32	pcln;
	int32	nfuncdata;
	int32	nfuncdataoff;
};

変更後:

struct	Func
{
	uintptr	entry;	// start pc
	int32	nameoff;	// function name
	
	// TODO: Perhaps remove these fields.
	int32	args;	// in/out args size
	int32	frame;	// legacy frame size; use pcsp if possible

	int32	pcsp;
	int32	pcfile;
	int32	pcln;
	int32	nfuncdata;
	int32	nfuncdataoff;
};

x1, x2, x3フィールドが削除されています。コメントも// TODO: Remove these fields.から// TODO: Perhaps remove these fields.に変更されており、将来的にargsframeも削除される可能性が示唆されています。

pclntabのレイアウト変更とリンカの調整 (src/cmd/ld/lib.c)

pclntabは、Func構造体の情報が連続して格納されるため、Func構造体のサイズ変更はpclntab全体のレイアウトに影響します。リンカは、このpclntabを構築する際に、各フィールドのオフセットとサイズを正確に計算する必要があります。

変更前は、Func構造体の固定サイズを計算する際に、削除されたフィールドの分も考慮されていました。

end = funcstart + PtrSize + 6*4 + 5*4 + npcdata*4 + nfuncdata*PtrSize;

6*4は、args, x1, frame, x2, x3、そしてもう一つ(おそらくnameoff)のint32フィールドの合計サイズを示していました。

変更後、x1, x2, x3が削除されたため、int32フィールドの数が3つ減り、3*4に変更されています。

end = funcstart + PtrSize + 3*4 + 5*4 + npcdata*4 + nfuncdata*PtrSize;

また、リンカがpclntabFunc構造体の情報を書き込む際にも、削除されたフィールドに対応する「Dead space」の書き込みが削除されています。

// Dead space. TODO: Delete (and update all parsers).
off = setuint32(ftab, off, 0);

// Dead space. TODO: Delete (and update all parsers).
off = setuint32(ftab, off, 0);
off = setuint32(ftab, off, 0);

これらの行が削除されたことで、リンカは不要なデータをpclntabに書き込まなくなりました。

シンボルテーブル解析ライブラリの更新 (src/libmach/sym.csrc/pkg/debug/gosym/pclntab.go)

Func構造体のレイアウトが変更されたため、Goバイナリのシンボルテーブルを読み取るライブラリも、新しいレイアウトに合わせてオフセットを調整する必要があります。

src/libmach/sym.cでは、Func構造体内の各フィールドへのオフセットを定義するマクロが追加され、それらのマクロを使用してフィールドにアクセスするように変更されています。

#define FuncEntry (0)
#define FuncName (pcptrsize)
#define FuncArgs (pcptrsize+4)
#define FuncFrame (pcptrsize+2*4)
#define FuncPCSP (pcptrsize+3*4)
#define FuncPCFile (pcptrsize+4*4)
#define FuncPCLine (pcptrsize+5*4)

これにより、Func構造体のレイアウト変更に柔軟に対応できるようになりました。以前はハードコードされたオフセット(例: f+pcptrsize+6*4)が使用されていましたが、新しいマクロを使用することで、より読みやすく、保守しやすいコードになっています。

同様に、src/pkg/debug/gosym/pclntab.goでも、Func構造体内のlinetabfiletabへのオフセット計算が変更されています。

変更前:

linetab := t.binary.Uint32(f[t.ptrsize+8*4:])
filetab := t.binary.Uint32(f[t.ptrsize+7*4:])

変更後:

linetab := t.binary.Uint32(f[t.ptrsize+5*4:])
filetab := t.binary.Uint32(f[t.ptrsize+4*4:])

これは、x1, x2, x3の3つのint32フィールド(合計12バイト)が削除されたことに対応しています。オフセットが3*4 = 12バイト分減っています。

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

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

  1. src/pkg/runtime/runtime.h: Func構造体からx1, x2, x3フィールドが削除されました。
  2. src/cmd/ld/lib.c: リンカがpclntabを構築する際のFunc構造体のサイズ計算と、不要な「Dead space」の書き込みが削除されました。
  3. src/libmach/sym.c: Func構造体内のフィールドへのオフセットを定義するマクロが追加され、それらを使用してフィールドにアクセスするように変更されました。
  4. src/pkg/debug/gosym/pclntab.go: debug/gosymパッケージがpclntabを解析する際のFunc構造体内のフィールドオフセット計算が更新されました。

コアとなるコードの解説

src/pkg/runtime/runtime.h

--- a/src/pkg/runtime/runtime.h
+++ b/src/pkg/runtime/runtime.h
@@ -405,12 +405,9 @@ struct	Func
 	uintptr	entry;	// start pc
 	int32	nameoff;	// function name
 	
-	// TODO: Remove these fields.
+	// TODO: Perhaps remove these fields.
 	int32	args;	// in/out args size
-	int32	x1;	// locals size
 	int32	frame;	// legacy frame size; use pcsp if possible
-	int32	x2;
-	int32	x3;
 
 	int32	pcsp;
 	int32	pcfile;

この変更は、Func構造体からx1, x2, x3という3つのint32型のフィールドを直接削除しています。これにより、Func構造体のメモリフットプリントが削減され、Goバイナリ全体のサイズが小さくなります。コメントの変更は、argsframeフィールドも将来的に削除される可能性があることを示唆しています。

src/cmd/ld/lib.c

--- a/src/cmd/ld/lib.c
+++ b/src/cmd/ld/lib.c
@@ -2400,7 +2400,7 @@ pclntab(void)
 
 		// fixed size of struct, checked below
 		off = funcstart;
-		end = funcstart + PtrSize + 6*4 + 5*4 + npcdata*4 + nfuncdata*PtrSize;
+		end = funcstart + PtrSize + 3*4 + 5*4 + npcdata*4 + nfuncdata*PtrSize;
 		if(nfuncdata > 0 && (end&(PtrSize-1)))
 			end += 4;
 		symgrow(ftab, end);
@@ -2417,9 +2417,6 @@ pclntab(void)
 		off = setuint32(ftab, off, ArgsSizeUnknown);
 	else
 		off = setuint32(ftab, off, cursym->args);
-
-		// Dead space. TODO: Delete (and update all parsers).
-		off = setuint32(ftab, off, 0);
 	
 		// frame int32
 		// TODO: Remove entirely. The pcsp table is more precise.
@@ -2432,10 +2429,6 @@ pclntab(void)
 		else
 			off = setuint32(ftab, off, (uint32)cursym->text->to.offset+PtrSize);
 
-		// Dead space. TODO: Delete (and update all parsers).
-		off = setuint32(ftab, off, 0);
-		off = setuint32(ftab, off, 0);
-
 		// pcsp table (offset int32)
 		off = addpctab(ftab, off, cursym, "pctospadj", pctospadj, 0);

pclntabのサイズ計算において、Func構造体の固定部分のサイズが6*4から3*4に削減されています。これは、削除された3つのint32フィールド(12バイト)に対応します。また、以前はFunc構造体内の「Dead space」として0を書き込んでいた部分が削除されました。これにより、リンカはよりコンパクトなpclntabを生成します。

src/libmach/sym.c

--- a/src/libmach/sym.c
+++ b/src/libmach/sym.c
@@ -1563,6 +1563,15 @@ dumphist(char *name)
 // Go 1.2 pcln table
 // See golang.org/s/go12symtab.
 
+// Func layout
+#define FuncEntry (0)
+#define FuncName (pcptrsize)
+#define FuncArgs (pcptrsize+4)
+#define FuncFrame (pcptrsize+2*4)
+#define FuncPCSP (pcptrsize+3*4)
+#define FuncPCFile (pcptrsize+4*4)
+#define FuncPCLine (pcptrsize+5*4)
+
 static int32 pcquantum;\
 static int32 pcptrsize;\
 static uvlong (*pcswav)(uvlong);\
@@ -1788,8 +1797,8 @@ go12pc2sp(uvlong pc)
 	f = go12findfunc(pc);
 	if(f == nil)
 		return ~(uvlong)0;
-	entry = pcuintptr(f);
-	off = pcswal(*(uint32*)(f+pcptrsize+6*4));
+	entry = pcuintptr(f+FuncEntry);
+	off = pcswal(*(uint32*)(f+FuncPCSP));
 	sp = pcvalue(off, entry, pc);
 	if(sp < 0)
 		return ~(uvlong)0;
@@ -1807,9 +1816,9 @@ go12fileline(char *str, int n, uvlong pc)
 	f = go12findfunc(pc);
 	if(f == nil)
 		return 0;
-	entry = pcuintptr(f);
-	fileoff = pcswal(*(uint32*)(f+pcptrsize+7*4));
-	lineoff = pcswal(*(uint32*)(f+pcptrsize+8*4));
+	entry = pcuintptr(f+FuncEntry);
+	fileoff = pcswal(*(uint32*)(f+FuncPCFile));
+	lineoff = pcswal(*(uint32*)(f+FuncPCLine));
 	lno = pcvalue(lineoff, entry, pc);
 	fno = pcvalue(fileoff, entry, pc);
 	if(lno < 0 || fno <= 0 || fno >= nfiletab) {
@@ -1845,9 +1854,9 @@ havefile:
 	// quick.
 	for(i=0; i<nfunctab; i++) {
 		func = pcline + pcuintptr(functab+i*2*pcptrsize+pcptrsize);
-		entry = pcuintptr(func);
-		fp = pcline + pcswal(*(uint32*)(func+pcptrsize+7*4));
-		lp = pcline + pcswal(*(uint32*)(func+pcptrsize+8*4));
+		entry = pcuintptr(func+FuncEntry);
+		fp = pcline + pcswal(*(uint32*)(func+FuncPCFile));
+		lp = pcline + pcswal(*(uint32*)(func+FuncPCLine));
 		fval = lval = -1;
 		fpc = lpc = entry;
 		fstartpc = fpc;

このファイルでは、Func構造体内の各フィールドへのオフセットを定義する#defineマクロが導入されました。これにより、コードがより明確になり、Func構造体のレイアウト変更に対する堅牢性が向上しました。例えば、pcspへのアクセスは以前のf+pcptrsize+6*4からf+FuncPCSPに変更されています。これは、Func構造体から3つのint32フィールドが削除されたため、オフセットが3*4=12バイト分ずれたことを反映しています。

src/pkg/debug/gosym/pclntab.go

--- a/src/pkg/debug/gosym/pclntab.go
+++ b/src/pkg/debug/gosym/pclntab.go
@@ -339,7 +339,7 @@ func (t *LineTable) go12PCToLine(pc uint64) (line int) {
 		return -1
 	}
 	entry := t.uintptr(f)
-	linetab := t.binary.Uint32(f[t.ptrsize+8*4:])
+	linetab := t.binary.Uint32(f[t.ptrsize+5*4:])
 	return int(t.pcvalue(linetab, entry, pc))
 }
 
@@ -356,7 +356,7 @@ func (t *LineTable) go12PCToFile(pc uint64) (file string) {
 		return ""
 	}
 	entry := t.uintptr(f)
-	filetab := t.binary.Uint32(f[t.ptrsize+7*4:])
+	filetab := t.binary.Uint32(f[t.ptrsize+4*4:])
 	fno := t.pcvalue(filetab, entry, pc)
 	if fno <= 0 {
 		return ""
@@ -384,8 +384,8 @@ func (t *LineTable) go12LineToPC(file string, line int) (pc uint64) {
 	for i := uint32(0); i < t.nfunctab; i++ {
 		f := t.Data[t.uintptr(t.functab[2*t.ptrsize*i+t.ptrsize:]):]
 		entry := t.uintptr(f)
-		filetab := t.binary.Uint32(f[t.ptrsize+7*4:])
-		linetab := t.binary.Uint32(f[t.ptrsize+8*4:])
+		filetab := t.binary.Uint32(f[t.ptrsize+4*4:])
+		linetab := t.binary.Uint32(f[t.ptrsize+5*4:])
 		pc := t.findFileLine(entry, filetab, linetab, int32(filenum), int32(line))
 		if pc != 0 {
 			return pc

このGoコードは、pclntabから行番号やファイル名などの情報を抽出する際に、Func構造体内のlinetabfiletabへのオフセットを調整しています。t.ptrsizeはポインタのサイズ(4バイトまたは8バイト)を示し、それに続く*4int32フィールドの数とサイズ(4バイト)を掛け合わせたものです。x1, x2, x3の3つのint32フィールドが削除されたため、オフセットが3*4=12バイト分減少し、8*45*4に、7*44*4に変更されています。これにより、debug/gosymパッケージは新しいpclntab形式を正しく解析できるようになります。

関連リンク

  • Go 1.2 Release Notes: https://go.dev/doc/go1.2 (Go 1.2の変更点全般について言及されていますが、シンボルテーブルの詳細は別途確認が必要です。)
  • Go 1.2 Symbol Table Format (golang.org/s/go12symtab): このコミットのコード内で参照されているドキュメント。Go 1.2のシンボルテーブルの新しい形式について詳細に説明されています。

参考にした情報源リンク