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

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

このコミットは、Go 1.2におけるpcln(Program Counter Line)テーブルの変更に対応するために、libmachライブラリとaddr2lineコマンドを更新するものです。pclnテーブルは、Goの実行可能ファイル内に埋め込まれたメタデータであり、プログラムカウンタ(PC)アドレスからファイル名や行番号、スタックポインタ(SP)オフセットなどのデバッグ情報をマッピングするために使用されます。Go 1.2でこのテーブルのフォーマットが変更されたため、デバッグツールが正しく機能するように更新が必要となりました。

コミット

commit 08ce3c313376b6d600c08b977103d6c51350b3f6
Author: Russ Cox <rsc@golang.org>
Date:   Thu Jul 18 10:12:28 2013 -0400

    libmach: update for Go 1.2 pcln table
    
    The change to addr2line makes it easy to test by hand.
    
    R=golang-dev, r
    CC=golang-dev
    https://golang.org/cl/11485044

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

https://github.com/golang/go/commit/08ce3c313376b6d600c08b977103d6c51350b3f6

元コミット内容

libmach: update for Go 1.2 pcln table

The change to addr2line makes it easy to test by hand.

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

変更の背景

Go言語のランタイムは、実行中のプログラムのデバッグを支援するために、プログラムカウンタ(PC)アドレスとソースコードの行番号、ファイル名、関数名などの情報を関連付けるためのメタデータを含んでいます。このメタデータは通常、pclnテーブル(Program Counter Line Table)として知られる構造に格納されています。

Go 1.2のリリースに向けて、このpclnテーブルの内部フォーマットが変更されました。これは、Goのコンパイラやリンカの進化に伴い、デバッグ情報の表現をより効率的または柔軟にするための変更であったと考えられます。

libmachは、Goの実行可能ファイルを解析し、シンボル情報やデバッグ情報を抽出するためのライブラリです。addr2lineは、このlibmachを利用して、与えられたPCアドレスに対応するソースコードのファイル名と行番号を特定するコマンドラインツールです。

pclnテーブルのフォーマットが変更されたため、既存のlibmachaddr2lineは新しいフォーマットのデバッグ情報を正しく解釈できなくなりました。このコミットは、これらのツールがGo 1.2で生成された実行可能ファイルと互換性を持つように、libmach内のpclnテーブル解析ロジックを更新し、それに伴いaddr2lineの機能も調整することを目的としています。

コミットメッセージにある「The change to addr2line makes it easy to test by hand.」という記述は、addr2lineにファイル名と行番号からPCアドレスを逆引きする機能が追加されたことを示唆しており、これにより新しいpclnテーブルの変更が正しく反映されているかを開発者が手動で簡単に検証できるようになったことを意味します。

前提知識の解説

プログラムカウンタ (PC)

プログラムカウンタ(Program Counter, PC)は、CPUが次に実行する命令のアドレスを保持するレジスタです。デバッグにおいては、特定のPCアドレスがどのソースコードの行に対応するかを知ることが重要になります。

スタックポインタ (SP)

スタックポインタ(Stack Pointer, SP)は、現在のスタックフレームの最上部(または最下部)のアドレスを指すレジスタです。関数呼び出しやローカル変数の割り当てなど、スタック操作に関連する情報を提供します。デバッグにおいては、特定のPCアドレスにおけるスタックポインタのオフセットを知ることで、スタックフレームの構造を理解し、変数の位置などを特定するのに役立ちます。

pclnテーブル (Program Counter Line Table)

pclnテーブルは、Goの実行可能ファイルに埋め込まれているデバッグ情報の一種です。これは、プログラムカウンタ(PC)アドレスと、それに対応するソースコードのファイル名、行番号、関数名、スタックポインタのオフセットなどの情報をマッピングするために使用されます。Goのデバッガやプロファイラ、addr2lineのようなツールは、このpclnテーブルを参照して、実行中のプログラムのどの部分がソースコードのどこに該当するかを特定します。

pclnテーブルは、PCアドレスの範囲と、その範囲に対応する行番号やファイル番号のデルタ値のシーケンスとしてエンコードされています。これにより、実行可能ファイルのサイズを抑えつつ、効率的にデバッグ情報を提供します。

addr2lineコマンド

addr2lineは、Goのツールチェインに含まれるコマンドラインユーティリティです。その名の通り、「アドレスから行へ」という意味で、Goの実行可能ファイル内のPCアドレスを受け取り、そのアドレスがソースコードのどのファイル、どの行に該当するかを出力します。これは、クラッシュダンプの解析や、プロファイリング結果の可読性を高める際などに非常に有用です。

libmachライブラリ

