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

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

このコミットは、Go言語のツールチェインに含まれるobjdumpコマンドに、実行可能ファイルの逆アセンブル機能を追加するものです。これにより、Goのバイナリから機械語命令を読みやすいアセンブリコードに変換して表示できるようになります。

コミット

commit 79fb16d32c20f14809a924e28d0ab18e1052647d
Author: Russ Cox <rsc@golang.org>
Date:   Wed May 14 19:51:15 2014 -0400

    objdump: implement disassembly
    
    There is some duplication here with cmd/nm.
    There is a TODO to address that after 1.3 is out.
    
    Update #7452
    
    x86 disassembly works and is tested.
    
    The arm disassembler does not exist yet
    and is therefore not yet hooked up.
    
    LGTM=crawshaw, iant
    R=crawshaw, iant
    CC=golang-codereviews
    https://golang.org/cl/91360046

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

https://github.com/golang/go/commit/79fb16d32c20f14809a924e28d0ab18e1052647d

元コミット内容

objdump: implement disassembly

このコミットは、objdumpツールに逆アセンブル機能を実装します。 cmd/nmとの重複が一部ありますが、Go 1.3リリース後に対応予定です。 Issue #7452を更新します。 x86の逆アセンブルは動作確認済みでテストもされています。 ARMの逆アセンブラはまだ存在しないため、まだ接続されていません。

変更の背景

この変更の主な背景は、Go言語のデバッグおよびプロファイリングツール群の強化です。特に、pprofのようなプロファイリングツールが、実行中のGoプログラムのパフォーマンスボトルネックを特定する際に、どの関数がCPU時間を消費しているかを詳細に分析するために、アセンブリレベルでのコードの理解が必要とされます。

以前のobjdumpツールは、GNU objdumpの最小限のシミュレーションであり、pprofをサポートするのに十分な機能しか持っていませんでした。しかし、Issue #7452("cmd/objdump: add disassembler")で示されているように、逆アセンブラ機能が不足していました。この機能が追加されることで、開発者はGoバイナリの内部動作をより深く掘り下げ、最適化の機会を見つけたり、コンパイラの生成するコードを理解したりすることが可能になります。

また、このコミットメッセージには「cmd/nmとの重複が一部ありますが、Go 1.3リリース後に対応予定です」と記載されており、ツール間のコードの再利用と整理も将来的な目標として意識されていたことが伺えます。

前提知識の解説

このコミットを理解するためには、以下の技術的な概念について知っておく必要があります。

  • objdump: objdumpは、オブジェクトファイルや実行可能ファイルに関する情報を表示するためのコマンドラインツールです。通常、シンボルテーブル、セクションヘッダ、リロケーションエントリ、そして最も重要な逆アセンブルされたコードなどを表示できます。GNU Binutilsの一部として広く利用されています。
  • 逆アセンブル (Disassembly): 逆アセンブルとは、機械語(CPUが直接実行するバイナリコード)を、人間が読めるアセンブリ言語のニーモニック(例: MOV, ADD, JMP)に変換するプロセスです。これにより、コンパイルされたプログラムの低レベルな動作を分析できます。
  • 実行可能ファイルフォーマット: オペレーティングシステムは、プログラムを実行するために特定のファイルフォーマットを使用します。
    • ELF (Executable and Linkable Format): Linux、FreeBSDなどのUnix系システムで広く使用されている標準的な実行可能ファイル、オブジェクトファイル、共有ライブラリのフォーマットです。
    • Mach-O (Mach Object): macOS (OS X) およびiOSで使用される実行可能ファイル、オブジェクトファイル、共有ライブラリのフォーマットです。
    • PE (Portable Executable): Microsoft Windowsで使用される実行可能ファイル、オブジェクトファイル、DLL (Dynamic Link Library) のフォーマットです。
    • Plan 9 a.out: Plan 9オペレーティングシステムで使用される実行可能ファイルフォーマットです。Go言語はPlan 9の影響を強く受けており、初期のGoツールチェインではPlan 9のツールや概念が多用されていました。
  • シンボルテーブル: 実行可能ファイルやオブジェクトファイルに含まれるデータ構造で、プログラム内の関数や変数などのシンボル(名前)と、それらがメモリ上のどこに配置されているか(アドレス)のマッピングを記録しています。デバッグや逆アセンブルにおいて非常に重要です。
  • debug/gosymパッケージ: Go標準ライブラリのdebug/gosymパッケージは、Goバイナリに埋め込まれたGo固有のシンボル情報(関数名、ファイル名、行番号など)を解析するために使用されます。これにより、機械語のアドレスとソースコードの行を関連付けることができます。
  • rsc.io/x86/x86asm: このコミットのMakefileで参照されているrsc.io/x86/x86asmは、Russ Cox氏(Go言語の主要開発者の一人)が作成したx86アセンブリのパーサーおよび逆アセンブラライブラリです。Goのobjdumpがx86コードを逆アセンブルするためにこのライブラリを利用しています。

