nm —— 列出目标文件/库的符号表
nm(全称 names,取自其输出内容"符号的名字",Unix 早期工具)专门列 ELF 的符号表(函数、全局变量的名字、地址、类型)。最常用来排查链接错误(undefined symbol)、确认某库有没有导出某符号、看二进制有没有被 strip。属 binutils。
数据来源
- 来源对象:目标 ELF 文件的符号表节——默认读
.symtab+.strtab(完整符号表,strip后会消失);加-D读.dynsym+.dynstr(动态符号表,动态链接必需,strip 也删不掉)。 - 采集方式:静态读文件,不运行程序。
- 由此决定的特性:一个二进制
nm输出 "no symbols" 而nm -D仍有内容,就说明它被strip过(普通符号删了、动态符号还在)——这正是判断"能否用 gdb 完整回溯"的依据。
bash
yum install binutils / apt install binutils一、常用选项
bash
nm <file> # 列符号(默认 .symtab)
nm -D <file> # 动态符号表 .dynsym(.so 和已 strip 的可执行文件看这个)
nm -C <file> # demangle:还原 C++ 修饰名为可读函数名
nm --defined-only <file> # 只看本文件已定义的符号
nm -u <file> # 只看未定义(undefined)符号 —— 排查链接错误
nm -n <file> # 按地址排序
nm -S <file> # 显示符号大小
nm -A *.o # 每行前加文件名(在一堆 .o 里找符号归属)二、读懂输出
bash
$ nm -C ./cpu_demo
0000000000001189 T main
0000000000001256 T busy_kernel_cpu()
U strcpy@GLIBC_2.2.5
0000000000004050 B g_tick
0000000000002004 R _IO_stdin_used三列:地址 | 类型(一个字母) | 符号名。类型字母(大写=全局/外部,小写=本地):
| 字母 | 含义 | 对应 section |
|---|---|---|
T/t | 代码(Text) | .text,函数 |
D/d | 已初始化数据 | .data |
B/b | 未初始化数据 | .bss(如 g_tick) |
R/r | 只读数据 | .rodata |
U | 未定义(本文件没有,要外部提供) | 链接时从别的 .o/.so 找 |
W/w | 弱符号(weak,可被覆盖) | |
A | 绝对符号 |
三、两个高频用途
① 排查 "undefined reference" 链接错误
链接报 undefined reference to 'foo' 时:
bash
nm -u main.o # 看 main.o 缺哪些符号(U 开头的)
nm -C --defined-only libfoo.a | grep foo # 看某库到底导没导出 foo(大写 T=导出了)
nm -DC libfoo.so | grep foo # 动态库同理若你要的符号在库里是 U(也未定义)或压根没有 → 库不对/版本不匹配/没链上。C++ 还要注意 mangling 和 extern "C":C++ 符号带修饰,C 符号不带,不匹配会找不到。
② 确认二进制是否被 strip
bash
nm ./app
# 输出 "no symbols" 或报错 → 已被 strip,gdb bt 会是问号
nm -D ./app # 动态符号还在(strip 删不掉 .dynsym)四、结合本仓库
bash
make
nm -C ./cpu_demo | grep -E 'main|busy|idle' # 看三个场景函数的地址和类型(T)
nm -C ./cpu_demo | grep g_tick # 全局变量 g_tick,类型 B(.bss) 或 D
nm -u ./cpu_demo | grep -iE 'write|pthread' # 依赖外部(U)的 libc/pthread 符号
strip ./cpu_demo
nm ./cpu_demo # "no symbols" —— 符号被删
nm -D ./cpu_demo # 动态符号仍在只想还原单个 mangled 名字(不整表)用
c++filt,见 misc。看符号所在段的详细属性用 readelf-s。
五、一句话总结
nm 输出 地址|类型|符号名,T 为代码、U 为未定义引用、B/R/D 对应不同数据段;可判断是否被 strip、定位符号所属段。