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

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

このコミットは、Go言語の初期のリンカである6l(64ビットアーキテクチャ向け)において、生成される実行ファイル(6.outs)にシンボルテーブルと行/PC(プログラムカウンタ)情報を追加する変更を導入しています。これにより、デバッグやプロファイリングの際に、実行時のアドレスをソースコードの行や関数名にマッピングできるようになり、開発者がプログラムの挙動をより詳細に分析できるようになります。

コミット

commit e9c9c9ace52ec99390e2f186da91b9fceb2cf322
Author: Rob Pike <r@golang.org>
Date:   Fri Jun 13 12:54:21 2008 -0700

    add symbol tables and line/pc information to 6.outs
    
    SVN=122699

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

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

元コミット内容

シンボルテーブルと行/PC情報を6.outs(64ビットアーキテクチャ向けリンカの出力ファイル)に追加する。

変更の背景

Go言語の初期段階において、生成される実行ファイルのデバッグ可能性と分析能力を向上させる必要がありました。プログラムがクラッシュしたり、予期せぬ動作をしたりした場合、その原因を特定するためには、実行時のメモリアドレスがどのソースコードの行や関数に対応しているかを知ることが不可欠です。このコミット以前は、6lリンカによって生成される実行ファイルには、このようなデバッグ情報が十分に埋め込まれていなかったと考えられます。

この変更は、Goプログラムのデバッグ体験を改善し、開発者がより効率的に問題を診断できるようにするための基礎を築くものです。特に、スタックトレースの可読性を高めたり、プロファイリングツールが正確な関数名や行番号を報告できるようにするために、シンボル情報と行/PC情報の埋め込みは極めて重要です。

前提知識の解説

リンカ (Linker)

リンカは、コンパイラによって生成された複数のオブジェクトファイル(機械語コードとデータを含む)と、必要なライブラリを結合して、単一の実行可能ファイルやライブラリを生成するプログラムです。リンカの主な役割は、未解決のシンボル参照(例えば、あるオブジェクトファイルで定義された関数を別のオブジェクトファイルが呼び出す場合)を解決し、最終的なプログラムのメモリレイアウトを決定することです。

シンボルテーブル (Symbol Table)

シンボルテーブルは、プログラム内のシンボル(変数名、関数名など)と、それらがメモリ上のどこに配置されているか(アドレス)をマッピングするデータ構造です。デバッガはシンボルテーブルを利用して、実行時のメモリアドレスを人間が理解できる変数名や関数名に変換します。これにより、開発者はソースコードレベルでプログラムの実行状態を追跡できます。

行/PC情報 (Line/PC Information)

行/PC情報(Line Number/Program Counter Information)は、実行ファイルの特定のアドレス(プログラムカウンタ、PC)が、ソースコードのどのファイル、どの行に対応しているかをマッピングする情報です。この情報は、デバッガがブレークポイントを設定したり、スタックトレースを表示したりする際に、ソースコードの正確な位置を示すために使用されます。

ELF形式 (Executable and Linkable Format)

ELFは、Unix系システムで広く使用されている実行可能ファイル、オブジェクトファイル、共有ライブラリの標準ファイル形式です。ELFファイルは、ヘッダ、プログラムヘッダテーブル、セクションヘッダテーブル、および様々なセクション(コード、データ、シンボルテーブル、デバッグ情報など)で構成されます。このコミットで追加される.gosymtab.gopclntabは、ELFファイルのセクションとして埋め込まれます。

debug['s'] フラグ

Goのツールチェインでは、コンパイルやリンクの際に様々なデバッグフラグが使用されます。debug['s']は、シンボル情報をストリップ(除去)するかどうかを制御するフラグであると推測されます。このフラグが設定されている場合、生成される実行ファイルからデバッグ情報が削除され、ファイルサイズが小さくなりますが、デバッグは困難になります。このコミットでは、debug['s']が設定されていない場合にのみシンボルテーブルと行/PC情報が追加されるように変更されています。

技術的詳細

