在 Linux 開發(fā)與調試中,nm
命令是分析二進制文件符號表的必備工具徊哑。無論是排查未定義符號錯誤袜刷,還是逆向分析程序結構,掌握 nm
的使用都能讓你事半功倍莺丑。本文將結合真實案例著蟹,手把手教你玩轉 nm
命令。
一梢莽、nm
命令是什么萧豆?
nm
(Names 的縮寫)是 GNU Binutils 工具集的一員,用于顯示目標文件(如.o蟹漓、.a炕横、.so)中的符號信息。它能快速告訴你:
- 哪些函數(shù)已定義葡粒?
- 哪些變量在全局數(shù)據(jù)段份殿?
- 是否存在未鏈接的符號?
其基本語法非常簡單:nm [選項] [文件]
嗽交。這里的文件就是我們想要查看符號表的目標文件卿嘲,可以是可執(zhí)行文件、目標文件(.o 文件)或者共享庫文件(.so 文件)夫壁。而選項則用于對輸出進行各種定制拾枣,以滿足不同的需求。
典型應用場景
- 排查編譯/鏈接時的符號缺失問題。
- 分析動態(tài)庫的導出和依賴關系梅肤。
- 逆向工程中查看二進制文件的函數(shù)列表司蔬。
二、核心用法速查表
1. 基礎命令格式
nm [選項] 文件名
常用選項
-
-C
:解析 C++ 符號(Demangle) -
-D
:顯示動態(tài)符號(適用于動態(tài)庫) -
-u
:僅顯示未定義符號(排查鏈接問題) -
-l
:顯示符號所在行號(需編譯時加-g) -
--defined-only
:僅列出已定義符號
2. 符號類型解析
nm
輸出的第二列字母代表符號類型姨蝴,常見類型如下:
符號 | 含義 | 示例場景 |
---|---|---|
T/t | 代碼段(函數(shù)) | main俊啼、print 函數(shù) |
D/d | 初始化數(shù)據(jù)段 | int g_var=1; |
B/b | 未初始化數(shù)據(jù)段(BSS) | static int a; |
U | 未定義符號(需外部鏈接) | 外部函數(shù)調用 |
W/w | 弱符號(可被覆蓋) | attribute((weak)) 修飾的變量/函數(shù) |
R/r | 只讀數(shù)據(jù)段 | const char* str="hello"; |
注意:大寫表示全局符號,小寫多為局部符號(部分例外如 u 表示未定義)左医。
查看符號表全貌
如果我們想要快速查看一個可執(zhí)行文件 a.out
的符號表信息授帕,直接在終端輸入 nm a.out
即可。命令執(zhí)行后浮梢,會輸出一系列的行跛十,每一行代表一個符號,包含了符號值(地址)秕硝、符號類型和符號名稱芥映。
例如:
0000000000300526 T main
0000000000401030 D global_variable
0000000000401040 B bss_variable
U printf
在這個輸出中,0000000000300526
是 main
函數(shù)的地址缝裤,T
表示 main
函數(shù)位于代碼段屏轰,main
就是函數(shù)名颊郎。global_variable
是一個已初始化的全局變量憋飞,位于數(shù)據(jù)段(D 類型),bss_variable
是未初始化的全局變量(B 類型)姆吭,而 printf
是一個未定義的符號(U 類型)榛做,說明程序中使用了 printf
函數(shù),但它的定義在其他地方内狸。
三检眯、實戰(zhàn)案例:如何排查"undefined reference"?
問題場景
編譯鏈接動態(tài)庫時,報錯 undefined reference to 'clickhouse::ColumnArray()'
昆淡,但代碼中明明有定義锰瘸。
排查步驟
-
確認符號是否存在:
nm -CD libXXX.so | grep clickhouse::ColumnArray
若輸出
U clickhouse::ColumnArray
,說明該符號在庫中未定義昂灵。 -
檢查符號可見性:
- C++ 函數(shù)需用
extern "C"
導出避凝,避免名稱修飾(Name Mangling)。 - 使用
__attribute__((visibility("default")))
確保符號可見眨补。
- C++ 函數(shù)需用
-
驗證依賴庫:
ldd libXXX.so # 查看依賴庫 nm -D libYYY.so | grep clickhouse::ColumnArray # 檢查依賴庫是否包含符號
-
編譯選項檢查:
- 確保編譯時未使用
-fvisibility=hidden
隱藏符號管削。 - 動態(tài)庫鏈接時需添加
-lXXX
指定依賴庫。
- 確保編譯時未使用
檢查某個目標文件中是否有該符號定義
nm test.o | grep 'function_name'
檢查某個庫文件中是否有該符號定義
nm -D test.so | grep 'function_name'
四撑螺、高級技巧:符號分析進階
1. 分析內存分布
通過符號地址判斷變量/函數(shù)位置:
nm -n a.out # 按地址排序
2. 逆向調試輔助
結合 objdump
反匯編:
nm a.out | grep suspicious_func # 獲取函數(shù)地址
objdump -d a.out | grep <地址> # 查看匯編代碼
3. 靜態(tài)庫分析
查看 .a
文件中的目標文件:
nm -A libtest.a # 顯示每個.o 文件的符號
五含思、常見問題 QA
Q1:nm
無法查看源碼文件(.c/.cpp)?
A:nm
僅支持二進制文件(.o/.a/.so/可執(zhí)行文件)。
Q2:如何查看 C++ 類的成員函數(shù)含潘?
A:使用 -C
選項解析修飾名饲做,例如:
nm -C lib.so | grep MyClass::method
Q3:符號顯示為 V 類型是什么?
A:V 表示弱符號(Weak Symbol)遏弱,常見于虛函數(shù)表(vtable)艇炎。
結語:掌握 nm
命令,相當于擁有了透視二進制文件的“X 光眼”腾窝。無論是日常開發(fā)中的鏈接問題缀踪,還是復雜的內存分析,它都能提供關鍵線索虹脯。下次遇到符號問題時驴娃,不妨先敲一句 nm -CD
,或許答案就在其中循集。
擴展工具推薦:
-
objdump
:反匯編與段信息分析 -
readelf
:查看 ELF 文件頭信息 -
ldd
:動態(tài)庫依賴檢查
參考文檔: