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

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

このコミットは、Go言語のツールチェインに含まれるnmコマンドをC言語からGo言語に再実装するものです。nmは、オブジェクトファイル、アーカイブ、または実行可能ファイルによって定義または使用されるシンボルをリスト表示するためのユーティリティです。この再実装により、Goバイナリが使用するシンボルテーブルの形式が、従来のPlan 9形式から各プラットフォームの標準的な形式(ELF, Mach-O, PEなど)に移行し、将来的にPlan 9シンボルテーブルの利用を廃止するための重要なステップとなります。

コミット

commit 500547f28baf67923d77fb4d88c3744b335d4ad0
Author: Russ Cox <rsc@golang.org>
Date:   Mon Dec 16 12:52:11 2013 -0500

    cmd/nm: reimplement in Go
    
    The immediate goal is to support the new object file format,
    which libmach (nm's support library) does not understand.
    Rather than add code to libmach or reengineer liblink to
    support this new use, just write it in Go.
    
    The C version of nm reads the Plan 9 symbol table stored in
    Go binaries, now otherwise unused.
    
    This reimplementation uses the standard symbol table for
    the corresponding file format instead, bringing us one step
    closer to removing the Plan 9 symbol table from Go binaries.
    
    Tell cmd/dist not to build cmd/nm anymore.
    Tell cmd/go to install cmd/nm in the tool directory.
    
    R=golang-dev, r, iant, alex.brainman
    CC=golang-dev
    https://golang.org/cl/40600043

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

https://github.com/golang/go/commit/500547f28baf67923d77fb4d88c3744b335d4ad0

元コミット内容

cmd/nm: reimplement in Go

このコミットの主な目的は、nmコマンドをC言語からGo言語で再実装することです。これにより、新しいオブジェクトファイル形式への対応、Plan 9シンボルテーブルからの脱却、そしてGoツールチェインの自己完結性の向上が図られます。

変更の背景

この変更の背景には、主に以下の2つの要因があります。

  1. 新しいオブジェクトファイル形式への対応: Go言語のコンパイラやリンカは進化しており、新しいオブジェクトファイル形式が導入されつつありました。従来のnmコマンドはC言語で書かれており、そのサポートライブラリであるlibmachがこの新しい形式を理解できませんでした。libmachに新しいコードを追加したり、liblinkを再設計してこの新しい用途をサポートするよりも、Go言語でnmを完全に書き直す方が効率的であると判断されました。

  2. Plan 9シンボルテーブルからの脱却: 従来のGoバイナリは、Plan 9オペレーティングシステムに由来する独自のシンボルテーブル形式を使用していました。C言語版のnmはこのPlan 9シンボルテーブルを読み取っていましたが、この形式はGoバイナリ内で他に利用されておらず、冗長になっていました。この再実装により、nmは各プラットフォームの標準的なシンボルテーブル(ELF, Mach-O, PEなど)を使用するようになり、GoバイナリからPlan 9シンボルテーブルを最終的に削除するための道筋がつけられました。これは、Goバイナリの構造を簡素化し、より標準的なツールとの互換性を高めることを目的としています。

これらの背景から、Goツールチェインの将来的な発展と保守性を考慮し、nmコマンドのGo言語での再実装が決定されました。

前提知識の解説

このコミットを理解するためには、以下の前提知識が必要です。

  • nmコマンド: nmは"name mangler"または"name list"の略で、Unix系システムで広く使われているユーティリティです。コンパイルされたオブジェクトファイル、共有ライブラリ、または実行可能ファイル内のシンボルテーブル(関数名、変数名など)を検査するために使用されます。シンボルのアドレス、タイプ、名前などを表示し、デバッグやプログラムの構造解析に役立ちます。

  • オブジェクトファイル形式: プログラムのソースコードがコンパイルされると、オブジェクトファイルが生成されます。これには、機械語コード、データ、そしてシンボルテーブルが含まれます。オペレーティングシステムやアーキテクチャによって異なるオブジェクトファイル形式が存在します。

    • ELF (Executable and Linkable Format): Linux、FreeBSDなどのUnix系システムで広く使用されている標準的なオブジェクトファイル形式です。
    • Mach-O (Mach Object): macOS (OS X) で使用されているオブジェクトファイル形式です。
    • PE (Portable Executable): Microsoft Windowsで使用されているオブジェクトファイル形式です。
    • Goオブジェクトファイル形式: Go言語独自の内部的なオブジェクトファイル形式も存在し、コンパイラとリンカの間で中間表現として使用されます。
  • シンボルテーブル: オブジェクトファイルや実行可能ファイル内に含まれるデータ構造で、プログラム内のシンボル(関数名、変数名、ラベルなど)とそのアドレス、サイズ、タイプなどの情報が格納されています。リンカはシンボルテーブルを使用して、異なるオブジェクトファイル間の参照を解決します。

  • Plan 9シンボルテーブル: Plan 9はベル研究所で開発された分散オペレーティングシステムです。Go言語は、その設計思想や一部のツール(例: go toolコマンドのルーツ)にPlan 9の影響を受けています。初期のGoバイナリは、Plan 9由来の独自のシンボルテーブル形式を内部的に持っていました。しかし、この形式はGoバイナリの他の部分では使用されなくなり、標準的なオブジェクトファイル形式のシンボルテーブルが主要な情報源となっていました。

  • libmach: 従来のC言語版nmコマンドが使用していたサポートライブラリです。オブジェクトファイルの解析やシンボルテーブルの読み取りを担当していました。

  • liblink: Go言語のリンカのC言語部分を指す可能性があります。リンカは、複数のオブジェクトファイルを結合して実行可能ファイルを生成するツールです。

  • cmd/dist: Goのビルドシステムの一部で、Goツールチェイン自体のビルドプロセスを管理します。どのツールをビルドするか、どのようにビルドするかなどを制御します。

  • cmd/go: Goコマンドラインツール(go build, go run, go installなど)の実装です。Goパッケージのビルド、テスト、インストールなどを担当します。

技術的詳細

このコミットにおける技術的詳細は、主に以下の点に集約されます。

  1. C言語からGo言語への移行:

    • 従来のsrc/cmd/nm/nm.cファイルが削除され、代わりにsrc/cmd/nm/nm.goが追加されました。これは、nmコマンドのメインロジックがCからGoに完全に書き換えられたことを意味します。
    • C言語版nmのビルド設定が含まれていたsrc/cmd/nm/Makefileが削除されました。Go言語のビルドシステムはGoモジュールとgo buildコマンドによって管理されるため、個別のMakefileは不要になります。
  2. オブジェクトファイル形式のパーシングの抽象化とGo標準ライブラリの活用:

    • 新しいGo言語版nmは、debug/elf, debug/macho, debug/pe, debug/goobjといったGo標準ライブラリのパッケージを活用して、各オブジェクトファイル形式のシンボル情報を解析します。
    • src/cmd/nm/elf.gosrc/cmd/nm/macho.gosrc/cmd/nm/pe.gosrc/cmd/nm/goobj.goといったファイルが新しく追加され、それぞれが特定のオブジェクトファイル形式(ELF、Mach-O、PE、Go独自のオブジェクトファイル)のシンボルを抽出するロジックをカプセル化しています。これにより、異なる形式のファイルを統一的に処理できる柔軟な構造が実現されました。
    • src/cmd/nm/nm.go内のparsers変数には、ファイルのマジックナンバー(ファイルの先頭バイト列)に基づいて適切なパーサー関数を選択するロジックが含まれています。これにより、nmは入力されたファイルがどの形式であるかを自動的に判別し、対応するパーサーを呼び出すことができます。
  3. Plan 9シンボルテーブルからの脱却:

    • C言語版nmはGoバイナリ内のPlan 9シンボルテーブルを読み取っていましたが、Go言語版nmは各プラットフォームの標準的なシンボルテーブル(ELF, Mach-O, PE)を直接解析するようになりました。これにより、GoバイナリからPlan 9シンボルテーブルを削除するための前提条件が整いました。これは、Goバイナリの構造を簡素化し、より標準的なツールとの互換性を高める上で重要な変更です。
  4. ビルドシステムの変更:

    • src/cmd/dist/build.cが変更され、cmd/nmがビルド対象から除外されました。これは、nmがC言語でビルドされるのではなく、Go言語のビルドシステムによって管理されるようになったためです。
    • src/cmd/go/pkg.goが変更され、cmd/nmがGoツールチェインのツールディレクトリにインストールされるように設定されました。これにより、go tool nmとしてコマンドが利用可能になります。
  5. nmコマンドの機能拡張と改善:

    • src/cmd/nm/doc.goが更新され、新しいGo言語版nmの利用方法とオプションが詳細に記述されました。特に、-size(シンボルサイズ表示)、-sort {address,name,none}(ソート順指定)、-type(シンボルタイプ表示)などの新しいオプションが追加され、機能が強化されています。
    • シンボルの種類を示す文字(T, t, D, d, B, b, Uなど)の定義が明確化され、より標準的なnmコマンドの出力に近づけられました。

これらの変更により、nmコマンドはより堅牢で、将来のGo言語の進化に対応しやすくなり、Goツールチェイン全体の整合性が向上しました。

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

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

  • src/cmd/dist/build.c:

    • static char *buildorder[]static char *cleantab[]から"cmd/nm"のエントリが削除されました。これは、cmd/distがC言語版のnmをビルドしなくなったことを示します。
  • src/cmd/go/pkg.go:

    • var goTools = map[string]targetDir"cmd/nm": toTool,が追加されました。これにより、go installコマンドがcmd/nmをツールディレクトリにインストールするようになります。
  • src/cmd/nm/Makefile:

    • ファイル全体が削除されました。これは、C言語版nmのビルド設定が不要になったためです。
  • src/cmd/nm/doc.go:

    • コメントが大幅に更新され、Go言語版nmの新しい使用方法、オプション(-n, -size, -sort, -type)、およびシンボルタイプの意味が詳細に記述されました。
  • src/cmd/nm/elf.go (新規):

    • ELF形式のオブジェクトファイルからシンボルを解析するためのGoコードが追加されました。debug/elfパッケージを使用し、ELFセクションのフラグに基づいてシンボルタイプ(T, R, D, B, U)を決定します。
  • src/cmd/nm/goobj.go (新規):

    • Go独自のオブジェクトファイル形式からシンボルを解析するためのGoコードが追加されました。debug/goobjパッケージを使用し、Goオブジェクトのシンボル種別(goobj.STEXT, goobj.SDATAなど)に基づいてシンボルタイプを決定します。
  • src/cmd/nm/macho.go (新規):

    • Mach-O形式のオブジェクトファイルからシンボルを解析するためのGoコードが追加されました。debug/machoパッケージを使用し、Mach-Oセグメントとセクション名に基づいてシンボルタイプを決定します。シンボルサイズを計算するために、シンボルアドレスのソートも行われます。
  • src/cmd/nm/nm.c:

    • ファイル全体が削除されました。これは、C言語版のnm実装が完全にGo言語版に置き換えられたためです。
  • src/cmd/nm/nm.go (新規):

    • Go言語版nmのメインロジックが実装されました。
    • コマンドライン引数のパース(flagパッケージを使用)。
    • 入力ファイルの形式を判別し、対応するパーサー(elfSymbols, goobjSymbols, machoSymbols, peSymbols)を呼び出すロジック。
    • シンボルをアドレスまたは名前でソートする機能。
    • シンボル情報を整形して標準出力に出力する機能。
    • Sym構造体(Addr, Size, Code, Name, Typeフィールドを持つ)の定義。
  • src/cmd/nm/pe.go (新規):

    • PE形式のオブジェクトファイルからシンボルを解析するためのGoコードが追加されました。debug/peパッケージを使用し、PEセクションの特性(Characteristics)に基づいてシンボルタイプを決定します。

これらの変更により、nmコマンドはC言語からGo言語への完全な移行を果たし、様々なオブジェクトファイル形式に対応できるようになりました。

コアとなるコードの解説

このコミットの核となるのは、src/cmd/nm/nm.goと、各オブジェクトファイル形式に対応するパーサーファイル(elf.go, goobj.go, macho.go, pe.go)です。

src/cmd/nm/nm.go: このファイルは、新しいGo言語版nmコマンドのエントリポイントであり、全体のフローを制御します。

  • main関数:
    • コマンドライン引数をパースし、ソート順(-sort)、サイズ表示(-size)、タイプ表示(-type)などのオプションを処理します。
    • 入力されたファイルごとにnm(file string)関数を呼び出します。
  • nm(file string)関数:
    • 指定されたファイルをオープンし、ファイルの先頭16バイトを読み取ってマジックナンバーをチェックします。
    • parsersスライスをイテレートし、マジックナンバーに基づいて適切なパーサー関数(例: elfSymbols, machoSymbols)を特定し、呼び出します。
    • パーサーから返されたシンボルリストを、指定されたソート順(アドレスまたは名前)でソートします。
    • 整形されたシンボル情報を標準出力に書き出します。シンボルのアドレス、サイズ(オプション)、タイプ文字、名前、そしてGoオブジェクトファイルの場合は追加のタイプ情報(オプション)が表示されます。
  • Sym構造体:
    • Addr (uint64): シンボルのアドレス。
    • Size (int64): シンボルのサイズ。
    • Code (rune): シンボルのタイプを示す文字(例: 'T' for text, 'D' for data, 'U' for undefined)。
    • Name (string): シンボルの名前。
    • Type (string): Goオブジェクトファイルの場合の追加のタイプ情報。
  • parsers変数:
    • 各オブジェクトファイル形式のマジックナンバーと、それに対応するパーサー関数をマッピングしたスライスです。これにより、nmは入力ファイルの形式を自動的に識別できます。

各パーサーファイル (elf.go, goobj.go, macho.go, pe.go): これらのファイルは、それぞれのオブジェクトファイル形式からシンボル情報を抽出する具体的なロジックを実装しています。

  • elfSymbols(f *os.File) []Sym (src/cmd/nm/elf.go):

    • debug/elf.NewFile(f)を使用してELFファイルを解析します。
    • p.Symbols()を呼び出してELFシンボルテーブルを取得します。
    • 各ELFシンボルをnm.goで定義されたSym構造体に変換します。
    • ELFセクションのタイプとフラグ(elf.SHF_WRITE, elf.SHF_ALLOC, elf.SHF_EXECINSTRなど)に基づいて、シンボルのCode(タイプ文字)を決定します。例えば、実行可能なセクションにあるシンボルは'T'(テキスト)に、書き込み可能なデータセクションにあるシンボルは'D'(データ)になります。
  • goobjSymbols(f *os.File) []Sym (src/cmd/nm/goobj.go):

    • debug/goobj.Parse(f, "")を使用してGoオブジェクトファイルを解析します。
    • pkg.Symsからシンボル情報を取得します。
    • 各GoオブジェクトシンボルをSym構造体に変換し、s.Kindgoobj.STEXT, goobj.SDATA, goobj.SBSSなど)に基づいてCodeを決定します。
    • 参照されているが定義されていないシンボル(SXREF)は'U'(未定義)として扱われます。
  • machoSymbols(f *os.File) []Sym (src/cmd/nm/macho.go):

    • debug/macho.NewFile(f)を使用してMach-Oファイルを解析します。
    • p.Symtab.SymsからMach-Oシンボルテーブルを取得します。
    • 各Mach-OシンボルをSym構造体に変換します。
    • シンボルのSect(セクション番号)とセクションのSeg(セグメント名)およびNameに基づいてCodeを決定します。例えば、__TEXT __textセクションにあるシンボルは'T'(テキスト)に、__DATA __bssセクションにあるシンボルは'B'(BSS)になります。
    • シンボルサイズは、次のシンボルのアドレスとの差から推測されます。
  • peSymbols(f *os.File) []Sym (src/cmd/nm/pe.go):

    • debug/pe.NewFile(f)を使用してPEファイルを解析します。
    • p.SymbolsからPEシンボルテーブルを取得します。
    • 各PEシンボルをSym構造体に変換します。
    • シンボルのSectionNumberとセクションのCharacteristics(特性フラグ)に基づいてCodeを決定します。例えば、実行可能コードを含むセクションは'T'(テキスト)に、書き込み可能なデータセクションは'D'(データ)になります。

これらのファイルが連携することで、nmコマンドは様々なプラットフォームのバイナリファイルからシンボル情報を正確に抽出し、表示することが可能になりました。これは、Goツールチェインがより自己完結的になり、外部のCライブラリへの依存を減らすというGoプロジェクトの長期的な目標に沿ったものです。

関連リンク

参考にした情報源リンク