[インデックス 16805] ファイルの概要
このコミットでは、Go 1.2の新しいpcln (Program Counter to Line Number) テーブル形式に対応するために、debug/gosymパッケージが更新されています。具体的には、以下のファイルが変更されました。
src/pkg/debug/gosym/pclinetest.asm: テスト用のアセンブリファイル。新しいpclnテーブル形式のテストのために調整されています。src/pkg/debug/gosym/pclntab.go:LineTable構造体と、プログラムカウンタと行番号のマッピングを扱うロジックが含まれる主要なファイルです。Go 1.2形式のpclnテーブルの解析と利用に関する大幅な変更が加えられています。src/pkg/debug/gosym/pclntab_test.go:pclntab.goのテストファイル。Go 1.2形式のpclnテーブルのテストケースが追加・修正されています。src/pkg/debug/gosym/symtab.go: シンボルテーブルを扱うファイル。Table構造体と、シンボルおよび関数情報の管理に関する変更が含まれています。特に、Go 1.2形式のpclnテーブルとの連携が強化されています。
コミット
commit d7b4a09ca8037d91a5a89ea0e9ab36cef37cef74
Author: Russ Cox <rsc@golang.org>
Date: Thu Jul 18 10:12:14 2013 -0400
debug/gosym: update for Go 1.2 pcln table
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/11495043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d7b4a09ca8037d91a5a89ea0e9ab36cef37cef74
元コミット内容
debug/gosym: update for Go 1.2 pcln table
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/11495043
変更の背景
このコミットの主な背景は、Go 1.2で導入された新しいシンボルテーブルおよびpcln (Program Counter to Line Number) テーブルのフォーマットへの対応です。Goの以前のバージョン(Go 1.1以前)では、Plan 9ベースのシンボルテーブル形式が使用されていました。この古い形式にはいくつかの問題がありました。
- 不安定なコードの存在: シンボルデコードプロセスの一部に不安定な部分があり、信頼性に欠けることがありました。
- メモリフットプリントの増大: ランタイムのメモリ使用量が多くなる傾向がありました。
- 起動時間のオーバーヘッド: プログラム起動時にテーブルをデコードする必要があり、これが起動時間のボトルネックとなることがありました。
- ガベージコレクション (GC) 実装の複雑さ: 新しいGCメタデータを追加する際に、既存のシンボルテーブル形式が障壁となることがありました。
- メタデータフローの非効率性: コンパイラからランタイムへのメタデータの受け渡しが、リンカによる複雑な変換を必要とし、非効率でした。
これらの問題を解決するため、Go 1.2では新しいシンボルテーブル形式が導入されました。この新しい形式は、ランタイムが直接利用できるインメモリ構造として設計されており、起動時のデコードコストを排除し、ランタイムメモリを削減することを目的としています。このコミットは、debug/gosymパッケージがこの新しいGo 1.2形式のpclnテーブルを正しく解析し、利用できるようにするためのものです。
前提知識の解説
プログラムカウンタ (PC) と行番号 (Line Number)
- プログラムカウンタ (PC): CPUが次に実行する命令のアドレスを指すレジスタです。Goのバイナリでは、特定のPC値がソースコードの特定の命令に対応します。
- 行番号 (Line Number): ソースコード内の行の識別子です。デバッグやプロファイリングにおいて、実行中のPCがソースコードのどの行に対応するかを知ることは非常に重要です。
pcln (Program Counter to Line Number) テーブル
pclnテーブルは、Goの実行可能バイナリに含まれるメタデータの一つで、プログラムカウンタ (PC) とソースコードの行番号をマッピングするために使用されます。これにより、デバッガやプロファイラが実行中のコードの場所をソースコード上で特定できるようになります。
シンボルテーブル
シンボルテーブルは、プログラム内のシンボル(関数名、変数名など)とそれらがメモリ上で占めるアドレスとの対応関係を記録したデータ構造です。デバッガはシンボルテーブルを利用して、人間が読めるシンボル名と機械が理解できるメモリアドレスを相互に変換します。
Goのシンボルテーブルの進化 (Go 1.1以前 vs. Go 1.2)
-
Go 1.1以前:
- 各関数(
Func構造体で表現される)が独自のLineTableを持っていました。 - 行番号は、プログラム全体のすべてのソース行にわたる通し番号(絶対行番号)でした。
- この絶対行番号は、別途ファイル名とファイル内の行番号に変換する必要がありました。
- Plan 9のシンボルおよび
pc->lineテーブル形式が使用され、これらはメモリにマッピングされた後、ランタイムによって起動時にデコードされていました。このデコードプロセスが起動時間とメモリ割り当てのオーバーヘッドを引き起こしていました。 - 新しいメタデータを追加する際に、コンパイラ、リンカ、ランタイムの変更が必要となり、プロセスが複雑でした。
- 各関数(
-
Go 1.2:
- データ形式が変更され、プログラム全体で共有される単一の
LineTableが存在するようになりました。 - 絶対行番号の概念がなくなり、特定のファイル内の行番号のみが扱われるようになりました。
- 新しい形式は、ランタイムが前処理なしで直接使用できるように設計されています。これにより、起動時のデコードコストが排除され、ランタイムメモリが削減されます。
FUNCDATAとPCDATAという擬似命令が導入され、コンパイラがカスタムメタデータをリンカの変更なしに直接ランタイムに渡せるようになりました。pc-valueテーブルという概念が導入され、PCからint32値へのマッピングを一般化しました。これは行番号だけでなく、スタックフレームサイズ、GCのためのスタック変数生存情報、パニック処理のためのレジスタフラッシュ情報など、様々なメタデータに利用されます。
- データ形式が変更され、プログラム全体で共有される単一の
このコミットは、debug/gosymパッケージがGo 1.2で導入されたこれらの変更に対応し、新しい形式のpclnテーブルを正確に解析・利用できるようにするためのものです。
技術的詳細
このコミットの核となる変更は、src/pkg/debug/gosym/pclntab.goとsrc/pkg/debug/gosym/symtab.goにおけるGo 1.2の新しいpclnテーブル形式への対応です。
LineTable構造体の変更 (src/pkg/debug/gosym/pclntab.go)
LineTable構造体は、Go 1.2形式のメタデータを保持するために大幅に拡張されました。
type LineTable struct {
Data []byte
PC uint64
Line int
// Go 1.2 state
mu sync.Mutex
go12 int // is this in Go 1.2 format? -1 no, 0 unknown, 1 yes
binary binary.ByteOrder
quantum uint32
ptrsize uint32
functab []byte
nfunctab uint32
filetab []byte
nfiletab uint32
fileMap map[string]uint32
}
mu: ミューテックス。Go 1.2形式の初期化処理(go12InitやinitFileMap)がスレッドセーフに行われるようにします。go12: このLineTableがGo 1.2形式であるかどうかを示すフラグ(-1: 否、0: 未知、1: はい)。binary: バイトオーダー(リトルエンディアンまたはビッグエンディアン)。quantum: プログラムカウンタの量子(命令のアラインメント)。ptrsize: ポインタのサイズ(4バイトまたは8バイト)。functab: 関数テーブルの生データ。nfunctab: 関数テーブル内のエントリ数。filetab: ファイルテーブルの生データ。nfiletab: ファイルテーブル内のエントリ数。fileMap: ファイル名からファイル番号へのマッピングをキャッシュするためのマップ。
Go 1.2形式の初期化と検出 (go12Init, isGo12)
isGo12():LineTableがGo 1.2形式であるかを報告します。内部でgo12Init()を呼び出して初期化を試みます。go12Init():LineTableのDataフィールドを解析し、Go 1.2形式のヘッダ(マジックナンバー0xfffffffb、PC量子、ポインタサイズなど)をチェックします。解析に失敗した場合(例: パニックが発生した場合)、Go 1.2形式ではないと判断します。成功した場合、binary、quantum、ptrsize、functab、nfunctab、filetab、nfiletabを初期化します。
pc-valueテーブルの解析
Go 1.2のpclnテーブルは、pc-valueテーブルという汎用的な形式でエンコードされています。これは、PCとそれに対応する値(行番号、ファイル番号など)のペアを効率的に表現するためのものです。
uintptr(b []byte) uint64: 指定されたバイトスライスから、テーブルのポインタサイズに応じたuintptr値を読み取ります。readvarint(pp *[]byte) uint32: 可変長整数(varint)を読み取ります。pc-valueテーブルのデルタ値のエンコーディングに使用されます。step(p *[]byte, pc *uint64, val *int32, first bool) bool: エンコードされたpc-valueテーブルから次のPCと値のペアに進みます。PCデルタと値デルタを読み取り、現在のPCと値を更新します。pcvalue(off uint32, entry, targetpc uint64) int32: 指定されたオフセットから始まるpc-valueテーブルを走査し、targetpcに対応する値を返します。
関数情報の検索 (findFunc)
findFunc(pc uint64) []byte: 指定されたプログラムカウンタpcに対応する関数情報(Func構造体の生データ)を検索します。関数テーブル(functab)はPC値でソートされており、二分探索によって効率的に検索されます。
PCと行番号/ファイル名のマッピング (Go 1.2形式)
go12PCToLine(pc uint64) (line int): Go 1.2形式のpclnテーブルを使用して、プログラムカウンタpcに対応する行番号を返します。findFuncで関数情報を見つけ、その中のpclnテーブルのオフセットを使ってpcvalueを呼び出します。go12PCToFile(pc uint64) (file string): Go 1.2形式のpclnテーブルを使用して、プログラムカウンタpcに対応するファイル名を返します。findFuncで関数情報を見つけ、その中のpcfileテーブルのオフセットを使ってpcvalueを呼び出し、ファイル番号からファイル名を解決します。go12LineToPC(file string, line int) (pc uint64): Go 1.2形式のpclnテーブルを使用して、指定されたファイルと行番号に対応するプログラムカウンタを返します。すべての関数を走査し、findFileLineを呼び出して一致するPCを探します。findFileLine(entry uint64, filetab, linetab uint32, filenum, line int32) uint64: 特定の関数内で、指定されたファイル番号と行番号に対応するプログラムカウンタを検索します。ファイルテーブルと行テーブルを同時に走査し、両方が一致するPCを見つけます。
ファイル名マップの初期化 (initFileMap, go12MapFiles)
initFileMap(): ファイルテーブル(filetab)を解析し、ファイル名からファイル番号へのマッピング(fileMap)を初期化します。go12MapFiles(m map[string]*Obj, obj *Obj): Go 1.2形式のLineTable内のすべてのファイル名をマップmに追加します。
Table構造体の変更 (src/pkg/debug/gosym/symtab.go)
Table構造体は、Go 1.2形式のLineTableへの参照を持つようになりました。
type Table struct {
Syms []Sym
Funcs []Func
Files map[string]*Obj // nil for Go 1.2 and later binaries
Objs []Obj // nil for Go 1.2 and later binaries
go12line *LineTable // Go 1.2 line number table
}
go12line: Go 1.2形式のLineTableへのポインタ。Go 1.2以降のバイナリでは、このフィールドが使用され、FilesとObjsはnilになります。
NewTable関数の変更 (src/pkg/debug/gosym/symtab.go)
NewTable関数は、渡されたpclnがGo 1.2形式であるかをチェックし、もしそうであればTable.go12lineを設定します。
func NewTable(symtab []byte, pcln *LineTable) (*Table, error) {
// ...
var t Table
if pcln.isGo12() {
t.go12line = pcln
}
// ...
}
また、Go 1.2形式のバイナリでは、すべての関数が単一のObjにまとめられ、ファイル情報はgo12lineを通じて管理されるため、従来のObjのPathsフィールドやTable.Files、Table.Objsの扱いが変更されています。
PCToLineとLineToPCの変更 (src/pkg/debug/gosym/symtab.go)
TableのPCToLineとLineToPCメソッドは、Go 1.2形式のpclnテーブルが存在する場合はそちらを利用するように変更されました。
PCToLine(pc uint64) (file string, line int, fn *Func):t.go12line != nilの場合、t.go12line.go12PCToFile(pc)とt.go12line.go12PCToLine(pc)を呼び出してファイル名と行番号を取得します。- それ以外の場合、従来の
fn.Obj.lineFromAline(fn.LineTable.PCToLine(pc))を使用します。
LineToPC(file string, line int) (pc uint64, fn *Func, err error):t.go12line != nilの場合、t.go12line.go12LineToPC(file, line)を呼び出してPCを取得します。- それ以外の場合、従来の
obj.alineFromLine(file, line)を使用します。
これらの変更により、debug/gosymパッケージはGo 1.2以降のバイナリのデバッグ情報も正確に解析できるようになりました。
コアとなるコードの変更箇所
src/pkg/debug/gosym/pclntab.go
LineTable構造体にGo 1.2形式のメタデータ(mu,go12,binary,quantum,ptrsize,functab,nfunctab,filetab,nfiletab,fileMap)が追加されました。isGo12()、go12Init()、uintptr()、readvarint()、step()、pcvalue()、findFunc()、string()などのGo 1.2形式のpclnテーブルを解析するためのヘルパー関数が追加されました。go12PCToLine()、go12PCToFile()、go12LineToPC()、findFileLine()、initFileMap()、go12MapFiles()など、Go 1.2形式のPC-行番号/ファイル名マッピングを行う関数が追加されました。- 既存の
PCToLine()とLineToPC()メソッドが、Go 1.2形式のLineTableが存在する場合は新しいロジックを呼び出すように変更されました。 quantum定数がoldQuantumにリネームされ、Go 1.2形式ではLineTable.quantumが使用されるようになりました。
src/pkg/debug/gosym/symtab.go
Obj構造体のコメントが更新され、Go 1.2以降では単一のObjがプログラム全体を表すことが明記されました。Table構造体にgo12line *LineTableフィールドが追加されました。NewTable()関数内で、pcln.isGo12()が真の場合にt.go12lineが設定されるようになりました。また、Go 1.2形式の場合、t.Objsは単一のObjを保持し、t.Filesはgo12line.go12MapFilesによって初期化されるようになりました。PCToLine()とLineToPC()メソッドが、t.go12lineが存在する場合はGo 1.2形式のロジックを呼び出すように変更されました。Obj.lineFromAline()のコメントが更新され、Go 1.1以前のレガシーコードであることが明記されました。
src/pkg/debug/gosym/pclinetest.asm
- テスト用のPC-行番号データに
BYTE $255;が追加されました。これは、pcfromline関数とmain関数の終端を示すマーカーとして使用され、テストロジックの調整に役立っています。
src/pkg/debug/gosym/pclntab_test.go
dotest()関数がGo 1.2形式のテストに対応するために修正されました。特に、go tool 6lコマンドに-H linuxオプションが追加され、Linux環境でのテストバイナリ生成が明示されました。TestLineFromAline()とTestLineAline()テストが、Go 1.2形式のテーブルではスキップされるようになりました。これは、alineの概念がGo 1.2形式には存在しないためです。TestPCLine()テストが、Go 1.2形式のpclnテーブルの変更に対応するように調整されました。特に、textdat[off] == 255によるブレーク条件が追加されています。
コアとなるコードの解説
LineTable構造体 (src/pkg/debug/gosym/pclntab.go)
Go 1.2のpclnテーブルは、以前のバージョンとは異なり、プログラム全体で共有される単一のテーブルとして設計されています。このため、LineTable構造体は、そのテーブルの全体的なメタデータと、テーブル内の各セクション(関数テーブル、ファイルテーブルなど)へのポインタを保持するように拡張されました。
Data:pclnテーブル全体の生バイトデータ。このデータから様々な情報を解析します。functab,nfunctab:pclnテーブル内の関数エントリのリストとその数。各エントリはPCと、対応するFunc構造体へのオフセットを含みます。filetab,nfiletab:pclnテーブル内のファイル名エントリのリストとその数。各エントリはファイル名文字列へのオフセットを含みます。binary,quantum,ptrsize: バイナリのバイトオーダー、PCの量子(命令のアラインメント)、ポインタサイズといった、テーブルの解析に必要なアーキテクチャ固有の情報を保持します。
go12Init() (src/pkg/debug/gosym/pclntab.go)
この関数は、LineTableがGo 1.2形式であるかを判断し、そのメタデータを初期化する重要な役割を担います。マジックナンバーのチェックから始まり、関数テーブルやファイルテーブルのオフセットとサイズを特定します。この初期化は一度だけ行われ、その結果はgo12フィールドにキャッシュされます。
findFunc(pc uint64) []byte (src/pkg/debug/gosym/pclntab.go)
Go 1.2のpclnテーブルでは、すべての関数情報が単一の関数テーブルに格納されています。この関数は、与えられたプログラムカウンタpcがどの関数に属するかを効率的に見つけ出すために、関数テーブルに対して二分探索を実行します。これにより、PCから対応するFunc構造体の生データへの高速なマッピングが可能になります。
pcvalue(off uint32, entry, targetpc uint64) int32 (src/pkg/debug/gosym/pclntab.go)
この関数は、Go 1.2で導入されたpc-valueテーブルの汎用的なデコーダです。pc-valueテーブルは、PCの範囲とそれに対応する値(行番号、ファイル番号など)を効率的にエンコードします。この関数は、指定されたオフセットからテーブルを読み込み、targetpcに対応する値を返します。これは、行番号やファイル番号の取得の基盤となります。
go12PCToLine(pc uint64) (line int) と go12PCToFile(pc uint64) (file string) (src/pkg/debug/gosym/pclntab.go)
これらの関数は、Go 1.2形式のpclnテーブルからPCに対応する行番号とファイル名を取得するための主要なAPIです。内部的にはfindFuncとpcvalueを組み合わせて使用し、効率的に情報を抽出します。
go12LineToPC(file string, line int) (pc uint64) (src/pkg/debug/gosym/pclntab.go)
この関数は、Go 1.2形式のpclnテーブルからファイル名と行番号に対応するプログラムカウンタを取得します。これは、デバッガがソースコードの特定の行にブレークポイントを設定する際などに利用されます。すべての関数を走査し、findFileLineを呼び出して一致するPCを見つけます。
Table構造体とgo12lineフィールド (src/pkg/debug/gosym/symtab.go)
Table構造体にgo12line *LineTableフィールドが追加されたことで、debug/gosymパッケージはGo 1.2以降のバイナリのデバッグ情報を透過的に扱えるようになりました。NewTable関数でこのフィールドが適切に設定されることで、PCToLineやLineToPCのような高レベルのAPIが、内部的にGo 1.2形式のLineTableのメソッドを呼び出すか、従来のロジックを呼び出すかを自動的に切り替えることができます。これにより、ユーザーはバイナリのバージョンを意識することなく、一貫したインターフェースでデバッグ情報にアクセスできるようになります。
関連リンク
- Go CL 11495043: https://golang.org/cl/11495043
- Go 1.2 Symbol Table Format: https://golang.org/s/go12symtab
参考にした情報源リンク
- Go 1.2 Symbol Table Format: https://golang.org/s/go12symtab