[インデックス 19379] ファイルの概要
このコミットは、Go言語のデバッグツールである cmd/addr2line および cmd/objdump が、Plan 9オペレーティングシステムで使用される a.out オブジェクトファイルを適切に処理できるようにするための変更です。これにより、Plan 9環境でコンパイルされたGoバイナリのデバッグと解析能力が向上します。
コミット
- コミットハッシュ:
23e8c0d28135f9d22af1c3ad0ab7fcef7632a22f - 作者: David du Colombier 0intro@gmail.com
- コミット日時: 2014年5月16日 金曜日 16:51:27 +0200
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/23e8c0d28135f9d22af1c3ad0ab7fcef7632a22f
元コミット内容
cmd/addr2line, cmd/objdump: handle Plan 9 a.out object files
Update #7947.
LGTM=iant
R=rsc, iant
CC=golang-codereviews
https://golang.org/cl/91500044
変更の背景
この変更の背景には、Go言語が様々なオペレーティングシステムやアーキテクチャをサポートする中で、Plan 9環境における開発およびデバッグ体験を向上させる必要があったことが挙げられます。addr2line と objdump は、Goバイナリの解析において非常に重要なツールですが、これらがPlan 9の a.out 形式の実行ファイルを認識・解析できないという問題がありました。
コミットメッセージにある Update #7947 は、おそらくGoの内部的な課題追跡システムにおける特定の課題(Plan 9 a.out ファイルのサポート不足)を指していると考えられます。この課題を解決することで、Plan 9上でGoアプリケーションを開発するユーザーが、アドレスからソースコードの行情報を取得したり、バイナリの逆アセンブルを行ったりできるようになり、デバッグ作業が大幅に効率化されます。
具体的には、GoのツールチェインがPlan 9の a.out 形式をネイティブに理解し、シンボル情報やPC-Lineテーブル(プログラムカウンタとソースコードの行番号のマッピング)を抽出できるようにすることが目的でした。これにより、Goのクロスプラットフォーム対応がさらに強化されました。
前提知識の解説
Plan 9 オペレーティングシステム
Plan 9 from Bell Labsは、Unixの設計思想をさらに推し進めた分散オペレーティングシステムです。全てのリソース(ファイル、デバイス、ネットワーク接続など)をファイルとして表現し、ファイルシステムを通じてアクセスするという徹底した「全てはファイル」の原則に基づいています。Go言語の設計者の一部はPlan 9の開発にも携わっており、Go言語にはPlan 9の影響が見られます。
a.out オブジェクトファイル形式
a.out は、Unix系システムで古くから使われている実行可能ファイル、オブジェクトファイル、共有ライブラリのフォーマットです。Plan 9もこの a.out 形式を独自の拡張を加えて使用しています。Plan 9の a.out ファイルは、ヘッダ、プログラムテキスト、データ、シンボルテーブル、PC/SPオフセットテーブル、PC/Line Numberテーブルなどのセクションで構成されます。特に、シンボルテーブルとPC/Line Numberテーブルは、デバッグツールがアドレスをソースコード情報にマッピングするために不可欠な情報を含んでいます。
cmd/addr2line
addr2line は、コンパイルされた実行ファイル内のメモリアドレスを、対応するソースコードのファイル名と行番号に変換するデバッグツールです。クラッシュレポートやプロファイリングデータなどで、生のメモリアドレスしか得られない場合に、そのアドレスがどの関数のどの行に該当するかを特定するために使用されます。Go言語の go tool addr2line <binary> コマンドとして提供されます。
cmd/objdump
objdump は、実行可能ファイルやオブジェクトファイルを逆アセンブルし、機械語命令をアセンブリ言語で表示するツールです。プログラムの低レベルな動作を理解したり、最適化の効果を確認したりする際に役立ちます。Go言語の go tool objdump <binary> コマンドとして提供されます。
Go言語の debug パッケージ群
Go言語の標準ライブラリには、様々な実行ファイル形式を解析するための debug パッケージ群が含まれています。例えば、debug/elf (ELF形式)、debug/macho (Mach-O形式)、debug/pe (PE形式) などがあります。このコミットでは、Plan 9の a.out 形式を扱うための debug/plan9obj パッケージが利用されています。
debug/plan9obj パッケージ
debug/plan9obj パッケージは、Go言語でPlan 9 a.out オブジェクトファイルを読み込み、解析するための機能を提供します。このパッケージを使用することで、Plan 9バイナリからセクション情報、シンボルテーブル、その他のメタデータを抽出できます。このコミットでは、このパッケージを addr2line と objdump に統合することで、Plan 9 a.out ファイルのサポートを実現しています。
技術的詳細
このコミットの技術的な核心は、cmd/addr2line と cmd/objdump の両ツールが、既存のELF、Mach-O、PEといったオブジェクトファイル形式のハンドリングロジックに加えて、Plan 9 a.out 形式のファイルを認識し、そこから必要なデバッグ情報を抽出できるように拡張された点にあります。
具体的には、以下のステップでPlan 9 a.out ファイルのサポートが追加されています。
-
debug/plan9objパッケージのインポート:src/cmd/addr2line/main.goとsrc/cmd/objdump/main.goの両方で、新しくdebug/plan9objパッケージがインポートされています。これにより、Plan 9a.outファイルを解析するためのAPIが利用可能になります。 -
ファイル形式の自動検出と処理:
loadTables関数(addr2line)およびloadTables関数(objdump)内で、入力ファイルがPlan 9a.out形式であるかどうかのチェックが追加されています。plan9obj.NewFile(f)を呼び出し、エラーがなければPlan 9a.outファイルとして処理を開始します。 -
シンボル情報の抽出:
findPlan9Symbolというヘルパー関数が新しく追加されています。この関数はplan9obj.Fileオブジェクトとシンボル名(例: "text", "pclntab", "symtab")を受け取り、対応するplan9obj.Symオブジェクトを返します。addr2lineとobjdumpは、実行可能コードの開始アドレスを示す "text" シンボルを検索してtextStartを取得します。
-
PC-Lineテーブルとシンボルテーブルのロード:
loadPlan9Tableというヘルパー関数が新しく追加されています。この関数はplan9obj.Fileオブジェクトと、テーブルの開始シンボル名(例: "pclntab", "symtab")および終了シンボル名(例: "epclntab", "esymtab")を受け取り、対応するテーブルのバイトスライスを返します。pclntab(PC-Lineテーブル) とsymtab(シンボルテーブル) は、Goバイナリのデバッグ情報の中核をなすデータ構造です。addr2lineはpclntabを使用してアドレスをファイル/行番号にマッピングし、objdumpはsymtabを使用してシンボル名を解決します。loadPlan9Table関数内では、textセクションのデータからpclntabやsymtabのオフセットを計算しています。これは、Plan 9a.outのシンボル値がテキストセクションの先頭からのオフセットとして表現されるためです。
-
テストの修正:
addr2line_test.goとobjdump_test.goから、Plan 9環境でのテストスキップロジックが削除されています。これは、このコミットによってPlan 9環境でもこれらのツールが正しく動作するようになったため、テストをスキップする必要がなくなったことを意味します。
これらの変更により、addr2line と objdump は、Plan 9 a.out 形式のGoバイナリから、関数名、ソースファイル名、行番号、および逆アセンブルされたコードを正確に抽出できるようになりました。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は、src/cmd/addr2line/main.go と src/cmd/objdump/main.go の2つのファイルに集中しています。
src/cmd/addr2line/main.go の変更
debug/plan9objパッケージのインポートが追加されました。loadTables関数内に、Plan 9a.outファイルを処理するための新しい条件分岐が追加されました。plan9obj.NewFile(f)でファイルを開き、成功した場合にPlan 9オブジェクトとして扱います。findPlan9Symbolを使用して "text" シンボルを見つけ、textStartを設定します。loadPlan9Tableを使用して "pclntab" と "symtab" をロードします。
findPlan9Symbol関数が新しく追加されました。これはplan9obj.Fileから指定された名前のシンボルを検索します。loadPlan9Table関数が新しく追加されました。これはplan9obj.Fileから指定された開始/終了シンボル名に対応するテーブルデータをロードします。
src/cmd/objdump/main.go の変更
debug/plan9objパッケージのインポートが追加されました。loadTables関数内に、Plan 9a.outファイルを処理するための新しい条件分岐が追加されました。plan9obj.NewFile(f)でファイルを開き、成功した場合にPlan 9オブジェクトとして扱います。findPlan9Symbolを使用して "text" シンボルを見つけ、textStartを設定します。obj.Section("text")からテキストデータを取得します。loadPlan9Tableを使用して "pclntab" と "symtab" をロードします。
findPlan9Symbol関数が新しく追加されました。(addr2lineと同様の機能)loadPlan9Table関数が新しく追加されました。(addr2lineと同様の機能)
テストファイルの変更
src/cmd/addr2line/addr2line_test.goおよびsrc/cmd/objdump/objdump_test.goから、runtime.GOOS == "plan9"の場合にテストをスキップするロジックが削除されました。
コアとなるコードの解説
このコミットで追加された主要なロジックは、findPlan9Symbol と loadPlan9Table の2つのヘルパー関数、そしてそれらが addr2line と objdump の loadTables 関数に統合される部分です。
findPlan9Symbol 関数
func findPlan9Symbol(f *plan9obj.File, name string) (*plan9obj.Sym, error) {
syms, err := f.Symbols() // Plan 9オブジェクトファイルから全てのシンボルを取得
if err != nil {
return nil, err
}
for _, s := range syms { // シンボルリストをイテレート
if s.Name != name { // 指定された名前のシンボルを探す
continue
}
return &s, nil // 見つかったシンボルを返す
}
return nil, fmt.Errorf("no %s symbol found", name) // 見つからなければエラー
}
この関数は、debug/plan9obj パッケージが提供する File オブジェクトからシンボルテーブルを取得し、指定された名前のシンボル(例: "text", "pclntab")を検索します。Goバイナリのデバッグ情報では、特定のデータセクション(PC-Lineテーブルやシンボルテーブル)の開始と終了がシンボルとしてマークされているため、これらのシンボルを見つけることがデータの正確な抽出に不可欠です。
loadPlan9Table 関数
func loadPlan9Table(f *plan9obj.File, sname, ename string) ([]byte, error) {
ssym, err := findPlan9Symbol(f, sname) // 開始シンボルを見つける
if err != nil {
return nil, err
}
esym, err := findPlan9Symbol(f, ename) // 終了シンボルを見つける
if err != nil {
return nil, err
}
text, err := findPlan9Symbol(f, "text") // "text" シンボルを見つける (オフセット計算用)
if err != nil {
return nil, err
}
sect := f.Section("text") // "text" セクションを取得
if sect == nil {
return nil, err
}
data, err := sect.Data() // "text" セクションの生データを取得
if err != nil {
return nil, err
}
// シンボルの値はテキストセクションの先頭からのオフセットとして扱われるため、
// 実際のデータ範囲を計算してスライスを返す
return data[ssym.Value-text.Value : esym.Value-text.Value], nil
}
この関数は、findPlan9Symbol を利用して、特定のテーブル(pclntab や symtab)の開始シンボルと終了シンボルを見つけます。Plan 9 a.out の特性として、これらのテーブルがテキストセクション内に配置され、シンボル値がテキストセクションの開始アドレスからのオフセットとして扱われるため、text シンボルの値を使用して正確なバイト範囲を計算し、その部分のデータを抽出しています。これにより、addr2line や objdump が必要とするPC-Lineテーブルやシンボルテーブルの生データが提供されます。
loadTables 関数への統合
addr2line と objdump の両方にある loadTables 関数は、入力された実行ファイルがどの形式であるかを判断し、それぞれの形式に応じた処理を行う役割を担っています。このコミットでは、既存のELF、Mach-O、PE形式のチェックに加えて、plan9obj.NewFile(f) を試行するロジックが追加されました。これにより、ファイルがPlan 9 a.out 形式であれば、上記の findPlan9Symbol や loadPlan9Table を呼び出して必要な情報を抽出し、ツールがその情報を利用してアドレス解決や逆アセンブルを実行できるようになります。
この一連の変更により、GoのデバッグツールがPlan 9環境でコンパイルされたバイナリを完全にサポートし、クロスプラットフォームな開発・デバッグ体験が向上しました。
関連リンク
- Go issue #7947 (言及されているが、公開されているGoリポジトリのIssueとしては見つからず、内部的なものか古いものと推測される)
- Go
debug/plan9objpackage documentation: https://pkg.go.dev/debug/plan9obj - Go
cmd/addr2linesource: https://github.com/golang/go/tree/master/src/cmd/addr2line - Go
cmd/objdumpsource: https://github.com/golang/go/tree/master/src/cmd/objdump
参考にした情報源リンク
- Plan 9
a.outformat: https://9p.io/sys/doc/a.out.html - Go
debug/plan9objpackage documentation: https://pkg.go.dev/debug/plan9obj - Go
cmd/addr2linedocumentation: https://pkg.go.dev/cmd/addr2line - Go
cmd/objdumpdocumentation: https://pkg.go.dev/cmd/objdump - Google Search results for "Plan 9 a.out object file format", "Go issue 7947", "Go debug/plan9obj package", "Go cmd/addr2line", "Go cmd/objdump".