プログラムをコンパイルして作成されたオブジェクトファイルや実行ファイル、ライブラリファイルの中に、「どんな関数や変数が含まれているのか」「どの関数が外部から参照されているのか」を確認したい場面があります。
そのようなときに便利なのが nm コマンドです。本記事では、nmコマンドの基本的な使い方から、出力されるシンボルタイプの見方、C++プログラムのデマングル、そしてトラブルシューティングでの実践的な活用例までをわかりやすく解説します。
nmコマンドとは?
nm コマンドは、オブジェクトファイル(.o)、実行可能ファイル、静的ライブラリ(.a)、共有ライブラリ(.so)などに含まれる「シンボル」のリストを表示するコマンドです。
シンボルとは、プログラム内の関数や変数などを識別するための名前や情報です。コンパイラやリンカがどのようにファイルを処理したかを確認できるため、開発中のデバッグや、「未定義の参照(undefined reference)」エラーを解決するための調査に欠かせないツールです。
nmコマンドの基本的な使い方
ターミナルで nm コマンドの後に調べたいファイルパスを指定して実行します。
nm [オプション] [ファイル名]シンボル情報を表示する
例えば、コンパイル済みの実行ファイル hello のシンボルを確認するには、以下のように実行します。
nm hello00000000004005b6 T main U printf@@GLIBC_2.2.50000000000601038 B uninitialized_var0000000000601030 D initialized_var※補足(バージョン付きシンボルの表示について)
printf@@GLIBC_2.2.5のようにシンボルにバージョン情報が付与されて表示されるかどうかは、システムにインストールされているbinutilsのバージョンやビルド設定(--with-symbol-versionsが有効かなど)に依存します。古い環境や無効化されている環境では、単にprintfと表示されることもあります。
出力は左から順に以下の3つの情報で構成されます。
- シンボルの値: シンボルに関連付けられた値。多くの場合はアドレスとして表示されますが、シンボルの種類によって意味が異なります。未定義シンボルの場合は通常空白になります。
- シンボルのクラス(タイプ): シンボルの種類や状態を表す1文字のアルファベット(大文字はグローバル、小文字はローカル)。
- シンボル名: 関数名や変数名。
シンボルクラス(タイプ)の見方
出力される中央の1文字のアルファベットは、そのシンボルがどのような性質のものかを表しています。特によく見る重要なものをいくつか紹介します。
| 文字 | 意味(大文字はグローバル、小文字はローカル) |
|---|---|
| T / t | Text(テキスト)セクション Text(コード)セクションにあるシンボルを示します。通常、関数などの実行コードが該当します。大文字の T はグローバル(external)シンボル、小文字の t は通常ローカルシンボルを表します。 |
| U | Undefined(未定義) このファイル内では定義されておらず、外部のライブラリや他のオブジェクトファイルからリンクされる予定の関数や変数です(例: printfなど)。 |
| D / d | Data(データ)セクション 初期値が設定されているグローバル変数や静的変数を示します。 |
| B / b | BSSセクション ゼロ初期化または未初期化のデータを格納するBSSセクションにあるシンボルを示します。 |
| R / r | Read-only(読み取り専用)セクション Read-only data(読み取り専用データ)セクションにあるシンボルを示します。文字列リテラルや読み取り専用データなどが該当することがあります。 |
※Tips(大文字と小文字の違い)
同じ文字でも、基本的には大文字が外部に公開されている(グローバルな)シンボル、小文字がファイル内でのみ有効な(ローカルな)シンボル(C言語のstatic関数など)を表します。
ただし、u(unique global symbol)やv(weak object)、w(weak symbol)など、小文字であっても例外的にグローバルシンボルを表す特殊なケースも存在します。
よく使う便利なオプション
nm コマンドには、出力を絞り込んだり見やすくしたりするための強力なオプションが用意されています。
-g(—extern-only):グローバルシンボルのみ表示
外部シンボル(external symbols)のみを表示します。ライブラリなどが外部に提供しているシンボルを確認するときに便利です。
nm -g libexample.so-u(—undefined-only):未定義シンボルのみ表示
そのファイル内で参照されているが、定義が存在しないシンボル(U)のみをリストアップします。「このライブラリを使うには、他にどんな外部関数が必要か」を調べる際に役立ちます。
nm -u program.o-C(—demangle):C++のシンボル名を人間に読みやすくする
C++で書かれたプログラムをコンパイルすると、オーバーロードや名前空間の情報を保持するために、関数名が「マングリング(名前修飾)」され、複雑な文字列(例:_Z4funcid など)に変換されます。
-C オプションを付けると、これを元の読みやすいC++の関数名(デマングル)に復元して表示してくれます。
nm program | grep func# 出力例: 0000000000401122 T _Z4funcidnm -C program | grep func# 出力例: 0000000000401122 T func(int, double)-A(—print-file-name):ファイル名を行の先頭に付与する
複数のファイルを対象に nm を実行する際、どのシンボルがどのファイルに含まれているのか分かりにくくなります。-A オプション(-o、--print-file-name も同じ意味)を付けると、各シンボルの行の先頭にファイル名が付与されるため、grepコマンドで検索する際にどこでヒットしたか一目で分かります。
nm -A *.o | grep "main"トラブルシューティングでの実践的な活用例
実務において、nmコマンドは主にビルドエラーの調査やライブラリの依存関係チェックで活躍します。
「未定義の参照(undefined reference)」エラーの調査
C/C++プログラムのリンク時に undefined reference to 'hoge_function' というエラーが出た場合、指定したライブラリの中に本当に hoge_function が含まれているか(実装されているか)を nm で確認します。
nm -g libtest.a | grep hoge_functionもし何も出力されなければ、そのライブラリには関数が含まれていないため、リンクするライブラリが間違っているか、ソースコードが正しくコンパイルされていません。出力されても U(未定義)となっていれば、そのシンボルを別のオブジェクトファイルやライブラリなどから解決する必要があります。
特定の関数がどのライブラリに含まれているかを探す
システム上に複数のライブラリ(.so や .a)が存在し、目的の関数(例:SSL_connect)がどのファイルで提供されているか分からない場合、ディレクトリ内のすべてのライブラリに対して検索をかけます。
nm -D -A /usr/lib/*.so 2>/dev/null | grep " T SSL_connect"※これにより、SSL_connect というシンボルを定義している共有ライブラリを検索できます。T はText(コード)セクションにあるシンボルを示します。-A オプションでファイル名を行頭に表示し、2>/dev/null で標準エラー出力を破棄しています。
まとめ
nm コマンドのポイントは以下の通りです。
- オブジェクトファイルやライブラリから、関数や変数などの「シンボル」情報を抽出して表示する。
- シンボルのタイプを確認することで、そのシンボルが定義されているか(例:
T)、**未定義で外部から解決する必要があるか(U)**を確認できる。 - C++プログラムを解析する場合は、
-Cオプションでシンボル名を読みやすく(デマングル)できる。 - リンク時のエラー調査や、特定の関数を提供するライブラリを探す際に非常に重宝する。
コンパイラが裏側で何を行っているのかを覗き見ることができる強力なコマンドです。C/C++の開発だけでなく、他の言語で作成されたバイナリやネイティブ拡張の挙動を調査する際にもぜひ活用してみてください。
以上で本記事の解説を終わります。
よいITライフを!
人気記事
- 1
- 2
- 3
- 4
- 5
Rocky Linux対応。実際にWebサーバーを公開するまでのプロセスを体験できる、最も確実で実践的なガイドブックです。