[インデックス 18055] ファイルの概要
このコミットは、Go言語のツールチェインに含まれる cmd/nm
コマンドに対する軽微なクリーンアップと改善を目的としています。cmd/nm
は、Unix系の nm
ユーティリティに似ており、Goのオブジェクトファイル、アーカイブ、または実行可能ファイルからシンボルをリスト表示するために使用されます。この変更は、以前のコミット (CL 40600043) で見落とされた改善点を適用し、出力の可読性を向上させ、シンボル表示のドキュメントを更新します。
コミット
commit f48120ef51bd007f0d84b42e25e1e23e75b9f244
Author: Russ Cox <rsc@golang.org>
Date: Wed Dec 18 13:29:40 2013 -0500
cmd/nm: minor cleanup from previous CL
I forgot to apply Ian's suggestions before submitting CL 40600043.
R=iant
CC=golang-dev
https://golang.org/cl/43560045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f48120ef51bd007f0d84b42e25e1e23e75b9f244
元コミット内容
このコミットは、cmd/nm
ツールに対する以下の変更を含んでいます。
src/cmd/nm/doc.go
に、読み取り専用データセグメントシンボル (R
およびr
) の説明を追加。src/cmd/nm/elf.go
のelfSymbols
関数において、ELFシンボルがローカルバインディングを持つ場合に、シンボルコードを大文字から小文字に変換するロジックを追加。src/cmd/nm/goobj.go
のgoobjSymbols
関数において、特定のシンボルタイプ (SXREF
,SMACHOSYMSTR
など) のコメントを「should not see」に変更。これは、これらのシンボルが通常はnm
の出力に現れるべきではないことを示唆。src/cmd/nm/nm.go
からfilter
関数を削除。src/cmd/nm/nm.go
に、複数のファイルが引数として与えられた場合に、各シンボル出力の前にファイル名を表示するfilePrefix
ロジックを追加。
変更の背景
このコミットは、以前に提出された変更 (CL 40600043) の「マイナーなクリーンアップ」として位置づけられています。コミットメッセージによると、以前のコミットを提出する前に、Ian (おそらくIan Lance Taylor氏) からの提案を適用し忘れたため、この追加のコミットでそれらの提案を組み込んだとのことです。
具体的な背景としては、cmd/nm
の機能改善とユーザビリティ向上が挙げられます。特に、複数のファイルを同時に処理する際の出力の分かりにくさや、シンボルタイプのドキュメントの不足が課題として認識されていた可能性があります。また、ELFシンボルの表示規則をより標準的な nm
ユーティリティの慣習に合わせる意図もあったと考えられます。
前提知識の解説
1. nm
コマンドとは
nm
は、Unix系オペレーティングシステムで広く使われているコマンドラインユーティリティです。コンパイルされたオブジェクトファイル、静的ライブラリ、または実行可能ファイル内のシンボルテーブルを検査するために使用されます。シンボルテーブルには、関数名、グローバル変数名、それらのアドレス、およびシンボルの種類(テキスト、データ、BSSなど)に関する情報が含まれています。
nm
の出力は通常、以下のような形式です。
アドレス タイプ シンボル名
ここで「タイプ」は、シンボルがコード (T
または t
)、初期化済みデータ (D
または d
)、未初期化データ (BSS, B
または b
)、未定義シンボル (U
) など、どのような種類であるかを示す一文字のコードです。大文字はグローバル(外部から参照可能)シンボルを、小文字はローカル(ファイル内でのみ参照可能)シンボルを示します。
2. Go言語のツールチェイン
Go言語は、コンパイラ、リンカ、アセンブラ、デバッガ、プロファイラなど、開発に必要な一連のツールを標準で提供しています。cmd/nm
もそのツールチェインの一部であり、Goプログラムのバイナリを分析するために使用されます。
3. ELF (Executable and Linkable Format)
ELFは、Unix系システムで広く使用されている実行可能ファイル、オブジェクトコード、共有ライブラリ、コアダンプファイルの標準ファイル形式です。Go言語でコンパイルされたバイナリも、LinuxなどのシステムではELF形式で出力されます。ELFファイルは、プログラムの実行に必要な様々なセクション(コード、データ、シンボルテーブルなど)を含んでいます。
4. シンボルバインディング (Symbol Binding)
ELFにおけるシンボルバインディングは、シンボルの可視性とリンケージの特性を定義します。
STB_GLOBAL
: グローバルシンボル。他のオブジェクトファイルから参照可能です。STB_LOCAL
: ローカルシンボル。定義されたオブジェクトファイル内でのみ参照可能です。STB_WEAK
: 弱いシンボル。グローバルシンボルに似ていますが、同名のグローバルシンボルが存在する場合、そちらが優先されます。
5. Goオブジェクトファイル形式 (goobj
)
Go言語には、独自のオブジェクトファイル形式 (goobj
) があります。これは、Goコンパイラが生成する中間ファイルであり、リンカによって最終的な実行可能ファイルに結合されます。cmd/nm
は、ELF形式だけでなく、このGo独自のオブジェクトファイル形式も解析できます。
技術的詳細
1. src/cmd/nm/doc.go
の変更
このファイルは cmd/nm
コマンドのドキュメント、特にシンボルタイプの説明を定義しています。追加された行は以下の通りです。
--- a/src/cmd/nm/doc.go
+++ b/src/cmd/nm/doc.go
@@ -13,6 +13,8 @@
//
// T text (code) segment symbol
// t static text segment symbol
+// R read-only data segment symbol
+// r static read-only data segment symbol
// D data segment symbol
// d static data segment symbol
// B bss segment symbol
これは、nm
の出力で R
および r
がそれぞれ読み取り専用データセグメントシンボル(グローバルおよびローカル/静的)を表すことを明示しています。これにより、ユーザーは nm
の出力結果をより正確に解釈できるようになります。
2. src/cmd/nm/elf.go
の変更
このファイルは、ELF形式のファイルからシンボルを抽出するロジックを扱います。変更点は elfSymbols
関数内にあります。
--- a/src/cmd/nm/elf.go
+++ b/src/cmd/nm/elf.go
@@ -47,6 +47,9 @@ func elfSymbols(f *os.File) []Sym {\
\t\t\t\tsym.Code = 'D'\
\t\t\t}\
\t\t}\
+\t\tif elf.ST_BIND(s.Info) == elf.STB_LOCAL {\
+\t\t\tsym.Code += 'a' - 'A'\
+\t\t}\
\t\tsyms = append(syms, sym)\
\t}\
\ndiff --git a/src/cmd/nm/goobj.go b/src/cmd/nm/goobj.go
elf.ST_BIND(s.Info) == elf.STB_LOCAL
は、現在のシンボル s
がローカルバインディングを持つかどうかをチェックしています。もしローカルシンボルであれば、sym.Code += 'a' - 'A'
という演算が行われます。
この演算は、シンボルコードを大文字から小文字に変換する一般的なトリックです。例えば、'D'
(データセグメントのグローバルシンボル) がローカルであれば、'D' + ('a' - 'A')
は 'd'
になります。これは、標準的な nm
ユーティリティの慣習に従い、ローカルシンボルを小文字で表示するためのものです。
3. src/cmd/nm/goobj.go
の変更
このファイルは、Go独自のオブジェクトファイル形式からシンボルを抽出するロジックを扱います。変更点は以下の通りです。
--- a/src/cmd/nm/goobj.go
+++ b/src/cmd/nm/goobj.go
@@ -42,7 +42,7 @@ func goobjSymbols(f *os.File) []Sym {\
\t\tcase goobj.SBSS, goobj.SNOPTRBSS, goobj.STLSBSS:\
\t\t\tsym.Code = 'B'\
\t\tcase goobj.SXREF, goobj.SMACHOSYMSTR, goobj.SMACHOSYMTAB, goobj.SMACHOINDIRECTPLT, goobj.SMACHOINDIRECTGOT, goobj.SFILE, goobj.SFILEPATH, goobj.SCONST, goobj.SDYNIMPORT, goobj.SHOSTOBJ:\
-\t\t\tsym.Code = 'X'\
+\t\t\tsym.Code = 'X' // should not see\
\t\t}\
\t\tif s.Version != 0 {\
\t\t\tsym.Code += 'a' - 'A'\
sym.Code = 'X'
の行に // should not see
というコメントが追加されました。これは、goobj.SXREF
(外部参照)、goobj.SMACHOSYMSTR
(Mach-Oシンボル文字列テーブル) などの特定のシンボルタイプが、nm
の通常のシンボルリストには現れるべきではない、あるいは現れるとすればそれは予期せぬ状況であることを示唆しています。これはコードの意図を明確にするためのコメントです。
また、if s.Version != 0 { sym.Code += 'a' - 'A' }
の行は、elf.go
で追加されたロジックと同様に、Goオブジェクトファイル形式においてもローカルシンボルを小文字で表示するための既存のロジックです。このコミットでは変更されていませんが、関連する部分として言及できます。
4. src/cmd/nm/nm.go
の変更
このファイルは cmd/nm
コマンドのメインロジックを含んでいます。
filter
関数の削除
--- a/src/cmd/nm/nm.go
+++ b/src/cmd/nm/nm.go
@@ -153,16 +159,6 @@ HaveSyms:\
\tw.Flush()\
}\
\
-func filter(syms []Sym, ok func(Sym) bool) []Sym {\
-\tout := syms[:0]\
-\tfor _, sym := range syms {\
-\t\tif ok(sym) {\
-\t\t\tout = append(out, sym)\
-\t\t}\
-\t}\
-\treturn out\
-}\
-\
type byAddr []Sym
\
func (x byAddr) Len() int { return len(x) }\
filter
関数は、シンボルのスライスをフィルタリングするための汎用的なヘルパー関数でしたが、このコミットで削除されました。これは、この関数がもはや使用されていないか、その機能がより直接的な方法で実現されるようになったためと考えられます。コードベースのクリーンアップの一環です。
filePrefix
ロジックの追加
--- a/src/cmd/nm/nm.go
+++ b/src/cmd/nm/nm.go
@@ -24,6 +24,8 @@ var (\
\tsortOrder = flag.String("sort", "name", "")\
\tprintSize = flag.Bool("size", false, "")\
\tprintType = flag.Bool("type", false, "")\
+\n+\tfilePrefix = false\
)\
\n func init() {\
@@ -64,6 +66,7 @@ func main() {\
\t}\
\n \targs := flag.Args()\
+\tfilePrefix = len(args) > 1\
\tif len(args) == 0 {\
\t\tflag.Usage()\
\t}\
@@ -136,6 +139,9 @@ HaveSyms:\
\n \tw := bufio.NewWriter(os.Stdout)\
\tfor _, sym := range syms {\
+\t\tif filePrefix {\
+\t\t\tfmt.Fprintf(w, "%s:\t", file)\
+\t\t}\
\t\tif sym.Code == 'U' {\
\t\t\tfmt.Fprintf(w, "%8s", "")\
\t\t} else {\
filePrefix
という新しいグローバル変数が導入され、初期値はfalse
です。main
関数内で、len(args) > 1
(つまり、nm
コマンドに複数のファイルが引数として渡された場合) にfilePrefix
がtrue
に設定されます。- シンボルを標準出力に書き出すループ内で、
filePrefix
がtrue
の場合にfmt.Fprintf(w, "%s:\t", file)
が実行されます。これにより、各シンボル情報の前に、そのシンボルが属するファイル名とタブ文字が出力されます。
この変更は、nm
を複数のファイルに対して実行した際の出力の可読性を大幅に向上させます。例えば、nm a.o b.o
のように実行した場合、どのシンボルが a.o
に属し、どのシンボルが b.o
に属するのかが一目でわかるようになります。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、以下の3つのファイルに分散しています。
src/cmd/nm/doc.go
: シンボルタイプR
とr
の説明追加。src/cmd/nm/elf.go
: ELFシンボルがローカルである場合に、シンボルコードを小文字に変換するロジックの追加。src/cmd/nm/nm.go
:filter
関数の削除。- 複数のファイルが指定された場合に、出力にファイル名をプレフィックスとして追加するロジックの実装。
コアとなるコードの解説
シンボルコードの小文字化 (src/cmd/nm/elf.go
)
if elf.ST_BIND(s.Info) == elf.STB_LOCAL {
sym.Code += 'a' - 'A'
}
このコードスニペットは、ELFシンボルの Info
フィールドからバインディング情報を抽出し、それが STB_LOCAL
(ローカルシンボル) であるかどうかをチェックします。もしローカルシンボルであれば、sym.Code
(シンボルタイプを示す文字) に 'a' - 'A'
を加算します。ASCII文字コードにおいて、小文字の 'a'
は大文字の 'A'
よりも大きな値を持つため、この演算は大文字を対応する小文字に変換します。例えば、'D'
(大文字のD) にこの値を加えると 'd'
(小文字のd) になります。これにより、nm
の出力が標準的な慣習に沿うようになります。
ファイル名プレフィックスの追加 (src/cmd/nm/nm.go
)
filePrefix = len(args) > 1
// ...
for _, sym := range syms {
if filePrefix {
fmt.Fprintf(w, "%s:\t", file)
}
// ... シンボル情報の出力 ...
}
filePrefix
変数は、コマンドライン引数の数 (len(args)
) が1より大きい場合に true
に設定されます。これは、nm
が複数の入力ファイルに対して実行されていることを意味します。シンボルをループ処理して出力する際に、filePrefix
が true
であれば、fmt.Fprintf(w, "%s:\t", file)
が実行され、現在のファイル名 (file
) とタブ文字がシンボル情報の前に書き込まれます。これにより、nm
の出力がどのファイルからのシンボルであるかを明確に示し、複数のファイルを分析する際の可読性を向上させます。
関連リンク
- Go言語の
cmd/nm
のソースコード: https://github.com/golang/go/tree/master/src/cmd/nm - Unix
nm
コマンドのマニュアルページ (例): https://man7.org/linux/man-pages/man1/nm.1.html - ELF (Executable and Linkable Format) の概要: https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
参考にした情報源リンク
- Go言語の公式ドキュメント
- ELFファイル形式に関する一般的な情報源
- Unix
nm
コマンドに関する情報源 - コミットメッセージに記載されているCL (Change List) へのリンク: https://golang.org/cl/43560045 (これはGoのコードレビューシステムGerritへのリンクであり、詳細な議論や関連する変更履歴を確認できます。)
- Goのソースコード内のコメントと構造