在 Linux 开发与调试中,nm 命令是分析二进制文件符号表的必备工具。无论是排查未定义符号错误,还是逆向分析程序结构,掌握 nm 的使用都能让你事半功倍。本文将结合真实案例,手把手教你玩转 nm 命令。
一、nm 命令是什么?
nm(Names 的缩写)是 GNU Binutils 工具集的一员,用于显示目标文件(如.o、.a、.so)中的符号信息。它能快速告诉你:
- 哪些函数已定义?
- 哪些变量在全局数据段?
- 是否存在未链接的符号?
其基本语法非常简单:nm [选项] [文件]。这里的文件就是我们想要查看符号表的目标文件,可以是可执行文件、目标文件(.o 文件)或者共享库文件(.so 文件)。而选项则用于对输出进行各种定制,以满足不同的需求。
典型应用场景
- 排查编译/链接时的符号缺失问题。
- 分析动态库的导出和依赖关系。
- 逆向工程中查看二进制文件的函数列表。
二、核心用法速查表
1. 基础命令格式
nm [选项] 文件名
常用选项
-
-C:解析 C++ 符号(Demangle) -
-D:显示动态符号(适用于动态库) -
-u:仅显示未定义符号(排查链接问题) -
-l:显示符号所在行号(需编译时加-g) -
--defined-only:仅列出已定义符号
2. 符号类型解析
nm 输出的第二列字母代表符号类型,常见类型如下:
| 符号 | 含义 | 示例场景 |
|---|---|---|
| T/t | 代码段(函数) | main、print 函数 |
| D/d | 初始化数据段 | int g_var=1; |
| B/b | 未初始化数据段(BSS) | static int a; |
| U | 未定义符号(需外部链接) | 外部函数调用 |
| W/w | 弱符号(可被覆盖) | attribute((weak)) 修饰的变量/函数 |
| R/r | 只读数据段 | const char* str="hello"; |
注意:大写表示全局符号,小写多为局部符号(部分例外如 u 表示未定义)。
查看符号表全貌
如果我们想要快速查看一个可执行文件 a.out 的符号表信息,直接在终端输入 nm a.out 即可。命令执行后,会输出一系列的行,每一行代表一个符号,包含了符号值(地址)、符号类型和符号名称。
例如:
0000000000300526 T main
0000000000401030 D global_variable
0000000000401040 B bss_variable
U printf
在这个输出中,0000000000300526 是 main 函数的地址,T 表示 main 函数位于代码段,main 就是函数名。global_variable 是一个已初始化的全局变量,位于数据段(D 类型),bss_variable 是未初始化的全局变量(B 类型),而 printf 是一个未定义的符号(U 类型),说明程序中使用了 printf 函数,但它的定义在其他地方。
三、实战案例:如何排查"undefined reference"?
问题场景
编译链接动态库时,报错 undefined reference to 'clickhouse::ColumnArray()',但代码中明明有定义。
排查步骤
-
确认符号是否存在:
nm -CD libXXX.so | grep clickhouse::ColumnArray若输出
U clickhouse::ColumnArray,说明该符号在库中未定义。 -
检查符号可见性:
- C++ 函数需用
extern "C"导出,避免名称修饰(Name Mangling)。 - 使用
__attribute__((visibility("default")))确保符号可见。
- C++ 函数需用
-
验证依赖库:
ldd libXXX.so # 查看依赖库 nm -D libYYY.so | grep clickhouse::ColumnArray # 检查依赖库是否包含符号 -
编译选项检查:
- 确保编译时未使用
-fvisibility=hidden隐藏符号。 - 动态库链接时需添加
-lXXX指定依赖库。
- 确保编译时未使用
检查某个目标文件中是否有该符号定义
nm test.o | grep 'function_name'
检查某个库文件中是否有该符号定义
nm -D test.so | grep 'function_name'
四、高级技巧:符号分析进阶
1. 分析内存分布
通过符号地址判断变量/函数位置:
nm -n a.out # 按地址排序
2. 逆向调试辅助
结合 objdump 反汇编:
nm a.out | grep suspicious_func # 获取函数地址
objdump -d a.out | grep <地址> # 查看汇编代码
3. 静态库分析
查看 .a 文件中的目标文件:
nm -A libtest.a # 显示每个.o 文件的符号
五、常见问题 QA
Q1:nm 无法查看源码文件(.c/.cpp)?
A:nm 仅支持二进制文件(.o/.a/.so/可执行文件)。
Q2:如何查看 C++ 类的成员函数?
A:使用 -C 选项解析修饰名,例如:
nm -C lib.so | grep MyClass::method
Q3:符号显示为 V 类型是什么?
A:V 表示弱符号(Weak Symbol),常见于虚函数表(vtable)。
结语:掌握 nm 命令,相当于拥有了透视二进制文件的“X 光眼”。无论是日常开发中的链接问题,还是复杂的内存分析,它都能提供关键线索。下次遇到符号问题时,不妨先敲一句 nm -CD,或许答案就在其中。
扩展工具推荐:
-
objdump:反汇编与段信息分析 -
readelf:查看 ELF 文件头信息 -
ldd:动态库依赖检查
参考文档: