# ELF 相关小工具合集

一批单独讲篇幅太短、但很常用的 ELF/二进制工具。多数属 binutils。

## 数据来源

- **来源对象**：本页工具几乎都**静态读/改目标文件本身**——`file`/`size`/`strings`/`addr2line` 从 ELF 各节读取信息，`strip`/`objcopy` 就地读改 ELF 节。
- **采集方式**：不运行程序、不读 `/proc`、不依赖内核。两个例外：`strings` 也能扫任意（非 ELF）二进制文件；`c++filt` 只对**传入的名字字符串**做还原，根本不读文件。
- **由此决定的特性**：全部可安全用于来路不明的二进制；`addr2line` 要给出源码行需二进制带 `-g`/有对应 `.debug`，否则只能返回 `??`。

---

## file —— 一眼识别文件类型

```bash
file ./cpu_demo
# ./cpu_demo: ELF 64-bit LSB pie executable, x86-64, dynamically linked,
#             interpreter /lib64/ld-linux-x86-64.so.2, ... with debug_info, not stripped
```

一行读出：位数、字节序、类型(pie/exec/shared object/core)、架构、动静态链接、**有没有调试信息、有没有 strip**。分析任何二进制/core 的第一步。

```bash
file core.1234    # core 文件会显示：ELF ... core file, from './cpu_demo'
```

---

## size —— 看 text/data/bss 段大小

```bash
$ size ./cpu_demo
   text    data     bss     dec     hex filename
   2565     640      16    3221     c95 ./cpu_demo
```

| 段 | 含义 |
|----|------|
| `text` | 代码 + 只读数据 |
| `data` | 已初始化的全局/静态变量（占文件体积）|
| `bss` | 未初始化的全局/静态变量（**不占文件体积**，加载时清零）|

用途：对比优化前后代码体积；验证"大数组无初值进 bss、有初值进 data"（见 [elf-format.md](/concepts/elf/elf-format.md) 第四节）。

```bash
make && size ./cpu_demo
make release && size ./cpu_demo    # 对比 -O2 后 text 段变化
```

---

## strings —— 提取可打印字符串

```bash
strings ./cpu_demo                    # 提取所有 >=4 字符的可打印串
strings -n 8 ./cpu_demo               # 只要长度 >=8 的
strings ./cpu_demo | grep -i version  # 找版本号等
strings core.1234 | grep -i password  # ⚠️ 这也说明 core 里可能有明文敏感信息
```

用途：快速看二进制里的硬编码字符串（提示信息、路径、SQL、密钥——**安全红线由此而来**）；分析未知程序的功能线索。cpu_demo 里能搜到 `"===== 正在运行"` 这些提示语。

---

## strip —— 剥离符号，减小体积

```bash
strip ./app                    # 删掉 .symtab / .debug_*，二进制变小
strip --strip-debug ./app      # 只删调试信息，保留普通符号表
strip --strip-unneeded lib.so  # 库：删掉动态链接不需要的符号
```

代价：`strip` 后 gdb `bt` 变问号。所以生产做法是**先分离符号再 strip**（见下面 objcopy 和 [core-dump.md](/crash/core-dump.md)）。

---

## objcopy —— 复制/转换目标文件（分离调试符号的核心）

```bash
# 分离调试符号三件套（线上二进制小 + 仍可事后调试）
objcopy --only-keep-debug app app.debug    # 抽出调试信息到单独文件
strip app                                   # 线上二进制剥离
objcopy --add-gnu-debuglink=app.debug app   # 建立链接，gdb 自动找 app.debug
# 其它常见用途
objcopy -O binary app app.bin               # ELF 转纯二进制（嵌入式烧录）
objcopy --remove-section=.comment app       # 删除指定节
```

这是 core dump 生产实践里"既让线上二进制小、又能完整分析 core"的关键手法。**完整工作流**（build-id 匹配、版本归档、配合的优化选项、如何"接回"符号）见 [symbol-separation.md](/crash/symbol-separation.md)。

---

## addr2line —— 地址反查源码行

崩溃日志/backtrace 里只有地址没有行号时，用它反查（需二进制带 `-g` 或有对应 .debug）：

```bash
addr2line -e ./cpu_demo -f -C 0x1256
# busy_kernel_cpu()          <- -f 显示函数名，-C demangle
# /home/derek/perf/main.cpp:86   <- 源码文件:行号
```

- `-e` 指定二进制，`-f` 输出函数名，`-C` demangle，可一次传多个地址。
- 很多崩溃处理脚本（含 [core-dump.md](/crash/core-dump.md) 的管道脚本思路）用它把裸地址栈转成可读行号。

```bash
# 配合 perf/dmesg 里的地址：
addr2line -e ./cpu_demo -fC 0x1189 0x1256    # 批量反查
```

---

## c++filt —— 还原 C++ 修饰名（demangle）

C++ 符号在 ELF 里是 mangled 的（`_Z15busy_kernel_cpuv`）。单独还原一个名字：

```bash
echo '_Z15busy_kernel_cpuv' | c++filt        # busy_kernel_cpu()
c++filt _Z15busy_kernel_cpuv                  # 同上，直接传参
nm ./cpu_demo | c++filt                       # 管道：把整张符号表 demangle
```

> `nm -C`、`objdump -C`、`addr2line -C`、`gdb` 都能内置 demangle；`c++filt` 是通用管道版，处理任意来源（如日志里拷出来的一个 mangled 名）最方便。

---

## 速查：拿到一个陌生二进制/core，先跑这几条

```bash
file X                          # 是什么（类型/架构/静动态/有无符号）
readelf -h X                    # ELF 头细节
nm -C X | head                  # 有没有符号（strip 了没）
ldd X   (或 readelf -d X)       # 依赖哪些库（不信任的用 readelf）
strings X | less                # 里面有什么字符串
# 若是可执行且要读代码：objdump -SC X
# 若是 core：readelf -n X 看信号/程序，再 gdb 分析
```