libmachは、Goの実行可能ファイル(Mach-O、ELF、PEなど)の構造を解析し、シンボルテーブル、セクション情報、デバッグ情報などを読み取るためのGoランタイム内部のライブラリです。addr2lineのようなツールは、このlibmachを利用して、実行可能ファイルから必要な情報を抽出します。

可変長整数 (Varint) エンコーディング

このコミットのコード変更箇所には、readvarintという関数が登場します。これは可変長整数(Varint)エンコーディングを読み取るためのものです。Varintは、小さな数値を少ないバイト数で、大きな数値をより多くのバイト数で表現するエンコーディング方式です。これにより、データサイズを効率的に削減できます。Goのpclnテーブルのようなデバッグ情報では、PCアドレスや行番号のデルタ値など、比較的小さな数値が頻繁に現れるため、Varintがよく利用されます。

技術的詳細

このコミットの主要な変更は、libmach/sym.cにGo 1.2の新しいpclnテーブルフォーマットを解析するためのロジックが追加されたことです。

Go 1.2 pclnテーブルの構造

Go 1.2のpclnテーブルの詳細は、golang.org/s/go12symtabで説明されています(このコミットのコメントにも記載があります)。このテーブルは、以下の主要な要素で構成されます。

  1. マジックナンバー: テーブルの先頭には、テーブルのバージョンとエンディアンを示すマジックナンバー(Go12PclnMagicまたはGo12PclnMagicRev)が含まれます。これにより、libmachはテーブルのフォーマットを識別し、必要に応じてバイトオーダーを変換できます。
  2. PC量子 (PC Quantum): 命令の最小単位(通常は1または4バイト)を示します。PCアドレスのデルタ値はこのPC量子の倍数で表現されます。
  3. ポインタサイズ: システムのポインタサイズ(4バイトまたは8バイト)を示します。テーブル内のアドレスやオフセットの読み取りに使用されます。
  4. 関数テーブル (functab): 各関数のエントリポイントPCアドレスと、その関数に対応する_func構造体へのオフセットのペアのリストです。_func構造体は、関数名、ファイル名、行番号、スタックフレーム情報など、その関数に関する詳細なデバッグ情報を含んでいます。
  5. ファイルテーブル (filetab): 実行可能ファイル内で参照されるすべてのソースファイル名のリストです。各ファイル名には一意のインデックスが割り当てられ、pclnテーブル内でファイル名が直接埋め込まれるのではなく、このインデックスが使用されます。

libmachの変更点

libmach/sym.cには、Go 1.2のpclnテーブルを扱うための新しい関数群が追加されています。

  • Go12PclnMagic / Go12PclnMagicRev: 新しいpclnテーブルのマジックナンバーを定義しています。
  • isgo12pcline(): 現在読み込んでいるpclnテーブルがGo 1.2フォーマットであるかを判定します。
  • go12init(): Go 1.2 pclnテーブルのヘッダを解析し、PC量子、ポインタサイズ、関数テーブル、ファイルテーブルのポインタなどを初期化します。バイトオーダーの変換(スワップ)もここで行われます。
  • go12clean(): 初期化されたGo 1.2 pclnテーブル関連のリソースをクリーンアップします。
  • go12findfunc(uvlong pc): 与えられたPCアドレスを含む関数を、関数テーブルをバイナリサーチして見つけ出し、その関数の_func構造体へのポインタを返します。
  • readvarint(uchar **pp): 可変長整数を読み取るヘルパー関数です。pclnテーブル内のデルタ値のエンコーディングに使用されます。
  • step(uchar **pp, uvlong *pc, int32 *value, int first): PC-valueテーブル(行番号やSPオフセットのテーブル)をステップ実行し、PCアドレスと対応する値(行番号やSPオフセット)を更新します。
  • pcvalue(uint32 off, uvlong entry, uvlong targetpc): 特定のPCアドレスに対応する値(行番号やSPオフセット)を、PC-valueテーブルから検索して返します。
  • go12pc2sp(uvlong pc): Go 1.2 pclnテーブルを使用して、PCアドレスからスタックポインタのオフセットを計算します。
  • go12fileline(char *str, int n, uvlong pc): Go 1.2 pclnテーブルを使用して、PCアドレスからファイル名と行番号を取得し、指定されたバッファにフォーマットして格納します。
  • go12file2pc(char *file, int line): Go 1.2 pclnテーブルを使用して、ファイル名と行番号から対応するPCアドレスを逆引きします。この機能は、addr2lineの新しいテスト機能の基盤となります。

これらの新しい関数は、既存のfile2pcfilelinepc2spなどの関数から呼び出されるように変更されており、isgo12pcline()の戻り値に基づいて、Go 1.2の新しいpclnテーブル解析ロジックを使用するか、従来のロジックを使用するかが切り替えられます。

addr2lineの変更点

