[インデックス 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/plan9obj
package documentation: https://pkg.go.dev/debug/plan9obj - Go
cmd/addr2line
source: https://github.com/golang/go/tree/master/src/cmd/addr2line - Go
cmd/objdump
source: https://github.com/golang/go/tree/master/src/cmd/objdump
参考にした情報源リンク
- Plan 9
a.out
format: https://9p.io/sys/doc/a.out.html - Go
debug/plan9obj
package documentation: https://pkg.go.dev/debug/plan9obj - Go
cmd/addr2line
documentation: https://pkg.go.dev/cmd/addr2line - Go
cmd/objdump
documentation: 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".