技術的詳細

このコミットは、objdumpツールに逆アセンブル機能を追加するために、複数の重要な技術的変更を導入しています。

  1. 実行可能ファイルフォーマットの解析:

    • src/cmd/objdump/elf.go: ELF形式の実行可能ファイルからシンボル情報を抽出するロジックが追加されました。debug/elfパッケージを利用して、ELFファイルのヘッダ、セクション、シンボルテーブルを解析し、関数やデータのアドレス、サイズ、タイプ(コード、データ、BSSなど)を特定します。
    • src/cmd/objdump/macho.go: Mach-O形式の実行可能ファイルからシンボル情報を抽出するロジックが追加されました。debug/machoパッケージを利用し、Mach-Oファイルのシンボルテーブルを読み込み、シンボルのアドレスと名前を解析します。シンボルのサイズは、ソートされたアドレスリストから次のシンボルとの差分で推測されます。
    • src/cmd/objdump/pe.go: PE形式の実行可能ファイルからシンボル情報を抽出するロジックが追加されました。debug/peパッケージを利用し、PEファイルのヘッダとシンボルテーブルを解析します。PEファイルでは、ImageBaseとセクションのVirtualAddressを考慮してシンボルの絶対アドレスを計算します。
    • src/cmd/objdump/plan9obj.go: Plan 9 a.out形式の実行可能ファイルからシンボル情報を抽出するロジックが追加されました。debug/plan9objパッケージを利用します。
    • これらの新しいファイルは、main.go内のloadSymbols関数によって、ファイルの先頭バイト(マジックナンバー)に基づいて適切なパーサーが選択されるように統合されています。
  2. 逆アセンブルエンジンの統合:

    • src/cmd/objdump/main.goに、各アーキテクチャ(x86-32, x86-64, ARM)に対応する逆アセンブル関数をマッピングするdisasmsマップが導入されました。
    • x86アーキテクチャ(386, amd64)の逆アセンブルには、外部ライブラリrsc.io/x86/x86asmが使用されています。disasm_x86関数がこのライブラリを呼び出し、機械語バイト列をアセンブリ命令の文字列に変換します。
    • ARMアーキテクチャについては、コミット時点では逆アセンブラが未実装であり、disasm_arm関数はプレースホルダーとして機能し、常に"?"とサイズ4を返します。これはコミットメッセージの「The arm disassembler does not exist yet」という記述と一致します。
  3. 出力フォーマットの改善とオプションの追加:

    • main.gomain関数が大幅に改修され、コマンドライン引数の解析ロジックが変更されました。
      • 引数が1つの場合(objdump binary)は、バイナリ全体のテキストセクション(コード)を逆アセンブルし、Go固有のシンボル情報(ファイル名、行番号)と共に表示します。このモードでは、TEXT <symbol>(SB) <file>のようなGoらしい出力フォーマットが採用されています。
      • 引数が3つの場合(objdump binary start end)は、指定されたアドレス範囲を逆アセンブルします。このモードはpprofでの利用を想定しており、GNU objdumpに似た形式で出力されます。
    • -s symregexpフラグが追加され、正規表現にマッチするシンボルのみを逆アセンブル対象とすることで、特定の関数に焦点を当てた分析が可能になりました。
    • text/tabwriterパッケージが導入され、逆アセンブル出力の整形が改善され、より読みやすくなっています。
  4. テストの拡充:

    • src/cmd/objdump/objdump_test.goに、新しい逆アセンブル機能のテストが追加されました。特にTestDisasm関数は、testdata/fmthello.goというシンプルなGoプログラムをビルドし、そのバイナリをobjdumpで逆アセンブルし、期待されるアセンブリ命令(JMP, CALL, RETなど)が含まれているかを確認します。これにより、x86逆アセンブラの基本的な機能が検証されています。

このコミットは、Goのツールチェインが単なるコンパイラとリンカのセットではなく、低レベルな分析を可能にする強力なデバッグツール群へと進化していく過程の一部を示しています。

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

このコミットにおける主要なコード変更は以下のファイルに集中しています。

  • src/cmd/objdump/Makefile: x86.goの生成時にgofmtを通すように変更。
  • src/cmd/objdump/elf.go (新規追加): ELF形式のシンボル解析。
  • src/cmd/objdump/macho.go (新規追加): Mach-O形式のシンボル解析。
  • src/cmd/objdump/main.go: objdumpのメインロジック。逆アセンブル機能の追加、コマンドライン引数の処理、出力フォーマットの変更、各ファイルフォーマットパーサーと逆アセンブラの統合。
  • src/cmd/objdump/objdump_test.go: 新しい逆アセンブル機能のテストケース追加。
  • src/cmd/objdump/pe.go (新規追加): PE形式のシンボル解析。
  • src/cmd/objdump/plan9obj.go (新規追加): Plan 9 a.out形式のシンボル解析。
  • src/cmd/objdump/testdata/fmthello.go (新規追加): 逆アセンブルテスト用のサンプルGoプログラム。

特にsrc/cmd/objdump/main.goが、新しい逆アセンブル機能のハブとなっています。

コアとなるコードの解説

src/cmd/objdump/main.goの変更がこのコミットの核です。

  1. main関数の変更:

    • コマンドライン引数の処理が強化され、-sフラグによるシンボルフィルタリングが追加されました。
    • loadSymbols関数(後述)を呼び出して、実行可能ファイルからシンボルとアーキテクチャ情報を取得します。
    • disasmsマップから現在のアーキテクチャに対応する逆アセンブル関数(disasmFunc型)を選択します。
    • 引数の数に応じて、バイナリ全体の逆アセンブル(dump関数)または特定範囲の逆アセンブル(gnuDump関数)を実行します。
  2. loadSymbols関数:

    • この関数は、入力ファイルの先頭バイトを読み取り、parsersスライスに定義されたマジックナンバーと照合することで、ファイルの形式(ELF, Mach-O, PE, Plan 9 a.out)を自動的に判別します。
    • 判別された形式に基づいて、対応するパーサー関数(例: elfSymbols, machoSymbolsなど)を呼び出し、ファイルからシンボル情報を抽出します。
    • 抽出されたシンボルはアドレス順にソートされ、Sym構造体のスライスとして返されます。Sym構造体は、シンボルのアドレス、サイズ、コード(タイプ)、名前を保持します。
  3. disasmFunc型とdisasmsマップ:

    • type disasmFunc func(code []byte, pc uint64, lookup lookupFunc) (text string, size int): 逆アセンブル関数のシグネチャを定義しています。これは、機械語バイト列、現在のプログラムカウンタ、アドレスからシンボルをルックアップする関数を受け取り、逆アセンブルされたテキストと命令のサイズを返します。
    • var disasms = map[string]disasmFunc{...}: 各アーキテクチャ名("386", "amd64", "arm")と、それに対応する逆アセンブル関数をマッピングしています。
      • disasm_386disasm_amd64は、内部でdisasm_x86を呼び出し、rsc.io/x86/x86asmライブラリを使用してx86命令を逆アセンブルします。
      • disasm_armは、まだ実装されていないため、ダミーの値を返します。
  4. dump関数 (Go固有の出力フォーマット):

    • バイナリ全体のテキストセクションを逆アセンブルし、Goのシンボル情報(ファイル名、行番号)と共に整形して出力します。
    • gosym.Tableを使用して、プログラムカウンタ(PC)からファイル名と行番号を取得します。
    • text/tabwriterを使用して、出力がタブ区切りで整列されるようにしています。
  5. gnuDump関数 (pprof向け出力フォーマット):

    • 指定されたアドレス範囲を逆アセンブルし、GNU objdumpに似た形式で出力します。これはpprofツールとの連携を目的としています。

これらの変更により、objdumpは単なるシンボル表示ツールから、Goバイナリの低レベルなコードを詳細に分析できる強力な逆アセンブルツールへと進化しました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメントとパッケージリファレンス
  • Go言語のIssueトラッカーとコードレビューシステム
  • ELF, Mach-O, PE, Plan 9 a.outなどの実行可能ファイルフォーマットに関する一般的な情報
  • アセンブリ言語と逆アセンブルに関する一般的な情報
  • GNU Binutils objdumpの機能に関する情報
  • rsc.io/x86/x86asmライブラリのソースコードとドキュメント
  • pprofツールに関する情報 (Goのプロファイリングツール)
  • Russ Cox氏のブログや発表資料 (Go言語の設計思想やツールの背景を理解するため)
  • Go言語のソースコード内のコメントとREADMEファイル
  • Go言語のコミュニティフォーラムやメーリングリストでの議論