src/cmd/addr2line/main.cでは、addr2lineコマンドに新しい機能が追加されています。

  • ファイル名と行番号からのPCアドレス逆引き: addr2lineが引数としてファイル名:行番号の形式を受け取った場合、file2pc関数(内部でgo12file2pcを呼び出す)を使用して、対応するPCアドレスを検索し、16進数形式で出力するようになりました。これにより、Go 1.2のpclnテーブルの変更が正しく機能しているかを、開発者が手動で簡単に検証できるようになります。

これらの変更により、Go 1.2で生成された実行可能ファイルに対しても、addr2lineが正しくデバッグ情報を提供できるようになり、またデバッグ情報の逆引きテストも容易になりました。

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

src/cmd/addr2line/main.c

diff --git a/src/cmd/addr2line/main.c b/src/cmd/addr2line/main.c
index 9faadc27bb..54c4d90b5c 100644
--- a/src/cmd/addr2line/main.c
+++ b/src/cmd/addr2line/main.c
@@ -31,7 +31,7 @@ void
 main(int argc, char **argv)
 {
 	int fd;
-	char *p;
+	char *p, *q;
 	uvlong pc;
 	Symbol s;
 	Fhdr fhdr;
@@ -67,6 +67,17 @@ main(int argc, char **argv)
 		if(p == nil)
 			break;
 		p[Blinelen(&bin)-1] = '\0';
+		q = strchr(p, ':');
+		if(q != nil) {
+			// reverse: translate file:line to pc
+			*q++ = '\0';
+			pc = file2pc(p, atoi(q));
+			if(pc == ~(uvlong)0)
+				Bprint(&bout, "!%r\n");
+			else
+				Bprint(&bout, "0x%llux\n", pc);
+			continue;
+		}			
 		pc = strtoull(p, 0, 16);
 		if(!findsym(pc, CTEXT, &s))
 			s.name = "??";

src/libmach/sym.c

diff --git a/src/libmach/sym.c b/src/libmach/sym.c
index 28c80d6413..d8cd8ea1d9 100644
--- a/src/libmach/sym.c
+++ b/src/libmach/sym.c
@@ -102,6 +102,17 @@ static int
 symerrmsg(int, char*);
 static int
 txtcomp(const void*, const void*);
 static int
 filecomp(const void*, const void*);
 
+/*
+ * Go 1.2 pcln table (also contains pcsp).
+ */
+#define Go12PclnMagic 0xfffffffb
+#define Go12PclnMagicRev 0xfbffffff
+static int	isgo12pcline(void);
+static uvlong go12pc2sp(uvlong);
+static int32 go12fileline(char*, int, uvlong);
+static void	go12clean(void);
+static uvlong go12file2pc(char*, int);
+
 /*
  *	initialize the symbol tables
  */
@@ -444,6 +455,7 @@ cleansyms(void)
 	if(pcline)
 		free(pcline);
 	pcline = 0;
+	go12clean();
 }
 
 /*
@@ -998,12 +1010,14 @@ file2pc(char *file, int32 line)
 	uvlong pc, start, end;
 	short *name;
 
+	if(isgo12pcline())
+		return go12file2pc(file, line);
 	if(buildtbls() == 0 || files == 0)
-		return ~0;
+		return ~(uvlong)0;
 	name = encfname(file);
 	if(name == 0) {			/* encode the file name */
 		werrstr("file %s not found", file);
-		return ~0;
+		return ~(uvlong)0;
 	}
 		/* find this history stack */
 	for(i = 0, fp = files; i < nfiles; i++, fp++)
@@ -1012,7 +1026,7 @@ file2pc(char *file, int32 line)
 	free(name);
 	if(i >= nfiles) {
 		werrstr("line %d in file %s not found", line, file);
-		return ~0;
+		return ~(uvlong)0;
 	}
 	start = fp->addr;		/* first text addr this file */
 	if(i < nfiles-1)
@@ -1026,9 +1040,9 @@ file2pc(char *file, int32 line)
 	if(debug)
 		print("find pc for %d - between: %llux and %llux\n", line, start, end);
 	pc = line2addr(line, start, end);
-	if(pc == ~0) {
+	if(pc == ~(uvlong)0) {
 		werrstr("line %d not in file %s", line, file);
-		return ~0;
+		return ~(uvlong)0;
 	}
 	return pc;
 }
@@ -1169,6 +1183,9 @@ fileline(char *str, int n, uvlong dot)
 	int32 line, top, bot, mid;
 	File *f;
 
+	if(isgo12pcline())
+		return go12fileline(str, n, dot);
+
 	*str = 0;
 	if(buildtbls() == 0)
 		return 0;
@@ -1368,13 +1385,16 @@ pc2sp(uvlong pc)
 	uchar *c, u;
 	uvlong currpc, currsp;
 
