在 Linux 环境中,nm
是一个命令行工具,用于显示目标文件(如可执行文件、目标文件或共享库 .so
文件)中符号表的内容。配合参数和管道操作,它可以实现很多功能。
基本解释
-
nm
:- 用于列出目标文件中的符号表。
- 符号表包括函数、变量等信息,是编译器生成的元数据,方便链接器和调试器使用。
-
-C
:- 让
nm
显示经过“解混淆”(demangle)后的符号名称。 - 在 C++ 编译过程中,函数或变量的名字通常会经过“名称修饰”(name mangling),使得重载等功能可以被支持。
-C
会将修饰的名字还原为原始的、可读的 C++ 名字。
- 让
-
.so
文件:- 是 Linux 中的共享库(shared object),类似于 Windows 的
.dll
文件。
- 是 Linux 中的共享库(shared object),类似于 Windows 的
-
grep
:- 用于过滤输出,只显示匹配特定模式的行。
nm -C .so | grep
的作用
这个命令组合的作用是:
- 提取
.so
文件中的符号表信息。 - 解码 C++ 名称修饰。
- 通过
grep
筛选感兴趣的符号。
具体步骤
-
列出符号表:
nm .so
会列出符号表,包括符号类型、地址和名称。
例子(未使用
-C
,符号未解混淆):nm libexample.so 00000000 T _Z3addii 00000010 T _Z4subii
-
解混淆符号:
nm -C .so
会将 C++ 符号还原为可读的形式:
nm -C libexample.so 00000000 T add(int, int) 00000010 T sub(int, int)
-
筛选符号:
- 使用
grep
筛选特定的符号。例如:
nm -C libexample.so | grep add 00000000 T add(int, int)
- 使用
符号类型解释
在 nm
输出中,符号类型用一列字母表示,比如 T
、U
、D
等。以下是常见的符号类型:
符号类型 | 含义 |
---|---|
T | 定义在目标文件中的全局(函数)符号 |
U | 未定义符号(引用到外部的函数或变量) |
D | 已初始化的全局数据 |
B /b | 未初始化的全局数据(BSS 段) |
C | 未初始化的静态数据 |
W | 弱符号(可被覆盖) |
V | 弱对象(变量) |
实际用途
-
查找特定函数或变量:
- 如果知道某个函数或变量的名字,可以使用
grep
过滤输出。例如:nm -C libexample.so | grep my_function
- 如果知道某个函数或变量的名字,可以使用
-
调试共享库:
- 确认某个函数是否导出、是否存在符号修饰问题。
- 检查未定义符号,确认依赖是否缺失:
nm -C libexample.so | grep U
-
分析库的符号结构:
- 查看导出函数或全局变量。
- 匹配特定命名空间、类或函数的符号。
示例
假设 libmath.so
包含以下代码:
// math.cpp
extern "C" int add(int a, int b) {return a + b;
}extern "C" int sub(int a, int b) {return a - b;
}namespace Math {double multiply(double a, double b) {return a * b;}
}
编译成共享库:
g++ -fPIC -shared math.cpp -o libmath.so
运行命令:
nm -C libmath.so | grep Math
输出:
00000020 T Math::multiply(double, double)
说明 Math::multiply
函数已正确导出到共享库中。
总结
nm -C .so | grep
是调试和分析共享库时的常用工具,尤其是在 C++ 程序中,可以:
- 快速检查符号是否正确导出。
- 查找未定义的符号依赖。
- 确认名称修饰问题(函数匹配失败等)。