このコミットは、Go言語のリンカ6lの内部実装に深く関わる変更です。具体的には、ELF形式の実行ファイルにデバッグ情報を埋め込むためのセクションを追加し、それらのセクションに適切なデータを書き込むロジックを実装しています。

  1. asmb関数の変更:

    • asmb関数は、リンカの主要な処理を行う部分であり、最終的な実行ファイルを組み立てる役割を担っています。
    • このコミットでは、asmb関数内でstrtabsizeという変数が導入され、文字列テーブルのサイズを管理するようになりました。
    • 特に、case 7(特定の出力形式またはデバッグモードに対応)の処理において、linuxstrtable()が呼び出され、その戻り値(文字列テーブルのサイズ)がstrtabsizeに格納されます。
    • 実行ファイルのセクションヘッダの数(# of Shdrs)が、debug['s']フラグの値に応じて動的に変更されるようになりました。debug['s']が設定されていない(シンボル情報を残す)場合、セクションヘッダの数が7に増え、これは.gosymtab.gopclntabの2つの新しいセクションが追加されることを意味します。
    • seekシステムコールによるファイルポインタの移動が、strtabsizeを考慮するように修正され、新しいセクションが適切に配置されるようになりました。
    • .gosymtab.gopclntabという新しいセクションのためのlinuxshdr(Linux ELFセクションヘッダを書き込む関数)の呼び出しが追加されました。これらのセクションは、シンボル情報とPC-行マッピング情報を格納します。
  2. linuxheadr関数の変更:

    • linuxheadr関数は、ELFファイルのヘッダ情報を生成する役割を担っています。
    • この関数もdebug['s']フラグに基づいて変更され、シンボル情報が残される場合に、.gosymtab.gopclntabセクションのために追加の領域(それぞれ64バイト)を確保するようになりました。これは、セクションヘッダテーブルのオフセット計算に影響します。
  3. linuxstrtable関数の変更:

    • linuxstrtable関数は、ELFファイルのセクション名文字列テーブル(.shstrtab)を生成する役割を担っています。
    • この関数の戻り値の型がvoidからintに変更され、生成された文字列テーブルの合計サイズを返すようになりました。
    • putstrtabという新しいヘルパー関数が導入され、個々の文字列をテーブルに追加し、そのサイズを返すようになりました。
    • debug['s']フラグが設定されていない場合に、.gosymtab.gopclntabのセクション名もこの文字列テーブルに追加されるようになりました。これにより、これらの新しいセクションがELFファイル内で正しく参照できるようになります。
  4. src/cmd/6l/l.hの変更:

    • linuxstrtable関数のプロトタイプ宣言が、戻り値の型変更に合わせて更新されました。

これらの変更により、Goのリンカは、デバッグやプロファイリングに不可欠なメタデータを生成される実行ファイルに埋め込むことができるようになりました。これは、Go言語のツールチェインが成熟し、より実用的な開発環境を提供するための重要なステップです。

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

src/cmd/6l/asm.c

--- a/src/cmd/6l/asm.c
+++ b/src/cmd/6l/asm.c
@@ -124,6 +124,9 @@ asmb(void)
  	int a;
  	uchar *op1;
  	vlong vl, va, fo, w;
+	int strtabsize;
+
+	strtabsize = 0;
 
  	if(debug['v'])
  		Bprint(&bso, "%5.2f asmb\n", cputime());
@@ -187,6 +190,10 @@ asmb(void)
  		break;
 
  	case 7:
+		debug['8'] = 1;	/* 64-bit addresses */
+		seek(cout, rnd(HEADR+textsize, INITRND)+datsize, 0);
+		strtabsize = linuxstrtable();
+		cflush();
  		v = rnd(HEADR+textsize, INITRND);
  		myseek(cout, v);
  		break;
@@ -221,17 +228,15 @@ asmb(void)
  		default:
  		case 2:
  		case 5:
-debug['s'] = 1;
+\t\t\tdebug['s'] = 1;
  			seek(cout, HEADR+textsize+datsize, 0);
  			break;
-\t\tcase 7:
-debug['s'] = 1;
-\t\t\tseek(cout, rnd(HEADR+textsize, INITRND)+datsize, 0);
-\t\t\tlinuxstrtable();
-\t\t\tbreak;
  		case 6:
  			debug['s'] = 1;
  			break;
+\t\tcase 7:
+\t\t\tseek(cout, rnd(HEADR+textsize, INITRND)+datsize+strtabsize, 0);
+\t\t\tbreak;
  		}
  		if(!debug['s'])
  			asmsym();
@@ -402,13 +407,15 @@ debug['s'] = 1;
  		wputl(56);			/* Phdr size */
  		wputl(3);			/* # of Phdrs */
  		wputl(64);			/* Shdr size */
-\t\twputl(5);			/* # of Shdrs */
+\t\tif (!debug['s'])
+\t\t\twputl(7);			/* # of Shdrs */
+\t\telse
+\t\t\twputl(5);			/* # of Shdrs */
  		wputl(4);			/* Shdr with strings */
 
-fo = 0;
-va = INITRND;
-w = HEADR+textsize;
-\n
+\t\tfo = 0;
+\t\tva = INITRND;
+\t\tw = HEADR+textsize;
 
  		linuxphdr(1,			/* text - type = PT_LOAD */
  			1L+4L,			/* text - flags = PF_X+PF_R */
@@ -419,9 +426,9 @@ w = HEADR+textsize;
  			w,			/* memory size */
  			INITRND);		/* alignment */
 
-fo = rnd(fo+w, INITRND);
-va = rnd(va+w, INITRND);
-w = datsize;
+\t\tfo = rnd(fo+w, INITRND);
+\t\tva = rnd(va+w, INITRND);
+\t\tw = datsize;
 
  		linuxphdr(1,			/* data - type = PT_LOAD */
  			2L+4L,			/* data - flags = PF_W+PF_R */
@@ -452,10 +459,10 @@ w = datsize;
  			0,			/* align */
  			0);			/* entsize */
 
-stroffset = 1;
-fo = 0;
-va = INITRND;
-w = HEADR+textsize;
+\t\tstroffset = 1;  /* 0 means no name, so start at 1 */
+\t\tfo = 0;
+\t\tva = INITRND;
+\t\tw = HEADR+textsize;
 
  		linuxshdr(".text",		/* name */
  			1,			/* type */
@@ -468,9 +475,9 @@ w = HEADR+textsize;
  			8,			/* align */
  			0);			/* entsize */
 
-fo = rnd(fo+w, INITRND);\n-va = rnd(va+w, INITRND);\n-w = datsize;
+\t\tfo = rnd(fo+w, INITRND);
+\t\tva = rnd(va+w, INITRND);
+\t\tw = datsize;
 
  		linuxshdr(".data",		/* name */
  			1,			/* type */
@@ -483,9 +490,9 @@ w = datsize;
  			8,			/* align */
  			0);			/* entsize */
 
-fo += w;
-va += w;
-w = bsssize;
+\t\tfo += w;
+\t\tva += w;
+\t\tw = bsssize;
 
  		linuxshdr(".bss",		/* name */
  			8,			/* type */
@@ -498,9 +505,7 @@ w = bsssize;
  			8,			/* align */
  			0);			/* entsize */
 
-w = stroffset +\n-\tstrlen(\".shstrtab\")+1;
-//\tstrlen(\".gosymtab\")+1;
+\t\tw = strtabsize;
 
  		linuxshdr(".shstrtab",		/* name */
  			3,			/* type */
@@ -513,18 +518,36 @@ w = stroffset +\
  			8,			/* align */
  			0);			/* entsize */
 
-//fo += w;
-//\n-//\t\tlinuxshdr(\".gosymtab\",\t\t/* name */
-//\t\t\t2,\t\t\t/* type */
-//\t\t\t0,\t\t\t/* flags */
-//\t\t\t0,\t\t\t/* addr */
-//\t\t\tfo,\t\t\t/* off */
-//\t\t\t0,\t\t\t/* size */
-//\t\t\t0,\t\t\t/* link */
-//\t\t\t0,\t\t\t/* info */
-//\t\t\t8,\t\t\t/* align */
-//\t\t\t0);\t\t\t/* entsize */
+\t\tif (debug['s'])
+\t\t\tbreak;
+\n+\t\tfo += w;
+\t\tw = symsize;
+\n+\t\tlinuxshdr(\".gosymtab\",		/* name */
+\t\t\t2,			/* type */
+\t\t\t0,			/* flags */
+\t\t\t0,			/* addr */
+\t\t\tfo,			/* off */
+\t\t\tw,			/* size */
+\t\t\t0,			/* link */
+\t\t\t0,			/* info */
+\t\t\t8,			/* align */
+\t\t\t24);			/* entsize */
+\t\t
+\t\tfo += w;
+\t\tw = lcsize;
+\n+\t\tlinuxshdr(\".gopclntab\",		/* name */
+\t\t\t2,			/* type */
+\t\t\t0,			/* flags */
+\t\t\t0,			/* addr */
+\t\t\tfo,			/* off */
+\t\t\tw,			/* size */
+\t\t\t0,			/* link */
+\t\t\t0,			/* info */
+\t\t\t8,			/* align */
+\t\t\t24);			/* entsize */
  		break;
  	}\n  	cflush();
@@ -796,7 +819,10 @@ linuxheadr(void)
  a += 64;	/* .data seg */
  a += 64;	/* .bss sect */
  a += 64;	/* .shstrtab sect - strings for headers */
-//\ta += 64;	/* .gosymtab sect */
+\tif (!debug['s']) {
+\t\ta += 64;	/* .gosymtab sect */
+\t\ta += 64;	/* .gopclntab sect */
+\t}
 
  	return a;
  }
@@ -837,21 +863,30 @@ linuxshdr(char *name, ulong type, vlong flags, vlong addr, vlong off,\n  		stroffset += strlen(name)+1;\n  }\n  \n-void\n+int\n+putstrtab(char* name) {\n+\tint w;\n+\n+\tw = strlen(name)+1;\n+\tstrnput(name, w);\n+\treturn w;\n+}\n+\n+int\n  linuxstrtable(void)\n  {\n  	char *name;\n-\n-\tname = \"\";
-\tstrnput(name, strlen(name)+1);
-\tname = \".text\";
-\tstrnput(name, strlen(name)+1);
-\tname = \".data\";
-\tstrnput(name, strlen(name)+1);
-\tname = \".bss\";
-\tstrnput(name, strlen(name)+1);
-\tname = \".shstrtab\";
-\tstrnput(name, strlen(name)+1);
-//\tname = \".gosymtab\";
-//\tstrnput(name, strlen(name)+1);
+\tint size;\n+\n+\tsize = 0;\n+\tsize += putstrtab(\"\");\n+\tsize += putstrtab(\".text\");\n+\tsize += putstrtab(\".data\");\n+\tsize += putstrtab(\".bss\");\n+\tsize += putstrtab(\".shstrtab\");\n+\tif (!debug['s']) {\n+\t\tsize += putstrtab(\".gosymtab\");\n+\t\tsize += putstrtab(\".gopclntab\");\n+\t}\n+\treturn size;\n  }

src/cmd/6l/l.h

--- a/src/cmd/6l/l.h
+++ b/src/cmd/6l/l.h
@@ -431,7 +431,7 @@ void	linuxphdr(int type, int flags, vlong foff,\n  vlong filesize, vlong memsize, vlong align);\n void	linuxshdr(char *name, ulong type, vlong flags, vlong addr, vlong off,\n  vlong size, ulong link, ulong info, vlong align, vlong entsize);\n-void	linuxstrtable(void);\n+int	linuxstrtable(void);\n  \n  \n  #pragma	varargck	type	"D"	Adr*

コアとなるコードの解説

asm.cにおける変更点

  • strtabsizeの導入と利用: asmb関数内でstrtabsize変数が追加され、linuxstrtable()の戻り値として文字列テーブルのサイズが取得されるようになりました。これにより、後続のセクションのオフセット計算にこのサイズが正確に反映されるようになります。
  • セクションヘッダ数の動的な調整: asmb関数内のELFヘッダ書き込み部分で、debug['s']フラグの状態に応じてセクションヘッダの数が5または7に設定されるようになりました。これは、シンボル情報を含める場合に.gosymtab.gopclntabの2つのセクションが追加されるためです。
  • .gosymtab.gopclntabセクションの追加: asmb関数内で、debug['s']が設定されていない場合に、.gosymtab(Goシンボルテーブル)と.gopclntab(Go PC-行テーブル)という新しいセクションのセクションヘッダがlinuxshdr関数によって書き込まれるようになりました。これらのセクションは、それぞれシンボル情報とPCからソースコードの行へのマッピング情報を格納します。
    • type2SHT_SYMTABまたはSHT_PROGBITSに相当する可能性があり、ここではカスタムのデバッグ情報セクションとして利用されています。
    • entsize24に設定されており、これはこれらのセクション内の各エントリのサイズを示唆しています。
  • linuxheadrにおけるセクションオフセットの調整: linuxheadr関数では、debug['s']が設定されていない場合に、.gosymtab.gopclntabのセクションヘッダのためにそれぞれ64バイトのオフセットが追加されるようになりました。これにより、ELFファイルのセクションヘッダテーブルのサイズが正しく計算されます。
  • linuxstrtableの機能拡張:
    • 戻り値がvoidからintに変更され、生成された文字列テーブルの合計サイズを返すようになりました。
    • putstrtabという新しいヘルパー関数が導入され、文字列をテーブルに追加する処理がカプセル化されました。
    • debug['s']が設定されていない場合に、.gosymtab.gopclntabのセクション名も文字列テーブルに追加されるようになりました。これにより、これらのセクションがELFファイル内で正しく識別できるようになります。

l.hにおける変更点

  • linuxstrtable関数のプロトタイプ宣言が、int linuxstrtable(void);に変更され、戻り値の型がintになったことを反映しています。

これらの変更は、Goのリンカが生成する実行ファイルに、デバッグやプロファイリングに不可欠なメタデータ(シンボル情報とPC-行マッピング)を埋め込むための基盤を確立するものです。これにより、Goプログラムのデバッグと分析が大幅に容易になりました。

関連リンク

参考にした情報源リンク