+	if(isgo12pcline())
+		return go12pc2sp(pc);
+
 	if(spoff == 0)
-		return ~0;
+		return ~(uvlong)0;
 	currsp = 0;
 	currpc = txtstart - mach->pcquant;
 
 	if(pc<currpc || pc>txtend)
-		return ~0;
+		return ~(uvlong)0;
 	for(c = spoff; c < spoffend; c++) {
 		if (currpc >= pc)
 			return currsp;
@@ -1391,7 +1411,7 @@ pc2sp(uvlong pc)
 		currpc += mach->pcquant*(u-129);
 		currpc += mach->pcquant;
 	}\n-	return ~0;
+\treturn ~(uvlong)0;
 }\n \n /*
@@ -1412,7 +1432,7 @@ pc2line(uvlong pc)
 	else
 		currpc = txtstart-mach->pcquant;
 	if(pc<currpc || pc>txtend)
-		return ~0;
+		return -1;
 
 	for(c = pcline; c < pclineend && currpc < pc; c++) {
 		u = *c;
@@ -1448,11 +1468,11 @@ line2addr(int32 line, uvlong basepc, uvlong endpc)
 	int found;
 
 	if(pcline == 0 || line == 0)
-		return ~0;
+		return ~(uvlong)0;
 
 	currline = 0;
 	currpc = txtstart-mach->pcquant;
-	pc = ~0;
+	pc = ~(uvlong)0;
 	found = 0;
 	delta = HUGEINT;
 
@@ -1485,7 +1505,7 @@ line2addr(int32 line, uvlong basepc, uvlong endpc)
 	}\n \tif(found)\n \t\treturn pc;\n-\treturn ~0;
+\treturn ~(uvlong)0;
 }\n \n /*
@@ -1539,3 +1559,316 @@ dumphist(char *name)\n 	\tfree(fname);\n }\n #endif\n+\n+// Go 1.2 pcln table\n+// See golang.org/s/go12symtab.\n+\n+static int32 pcquantum;\n+static int32 pcptrsize;\n+static uvlong (*pcswav)(uvlong);\n+static uint32 (*pcswal)(uint32);\n+static uvlong (*pcuintptr)(uchar*);\n+static uchar *functab;\n+static uint32 nfunctab;\n+static uint32 *filetab;\n+static uint32 nfiletab;\n+\n+static uint32\nxswal(uint32 v)\n+{\n+\treturn (v>>24) | ((v>>8)&0xFF00) | ((v<<8)&0xFF0000) | v<<24;\n+}\n+\n+static uvlong\nxswav(uvlong v)\n+{\n+\treturn (uvlong)xswal(v)<<32 | xswal(v>>32);\n+}\n+\n+static uvlong\nnoswav(uvlong v)\n+{\n+\treturn v;\n+}\n+\n+static uint32\nnoswal(uint32 v)\n+{\n+\treturn v;\n+}\n+\n+static uvlong\n+readuintptr64(uchar *p)\n+{\n+\treturn pcswav(*(uvlong*)p);\n+}\n+\n+static uvlong\n+readuintptr32(uchar *p)\n+{\n+\treturn pcswal(*(uint32*)p);\n+}\n+\n+static void\ngo12clean(void)\n+{\n+\tpcquantum = 0;\n+\tpcswav = nil;\n+\tpcswal = nil;\n+\tfunctab = nil;\n+\tnfunctab = 0;\n+\tfiletab = nil;\n+\tnfiletab = 0;\n+}\n+\n+static void\ngo12init(void)\n+{\n+\tuint32 m;\n+\tuchar *p;\n+\n+\tif(pcquantum != 0)\n+\t\treturn;\n+\tpcquantum = -1; // not go 1.2\n+\tif(pcline == nil || pclineend - pcline < 16 ||\n+\t\tpcline[4] != 0 || pcline[5] != 0 ||\n+\t\t(pcline[6] != 1 && pcline[6] != 4) ||\n+\t\t(pcline[7] != 4 && pcline[7] != 8))\n+\t\treturn;\n+\n+\t// header is magic, 00 00 pcquantum ptrsize\n+\tm = *(uint32*)pcline;\n+\tif(m == Go12PclnMagic) {\n+\t\tpcswav = noswav;\n+\t\tpcswal = noswal;\n+\t} else {\n+\t\tpcswav = xswav;\n+\t\tpcswal = xswal;\n+\t}\n+\tpcptrsize = pcline[7];\n+\t\n+\tif(pcptrsize == 4)\n+\t\tpcuintptr = readuintptr32;\n+\telse\n+\t\tpcuintptr = readuintptr64;\n+\n+\tnfunctab = pcuintptr(pcline+8);\n+\tfunctab = pcline + 8 + pcptrsize;\n+\t\n+\t// functab is 2*nfunctab pointer-sized values.\n+\t// The offset to the file table follows.\n+\tp = functab + nfunctab*2*pcptrsize + pcptrsize;\n+\tif(p+4 > pclineend)\n+\t\treturn;\n+\tfiletab = (uint32*)(pcline + pcswal(*(uint32*)p));\n+\tif((uchar*)filetab+4 > pclineend)\n+\t\treturn;\n+\t\n+\t// File table begins with count.\n+\tnfiletab = pcswal(filetab[0]);\n+\tif((uchar*)(filetab + nfiletab) > pclineend)\n+\t\treturn;\n+\n+\t// Committed.\n+\tpcquantum = pcline[6];\n+}\n+\n+static int\nisgo12pcline(void)\n+{\n+\tgo12init();\n+\treturn pcquantum > 0;\n+}\n+\n+static uchar*\ngo12findfunc(uvlong pc)\n+{\n+\tuchar *f, *fm;\n+\tint32 nf, m;\n+\n+\tif(pc < pcuintptr(functab) || pc >= pcuintptr(functab+2*nfunctab*pcptrsize))\n+\t\treturn nil;\n+\n+\t// binary search to find func with entry <= addr.\n+\tf = functab;\n+\tnf = nfunctab;\n+\twhile(nf > 0) {\n+\t\tm = nf/2;\n+\t\tfm = f + 2*pcptrsize*m;\n+\t\tif(pcuintptr(fm) <= pc && pc < pcuintptr(fm+2*pcptrsize)) {\n+\t\t\tf = pcline + pcuintptr(fm+pcptrsize);\n+\t\t\tif(f >= pclineend)\n+\t\t\t\treturn nil;\n+\t\t\treturn f;\n+\t\t} else if(pc < pcuintptr(fm))\n+\t\t\tnf = m;\n+\t\telse {\n+\t\t\tf += (m+1)*2*pcptrsize;\n+\t\t\tnf -= m+1;\n+\t\t}\n+\t}\n+\treturn nil;\n+}\n+\n+static uint32\n+readvarint(uchar **pp)\n+{\n+\tuchar *p;\n+\tuint32 v;\n+\tint32 shift;\n+\t\n+\tv = 0;\n+\tp = *pp;\n+\tfor(shift = 0;; shift += 7) {\n+\t\tv |= (*p & 0x7F) << shift;\n+\t\tif(!(*p++ & 0x80))\n+\t\t\tbreak;\n+\t}\n+\t*pp = p;\n+\treturn v;\n+}\n+\n+static char*\npcstring(uint32 off)\n+{\n+\tif(off == 0 || off >= pclineend - pcline ||\n+\t   memchr(pcline + off, '\0', pclineend - (pcline + off)) == nil)\n+\t\treturn \"?\";\n+\treturn (char*)pcline+off;\n+}\n+\n+\n+static int\n+step(uchar **pp, uvlong *pc, int32 *value, int first)\n+{\n+\tuint32 uvdelta, pcdelta;\n+\tint32 vdelta;\n+\n+\tuvdelta = readvarint(pp);\n+\tif(uvdelta == 0 && !first)\n+\t\treturn 0;\n+\tif(uvdelta&1)\n+\t\tuvdelta = ~(uvdelta>>1);\n+\telse\n+\t\tuvdelta >>= 1;\n+\tvdelta = (int32)uvdelta;\n+\tpcdelta = readvarint(pp) * pcquantum;\n+\t*value += vdelta;\n+\t*pc += pcdelta;\n+\treturn 1;\n+}\n+\n+static int32\npcvalue(uint32 off, uvlong entry, uvlong targetpc)\n+{\n+\tuvlong pc;\n+\tint32 val;\n+\tuchar *p;\n+\t\n+\tval = -1;\n+\tpc = entry;\n+\tif(off == 0 || off >= pclineend - pcline)\n+\t\treturn -1;\t\n+\tp = pcline + off;\n+\twhile(step(&p, &pc, &val, pc == entry)) {\n+\t\tif(targetpc < pc)\n+\t\t\treturn val;\n+\t}\n+\treturn -1;\n+}\n+\n+static uvlong\ngo12pc2sp(uvlong pc)\n+{\n+\tuchar *f;\n+\tuint32 off;\n+\tuvlong entry;\n+\tint32 sp;\n+\n+\tf = go12findfunc(pc);\n+\tif(f == nil)\n+\t\treturn ~(uvlong)0;\n+\tentry = pcuintptr(f);\n+\toff = pcswal(*(uint32*)(f+pcptrsize+6*4));\n+\tsp = pcvalue(off, entry, pc);\n+\tif(sp < 0)\n+\t\treturn ~(uvlong)0;\n+\treturn sp;\n+}\n+\n+static int32\ngo12fileline(char *str, int n, uvlong pc)\n+{\n+\tuchar *f;\n+\tuint32 fileoff, lineoff;\n+\tuvlong entry;\n+\tint lno, fno;\n+\n+\tf = go12findfunc(pc);\n+\tif(f == nil)\n+\t\treturn 0;\n+\tentry = pcuintptr(f);\n+\tfileoff = pcswal(*(uint32*)(f+pcptrsize+7*4));\n+\tlineoff = pcswal(*(uint32*)(f+pcptrsize+8*4));\n+\tlno = pcvalue(lineoff, entry, pc);\n+\tfno = pcvalue(fileoff, entry, pc);\n+\tif(lno < 0 || fno <= 0 || fno >= nfiletab) {\n+\t\treturn 0;\n+\t}\n+\tsnprint(str, n, \"%s:%d\", pcstring(pcswal(filetab[fno])), lno);\n+\treturn 1;\n+}\n+\n+static uvlong\ngo12file2pc(char *file, int line)\n+{\n+\tint fno;\n+\tint32 i, fval, lval;\n+\tuchar *func, *fp, *lp;\n+\tuvlong fpc, lpc, fstartpc, lstartpc, entry;\n+\n+\t// Map file to file number.\n+\t// NOTE(rsc): Could introduce a hash table for repeated\n+\t// lookups if anyone ever calls this.\n+\tfor(fno=1; fno<nfiletab; fno++)\n+\t\tif(strcmp(pcstring(pcswal(filetab[fno])), file) == 0)\n+\t\t\tgoto havefile;\n+\twerrstr(\"cannot find file\");\n+\treturn ~(uvlong)0;\n+\n+havefile:\n+\t// Consider each func.\n+\t// Run file number program to find file match,\n+\t// then run line number program to find line match.\n+\t// Most file number programs are tiny, and most will\n+\t// not mention the file number, so this should be fairly\n+\t// quick.\n+\tfor(i=0; i<nfunctab; i++) {\n+\t\tfunc = pcline + pcuintptr(functab+i*2*pcptrsize+pcptrsize);\n+\t\tentry = pcuintptr(func);\n+\t\tfp = pcline + pcswal(*(uint32*)(func+pcptrsize+7*4));\n+\t\tlp = pcline + pcswal(*(uint32*)(func+pcptrsize+8*4));\n+\t\tfval = lval = -1;\n+\t\tfpc = lpc = entry;\n+\t\tfstartpc = fpc;\n+\t\twhile(step(&fp, &fpc, &fval, fpc==entry)) {\n+\t\t\tif(fval == fno && fstartpc < fpc) {\n+\t\t\t\tlstartpc = lpc;\n+\t\t\t\twhile(lpc < fpc && step(&lp, &lpc, &lval, lpc==entry)) {\n+\t\t\t\t\tif(lval == line) {\n+\t\t\t\t\t\tif(fstartpc <= lstartpc) {\n+\t\t\t\t\t\t\treturn lstartpc;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t\tif(fstartpc < lpc) {\n+\t\t\t\t\t\t\treturn fstartpc;\n+\t\t\t\t\t\t}\n+\t\t\t\t\t}\n+\t\t\t\t\tlstartpc = lpc;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tfstartpc = fpc;\n+\t\t}\n+\t}\n+\twerrstr(\"cannot find line in file\");\n+\treturn ~(uvlong)0;\n+}\n```

## コアとなるコードの解説

### `src/cmd/addr2line/main.c`の変更

このファイルでは、`addr2line`コマンドのメインロジックが変更されています。

*   **`char *q;` の追加**: 新しいポインタ`q`が宣言されています。これは、入力文字列にコロン`:`が含まれる場合に、ファイル名と行番号を分割するために使用されます。
*   **ファイル名:行番号からのPCアドレス逆引き機能**:
    *   `q = strchr(p, ':');` で、入力文字列`p`にコロン`:`が含まれているかをチェックします。
    *   `if(q != nil)` の条件が真の場合、つまりコロンが見つかった場合、これはファイル名と行番号の形式の入力と判断されます。
    *   `*q++ = '\0';` でコロンをヌル文字に置き換え、文字列`p`をファイル名として終端させ、`q`を行番号の文字列の先頭に移動させます。
    *   `pc = file2pc(p, atoi(q));` を呼び出し、ファイル名(`p`)と行番号(`atoi(q)`で文字列を行番号に変換)から対応するPCアドレスを取得します。`file2pc`は後述の`libmach/sym.c`で定義されており、Go 1.2の`pcln`テーブルに対応した新しいロジックを内部で呼び出します。
    *   `if(pc == ~(uvlong)0)` で、`file2pc`がPCアドレスを見つけられなかった場合(エラーを示す`~(uvlong)0`が返された場合)は、エラーメッセージ`!%r`を出力します。
    *   それ以外の場合(PCアドレスが見つかった場合)は、`Bprint(&bout, "0x%llux\n", pc);` で、見つかったPCアドレスを16進数形式で標準出力に出力します。
    *   `continue;` で、現在の入力行の処理を終了し、次の入力行の処理に移ります。これにより、通常のPCアドレスからの変換ロジックはスキップされます。

この変更により、`addr2line`は従来のPCアドレスからファイル名・行番号への変換だけでなく、ファイル名・行番号からPCアドレスへの逆変換もサポートするようになり、Go 1.2の`pcln`テーブルの正確性を手動で検証する際の利便性が向上しました。

### `src/libmach/sym.c`の変更

このファイルは、Goの実行可能ファイルからシンボル情報を読み取る`libmach`ライブラリの核心部分です。Go 1.2の`pcln`テーブルフォーマットに対応するための大幅な変更が加えられています。

*   **Go 1.2 `pcln`テーブル関連の定数と関数プロトタイプ宣言**:
    *   `Go12PclnMagic`と`Go12PclnMagicRev`というマジックナンバーが定義されています。これらはGo 1.2の`pcln`テーブルの先頭に埋め込まれ、テーブルのバージョンとエンディアンを識別するために使用されます。
    *   `isgo12pcline`, `go12pc2sp`, `go12fileline`, `go12clean`, `go12file2pc`といった新しい静的関数のプロトタイプが宣言されています。これらはGo 1.2の`pcln`テーブルを解析するための主要なAPIとなります。

*   **`cleansyms()`の変更**:
    *   `go12clean();` が追加されました。これにより、シンボルテーブルがクリーンアップされる際に、Go 1.2 `pcln`テーブル関連のグローバル変数もリセットされるようになります。

*   **`file2pc()`、`fileline()`、`pc2sp()`の変更**:
    *   これらの既存の関数は、PCアドレスからファイル名・行番号への変換、ファイル名・行番号からPCアドレスへの逆変換、PCアドレスからスタックポインタオフセットへの変換を行う主要な関数です。
    *   各関数の冒頭に `if(isgo12pcline()) return go12...;` という条件分岐が追加されました。これは、現在読み込んでいる実行可能ファイルの`pcln`テーブルがGo 1.2フォーマットであるかを`isgo12pcline()`でチェックし、もしそうであれば、新しく追加された`go12...`プレフィックスを持つ対応する関数(例: `go12file2pc`、`go12fileline`、`go12pc2sp`)を呼び出すように変更されています。
    *   これにより、`libmach`はGo 1.2以前のフォーマットとGo 1.2の新しいフォーマットの両方に対応できるようになります。
    *   また、エラーを示す戻り値が`~0`から`~(uvlong)0`に変更されています。これは、`uvlong`型(符号なし64ビット整数)の最大値を明示的に示すための変更で、より型安全な記述になっています。

*   **Go 1.2 `pcln`テーブル解析のための新しい関数群の追加**:
    *   **グローバル変数**: `pcquantum`, `pcptrsize`, `pcswav`, `pcswal`, `pcuintptr`, `functab`, `nfunctab`, `filetab`, `nfiletab`といった静的グローバル変数が追加されています。これらはGo 1.2 `pcln`テーブルの解析状態を保持するために使用されます。
    *   **バイトスワップ関数**: `xswal` (32ビット値のスワップ) と `xswav` (64ビット値のスワップ) が定義されています。これらは、`pcln`テーブルが現在のシステムのエンディアンと異なる場合にバイトオーダーを変換するために使用されます。`noswal`と`noswav`は、スワップが不要な場合に使用されます。
    *   **ポインタ読み取り関数**: `readuintptr32`と`readuintptr64`は、ポインタサイズに応じて32ビットまたは64ビットのポインタ値を読み取るための関数です。
    *   **`go12clean()`**: Go 1.2 `pcln`テーブル関連のグローバル変数を初期状態に戻します。
    *   **`go12init()`**: Go 1.2 `pcln`テーブルの初期化を行います。
        *   `pcline`(`pcln`テーブルの生データへのポインタ)の有効性をチェックします。
        *   マジックナンバーを読み取り、エンディアンを判定して適切なバイトスワップ関数(`pcswav`, `pcswal`)を設定します。
        *   PC量子(`pcquantum`)とポインタサイズ(`pcptrsize`)を読み取ります。
        *   関数テーブル(`functab`)とファイルテーブル(`filetab`)のポインタとサイズを解析します。
        *   この関数は一度だけ実行されるように設計されています。
    *   **`isgo12pcline()`**: `go12init()`を呼び出し、`pcquantum`が0より大きい(つまりGo 1.2 `pcln`テーブルが正常に初期化された)場合に真を返します。
    *   **`go12findfunc(uvlong pc)`**: 与えられたPCアドレスを含む関数を、関数テーブルに対してバイナリサーチで効率的に検索し、その関数の`_func`構造体へのポインタを返します。
    *   **`readvarint(uchar **pp)`**: `pcln`テーブル内で使用される可変長整数を読み取ります。
    *   **`pcstring(uint32 off)`**: ファイルテーブル内のオフセットからファイル名文字列を取得します。
    *   **`step(uchar **pp, uvlong *pc, int32 *value, int first)`**: PC-valueテーブル(行番号やSPオフセットのデルタ値が格納されている)を解析し、PCアドレスと対応する値(行番号やSPオフセット)を更新します。
    *   **`pcvalue(uint32 off, uvlong entry, uvlong targetpc)`**: 特定のPCアドレス`targetpc`に対応する値(行番号やSPオフセット)を、PC-valueテーブルから検索して返します。
    *   **`go12pc2sp(uvlong pc)`**: `go12findfunc`と`pcvalue`を組み合わせて、PCアドレスからスタックポインタのオフセットを計算します。
    *   **`go12fileline(char *str, int n, uvlong pc)`**: `go12findfunc`と`pcvalue`を組み合わせて、PCアドレスからファイル名と行番号を取得し、指定されたバッファに`ファイル名:行番号`の形式で格納します。
    *   **`go12file2pc(char *file, int line)`**: ファイル名と行番号からPCアドレスを逆引きする最も複雑な関数です。
        *   まず、ファイル名をファイルテーブルから検索して対応するファイル番号を取得します。
        *   次に、すべての関数をイテレートし、各関数のPC-valueテーブル(ファイル番号と行番号のデルタ値が格納されている)を解析します。
        *   指定されたファイル番号と行番号に一致するPCアドレスを見つけ出し、それを返します。この処理は、PC-valueテーブルの構造を理解し、PCアドレスと値のデルタを正確に適用する必要があります。

これらの変更により、`libmach`はGo 1.2の新しい`pcln`テーブルフォーマットを完全にサポートし、デバッグツールがGo 1.2でコンパイルされたプログラムに対して正確なデバッグ情報を提供できるようになりました。

## 関連リンク

*   Go 1.2リリースノート (関連する変更が言及されている可能性があります): [https://go.dev/doc/go1.2](https://go.dev/doc/go1.2)
*   Goのシンボルテーブルに関するドキュメント (Go 1.2 `pcln`テーブルの仕様について言及されている可能性): [https://golang.org/s/go12symtab](https://golang.org/s/go12symtab) (コミットコメントに記載されているリンク)

## 参考にした情報源リンク

*   Goのソースコード (特に`src/cmd/addr2line/main.c`と`src/libmach/sym.c`): [https://github.com/golang/go](https://github.com/golang/go)
*   Goの`pcln`テーブルに関する議論やドキュメント (Go 1.2の変更に関する詳細):
    *   [https://go.dev/src/runtime/symtab.go](https://go.dev/src/runtime/symtab.go) (Goランタイム内のシンボルテーブル関連コード)
    *   [https://go.dev/src/debug/gosym/symtab.go](https://go.dev/src/debug/gosym/symtab.go) (Goのデバッグパッケージ内のシンボルテーブル解析コード)
*   可変長整数 (Varint) エンコーディングに関する一般的な情報: [https://en.wikipedia.org/wiki/Variable-length_quantity](https://en.wikipedia.org/wiki/Variable-length_quantity)
*   `addr2line`コマンドに関する一般的な情報: [https://man7.org/linux/man-pages/man1/addr2line.1.html](https://man7.org/linux/man-pages/man1/addr2line.1.html) (一般的な`addr2line`コマンドのmanページ)
*   Goのデバッグ情報に関するブログ記事や解説 (Goのデバッグ情報の進化について):
    *   [https://blog.golang.org/go1.2](https://blog.golang.org/go1.2) (Go 1.2の公式ブログ記事)
    *   [https://go.dev/blog/go1.2-runtime](https://go.dev/blog/go1.2-runtime) (Go 1.2ランタイムに関するブログ記事)
    *   [https://go.dev/blog/go1.2-compiler](https://go.dev/blog/go1.2-compiler) (Go 1.2コンパイラに関するブログ記事)
*   GoのIssueトラッカー (関連するIssueや変更履歴): [https://github.com/golang/go/issues](https://github.com/golang/go/issues)
*   Goのコードレビューシステム (Gerrit): [https://go.dev/cl/](https://go.dev/cl/) (コミットメッセージに記載されている`https://golang.org/cl/11485044`のベースURL)