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

[インデックス 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環境における開発およびデバッグ体験を向上させる必要があったことが挙げられます。addr2lineobjdump は、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バイナリからセクション情報、シンボルテーブル、その他のメタデータを抽出できます。このコミットでは、このパッケージを addr2lineobjdump に統合することで、Plan 9 a.out ファイルのサポートを実現しています。

技術的詳細

このコミットの技術的な核心は、cmd/addr2linecmd/objdump の両ツールが、既存のELF、Mach-O、PEといったオブジェクトファイル形式のハンドリングロジックに加えて、Plan 9 a.out 形式のファイルを認識し、そこから必要なデバッグ情報を抽出できるように拡張された点にあります。

具体的には、以下のステップでPlan 9 a.out ファイルのサポートが追加されています。

  1. debug/plan9obj パッケージのインポート: src/cmd/addr2line/main.gosrc/cmd/objdump/main.go の両方で、新しく debug/plan9obj パッケージがインポートされています。これにより、Plan 9 a.out ファイルを解析するためのAPIが利用可能になります。

  2. ファイル形式の自動検出と処理: loadTables 関数(addr2line)および loadTables 関数(objdump)内で、入力ファイルがPlan 9 a.out 形式であるかどうかのチェックが追加されています。plan9obj.NewFile(f) を呼び出し、エラーがなければPlan 9 a.out ファイルとして処理を開始します。

  3. シンボル情報の抽出:

    • findPlan9Symbol というヘルパー関数が新しく追加されています。この関数は plan9obj.File オブジェクトとシンボル名(例: "text", "pclntab", "symtab")を受け取り、対応する plan9obj.Sym オブジェクトを返します。
    • addr2lineobjdump は、実行可能コードの開始アドレスを示す "text" シンボルを検索して textStart を取得します。
  4. PC-Lineテーブルとシンボルテーブルのロード:

    • loadPlan9Table というヘルパー関数が新しく追加されています。この関数は plan9obj.File オブジェクトと、テーブルの開始シンボル名(例: "pclntab", "symtab")および終了シンボル名(例: "epclntab", "esymtab")を受け取り、対応するテーブルのバイトスライスを返します。
    • pclntab (PC-Lineテーブル) と symtab (シンボルテーブル) は、Goバイナリのデバッグ情報の中核をなすデータ構造です。addr2linepclntab を使用してアドレスをファイル/行番号にマッピングし、objdumpsymtab を使用してシンボル名を解決します。
    • loadPlan9Table 関数内では、text セクションのデータから pclntabsymtab のオフセットを計算しています。これは、Plan 9 a.out のシンボル値がテキストセクションの先頭からのオフセットとして表現されるためです。
  5. テストの修正: addr2line_test.goobjdump_test.go から、Plan 9環境でのテストスキップロジックが削除されています。これは、このコミットによってPlan 9環境でもこれらのツールが正しく動作するようになったため、テストをスキップする必要がなくなったことを意味します。

これらの変更により、addr2lineobjdump は、Plan 9 a.out 形式のGoバイナリから、関数名、ソースファイル名、行番号、および逆アセンブルされたコードを正確に抽出できるようになりました。

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

このコミットにおける主要なコード変更は、src/cmd/addr2line/main.gosrc/cmd/objdump/main.go の2つのファイルに集中しています。

src/cmd/addr2line/main.go の変更

  • debug/plan9obj パッケージのインポートが追加されました。
  • loadTables 関数内に、Plan 9 a.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 9 a.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" の場合にテストをスキップするロジックが削除されました。

コアとなるコードの解説

このコミットで追加された主要なロジックは、findPlan9SymbolloadPlan9Table の2つのヘルパー関数、そしてそれらが addr2lineobjdumploadTables 関数に統合される部分です。

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 を利用して、特定のテーブル(pclntabsymtab)の開始シンボルと終了シンボルを見つけます。Plan 9 a.out の特性として、これらのテーブルがテキストセクション内に配置され、シンボル値がテキストセクションの開始アドレスからのオフセットとして扱われるため、text シンボルの値を使用して正確なバイト範囲を計算し、その部分のデータを抽出しています。これにより、addr2lineobjdump が必要とするPC-Lineテーブルやシンボルテーブルの生データが提供されます。

loadTables 関数への統合

addr2lineobjdump の両方にある loadTables 関数は、入力された実行ファイルがどの形式であるかを判断し、それぞれの形式に応じた処理を行う役割を担っています。このコミットでは、既存のELF、Mach-O、PE形式のチェックに加えて、plan9obj.NewFile(f) を試行するロジックが追加されました。これにより、ファイルがPlan 9 a.out 形式であれば、上記の findPlan9SymbolloadPlan9Table を呼び出して必要な情報を抽出し、ツールがその情報を利用してアドレス解決や逆アセンブルを実行できるようになります。

この一連の変更により、GoのデバッグツールがPlan 9環境でコンパイルされたバイナリを完全にサポートし、クロスプラットフォームな開発・デバッグ体験が向上しました。

関連リンク

参考にした情報源リンク