ELF 相关小工具合集
一批单独讲篇幅太短、但很常用的 ELF/二进制工具。多数属 binutils。
数据来源
- 来源对象:本页工具几乎都静态读/改目标文件本身——file/
size/strings/addr2line从 ELF 各节读取信息,strip/objcopy就地读改 ELF 节。 - 采集方式:不运行程序、不读
/proc、不依赖内核。两个例外:strings 也能扫任意(非 ELF)二进制文件;c++filt只对传入的名字字符串做还原,根本不读文件。 - 由此决定的特性:全部可安全用于来路不明的二进制;
addr2line要给出源码行需二进制带-g/有对应.debug,否则只能返回??。
file —— 一眼识别文件类型
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 的第一步。
file core.1234 # core 文件会显示:ELF ... core file, from './cpu_demo'size —— 看 text/data/bss 段大小
$ size ./cpu_demo
text data bss dec hex filename
2565 640 16 3221 c95 ./cpu_demo| 段 | 含义 |
|---|---|
text | 代码 + 只读数据 |
data | 已初始化的全局/静态变量(占文件体积) |
bss | 未初始化的全局/静态变量(不占文件体积,加载时清零) |
用途:对比优化前后代码体积;验证"大数组无初值进 bss、有初值进 data"(见 elf-format 第四节)。
make && size ./cpu_demo
make release && size ./cpu_demo # 对比 -O2 后 text 段变化strings —— 提取可打印字符串
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 —— 剥离符号,减小体积
strip ./app # 删掉 .symtab / .debug_*,二进制变小
strip --strip-debug ./app # 只删调试信息,保留普通符号表
strip --strip-unneeded lib.so # 库:删掉动态链接不需要的符号代价:strip 后 gdb bt 变问号。所以生产做法是先分离符号再 strip(见下面 objcopy 和 core-dump)。
objcopy —— 复制/转换目标文件(分离调试符号的核心)
# 分离调试符号三件套(线上二进制小 + 仍可事后调试)
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。
addr2line —— 地址反查源码行
崩溃日志/backtrace 里只有地址没有行号时,用它反查(需二进制带 -g 或有对应 .debug):
addr2line -e ./cpu_demo -f -C 0x1256
# busy_kernel_cpu() <- -f 显示函数名,-C demangle
# /home/derek/perf/main.cpp:86 <- 源码文件:行号-e指定二进制,-f输出函数名,-Cdemangle,可一次传多个地址。- 很多崩溃处理脚本(含 core-dump 的管道脚本思路)用它把裸地址栈转成可读行号。
# 配合 perf/dmesg 里的地址:
addr2line -e ./cpu_demo -fC 0x1189 0x1256 # 批量反查c++filt —— 还原 C++ 修饰名(demangle)
C++ 符号在 ELF 里是 mangled 的(_Z15busy_kernel_cpuv)。单独还原一个名字:
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,先跑这几条
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 分析总结
排查陌生二进制按 file→readelf→nm/ldd→strings 顺序定位类型、架构、依赖与符号;objcopy/addr2line/c++filt 负责分离调试符号、地址反查行号与还原 C++ 修饰名。