﻿# nm —— 列出目标文件/库的符号表

`nm` 专门列 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.md](/concepts/elf/misc.md)。看符号所在段的详细属性用 [readelf](/concepts/elf/readelf.md) `-s`。

