[インデックス 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つの要因があります。
-
新しいオブジェクトファイル形式への対応: Go言語のコンパイラやリンカは進化しており、新しいオブジェクトファイル形式が導入されつつありました。従来の
nm
コマンドはC言語で書かれており、そのサポートライブラリであるlibmach
がこの新しい形式を理解できませんでした。libmach
に新しいコードを追加したり、liblink
を再設計してこの新しい用途をサポートするよりも、Go言語でnm
を完全に書き直す方が効率的であると判断されました。 -
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パッケージのビルド、テスト、インストールなどを担当します。
技術的詳細
このコミットにおける技術的詳細は、主に以下の点に集約されます。
-
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は不要になります。
- 従来の
-
オブジェクトファイル形式のパーシングの抽象化とGo標準ライブラリの活用:
- 新しいGo言語版
nm
は、debug/elf
,debug/macho
,debug/pe
,debug/goobj
といったGo標準ライブラリのパッケージを活用して、各オブジェクトファイル形式のシンボル情報を解析します。 src/cmd/nm/elf.go
、src/cmd/nm/macho.go
、src/cmd/nm/pe.go
、src/cmd/nm/goobj.go
といったファイルが新しく追加され、それぞれが特定のオブジェクトファイル形式(ELF、Mach-O、PE、Go独自のオブジェクトファイル)のシンボルを抽出するロジックをカプセル化しています。これにより、異なる形式のファイルを統一的に処理できる柔軟な構造が実現されました。src/cmd/nm/nm.go
内のparsers
変数には、ファイルのマジックナンバー(ファイルの先頭バイト列)に基づいて適切なパーサー関数を選択するロジックが含まれています。これにより、nm
は入力されたファイルがどの形式であるかを自動的に判別し、対応するパーサーを呼び出すことができます。
- 新しいGo言語版
-
Plan 9シンボルテーブルからの脱却:
- C言語版
nm
はGoバイナリ内のPlan 9シンボルテーブルを読み取っていましたが、Go言語版nm
は各プラットフォームの標準的なシンボルテーブル(ELF, Mach-O, PE)を直接解析するようになりました。これにより、GoバイナリからPlan 9シンボルテーブルを削除するための前提条件が整いました。これは、Goバイナリの構造を簡素化し、より標準的なツールとの互換性を高める上で重要な変更です。
- C言語版
-
ビルドシステムの変更:
src/cmd/dist/build.c
が変更され、cmd/nm
がビルド対象から除外されました。これは、nm
がC言語でビルドされるのではなく、Go言語のビルドシステムによって管理されるようになったためです。src/cmd/go/pkg.go
が変更され、cmd/nm
がGoツールチェインのツールディレクトリにインストールされるように設定されました。これにより、go tool nm
としてコマンドが利用可能になります。
-
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
のビルド設定が不要になったためです。
- ファイル全体が削除されました。これは、C言語版
-
src/cmd/nm/doc.go
:- コメントが大幅に更新され、Go言語版
nm
の新しい使用方法、オプション(-n
,-size
,-sort
,-type
)、およびシンボルタイプの意味が詳細に記述されました。
- コメントが大幅に更新され、Go言語版
-
src/cmd/nm/elf.go
(新規):- ELF形式のオブジェクトファイルからシンボルを解析するためのGoコードが追加されました。
debug/elf
パッケージを使用し、ELFセクションのフラグに基づいてシンボルタイプ(T, R, D, B, U)を決定します。
- ELF形式のオブジェクトファイルからシンボルを解析するためのGoコードが追加されました。
-
src/cmd/nm/goobj.go
(新規):- Go独自のオブジェクトファイル形式からシンボルを解析するためのGoコードが追加されました。
debug/goobj
パッケージを使用し、Goオブジェクトのシンボル種別(goobj.STEXT
,goobj.SDATA
など)に基づいてシンボルタイプを決定します。
- Go独自のオブジェクトファイル形式からシンボルを解析するためのGoコードが追加されました。
-
src/cmd/nm/macho.go
(新規):- Mach-O形式のオブジェクトファイルからシンボルを解析するためのGoコードが追加されました。
debug/macho
パッケージを使用し、Mach-Oセグメントとセクション名に基づいてシンボルタイプを決定します。シンボルサイズを計算するために、シンボルアドレスのソートも行われます。
- Mach-O形式のオブジェクトファイルからシンボルを解析するためのGoコードが追加されました。
-
src/cmd/nm/nm.c
:- ファイル全体が削除されました。これは、C言語版の
nm
実装が完全にGo言語版に置き換えられたためです。
- ファイル全体が削除されました。これは、C言語版の
-
src/cmd/nm/nm.go
(新規):- Go言語版
nm
のメインロジックが実装されました。 - コマンドライン引数のパース(
flag
パッケージを使用)。 - 入力ファイルの形式を判別し、対応するパーサー(
elfSymbols
,goobjSymbols
,machoSymbols
,peSymbols
)を呼び出すロジック。 - シンボルをアドレスまたは名前でソートする機能。
- シンボル情報を整形して標準出力に出力する機能。
Sym
構造体(Addr
,Size
,Code
,Name
,Type
フィールドを持つ)の定義。
- Go言語版
-
src/cmd/nm/pe.go
(新規):- PE形式のオブジェクトファイルからシンボルを解析するためのGoコードが追加されました。
debug/pe
パッケージを使用し、PEセクションの特性(Characteristics
)に基づいてシンボルタイプを決定します。
- PE形式のオブジェクトファイルからシンボルを解析するためのGoコードが追加されました。
これらの変更により、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.Kind
(goobj.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プロジェクトの長期的な目標に沿ったものです。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Go言語のツールに関するドキュメント: https://golang.org/cmd/go/
debug/elf
パッケージ: https://pkg.go.dev/debug/elfdebug/macho
パッケージ: https://pkg.go.dev/debug/machodebug/pe
パッケージ: https://pkg.go.dev/debug/pedebug/goobj
パッケージ: https://pkg.go.dev/debug/goobj
参考にした情報源リンク
- Go CL 40600043: https://golang.org/cl/40600043
- ELF (Executable and Linkable Format) - Wikipedia: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
- Mach-O - Wikipedia: https://en.wikipedia.org/wiki/Mach-O
- Portable Executable - Wikipedia: https://en.wikipedia.org/wiki/Portable_Executable
- Plan 9 from Bell Labs - Wikipedia: https://en.wikipedia.org/wiki/Plan_9_from_Bell_Labs
nm
(Unix) - Wikipedia: https://en.wikipedia.org/wiki/Nm_(Unix)