变量约定:
LIB:要解析的*.so绝对或相对路径(请换成你的文件)。OFF:栈里+0x...的十六进制偏移;下文示例中OFF = 0x8fa860(与addr2line所用一致)。PC:栈里[0x...]的进程内虚拟地址;示例PC = 0x15182684b860(会随每次运行/ASLR 变化
,仅为与下组BASE配套的一例)。BASE:与上式满足PC - BASE = OFF的映射基址;示例BASE = 0x151825f51000。具体取
maps中哪一段以实际进程为准。
场景
栈回溯里常见这种形式:
1 | /path/to/libapp.so(+0x8fa860)[0x15182684b860] |
含义:
libapp.so:出问题的共享库(例名,请替换为实际名)。+0x8fa860:相对该次映射基址的偏移(示例中固定为上述OFF)。[0x15182684b860]:当时的绝对虚拟地址(示例;单独用时需配合maps中的BASE)。
调试时通常先用 +0x... 偏移 对 磁盘上的 .so 文件 做解析。
首选:addr2line
在带有 debug info(-g,且未 strip 掉调试信息)的 .so 上:
1 | addr2line -e /path/to/libapp.so -f -C 0x8fa860 |
形态示例(你本机实跑时会是你的函数与路径):
1 | myapp::CommandHandler(int, char const**, long, char const*) |
addr2line 可能把路径记成编译时目录(如 out/../handler.cpp),以 DWARF 记录为准;若行号对不上
,多半是二进制与当前源码不是同一版构建。
| 选项 | 作用 |
|---|---|
-e FILE |
指定 ELF(.so 或可执行文件) |
-f |
同时打印函数名 |
-C |
Demangle C++ 符号 |
从「PC + 映射基址」反算并调用(注意 64 位运算)
addr2line 需要的是相对该 ELF 文件布局的偏移,通常就是栈上 +0x...,此处即
0x8fa860。若你只有 PC 和 BASE,应得到 OFF = PC - BASE(在 64 位下计算)。
Bash 的 $(( ... )) 对很大的十六进制字面量会溢出;大地址用 Python 等做减法后再喂给
addr2line。
脚本形态(数值与上节 PC / OFF / BASE 一致):
1 | python3 <<'PY' |
形态示例(BASE 行与上式为算术一致的一例;函数名/路径为占位):
1 | BASE = 0x15182684b860 - 0x8fa860 = 0x151825f51000 |
当 pc - base 与栈中 +0x8fa860 一致时,与直接 addr2line -e ... -C 0x8fa860 等价。
补充:nm / objdump(看落在哪个符号里)
无行号信息或想确认符号边界时,可按地址排序查看。全量往往很长,可只取前几行,或用 grep 搜已知子
串(来自 demangle 后的名字片段)。
1 | nm -n --defined-only /path/to/libapp.so | head -5 |
形态示例:
1 | 0000000000000000 n _GLOBAL_OFFSET_TABLE_ |
粗判:在排序列表里找 地址不大于目标 0x8fa860 的最后一个对应函数符号。精确行号仍以 addr2line
为准。
符号条数可能很大,例如某次对同一类大 .so 的统计(仅作数量级参考):
1 | nm -n --defined-only /path/to/libapp.so | wc -l |
形态示例:
1 | 86817 |
用 objdump 在符号表里按短关键字辅助定位;下面地址与偏移 0x8fa860 配套(符号起点略小于该
PC,落在函数体内):
1 | objdump -t /path/to/libapp.so | grep 'CommandHandler' | head -3 |
形态示例:
1 | 00000000008fa71a l F .text 0000000000000abc _ZN5myapp15CommandHandlerEiPPKclS1_ |
行首 0x8fa71a 为该符号的起点,大小 0xabc;0x8fa860 落在区间 [0x8fa71a, 0x8fa71a+0xabc)
内。精确行号仍以 addr2line 为准。
readelf:查看 LOAD 段
下面来自与上述 0x8fa860 同一 ELF 样例的 readelf -l | head -20(段尺寸因库而异,此组与示
例偏移同时出现时便于对照;若你换库则整段以本机为准):
1 | readelf -l /path/to/libapp.so | head -20 |
配套示例输出:
1 |
|
不同 readelf 版本与不同 ELF 细节可能略有差异,以本机实跑为准。
C++ 符号 demangle
1 | echo '_ZN5myapp15CommandHandlerEiPPKclS1_' | c++filt |
形态示例:
1 | myapp::CommandHandler(int, char const**, long, char const*) |
(上式 mangled 名须换成你从 nm/objdump/栈上看到的真实字符串。)
文件是符号链时
1 | file /path/to/libapp.so |
形态示例:
1 | /path/to/libapp.so: symbolic link to ../../build/obj/libapp.so |
addr2line 应针对实际打开的那份 ELF(解析符号链后的目标或你确认参与调试的那份);若安装目录下链
到构建树,以 inode 与更新时间为准,避免对旧副本解析。
常见问题
-
??或明显不对- 二进制被 strip、或 debug 在单独文件且工具未找到 → 需带 debug 的构建物或
DEBUGINFOD等。
- 二进制被 strip、或 debug 在单独文件且工具未找到 → 需带 debug 的构建物或
-
行号与当前仓库不一致
- 对应栈的二进制与当前源码版本/配置不同 → 以构建该库时的源码为准。
-
大地址用 Bash 做
PC - BASE- 易 32 位溢出;用
python3或能表示 64 位无符号/有符号整数的工具计算后再调addr2line。
- 易 32 位溢出;用
最小复现(变量 + 一次 addr2line)
1 | SO=/path/to/libapp.so |
形态示例:
1 | myapp::CommandHandler(int, char const**, long, char const*) |
将 SO 换成你的库路径;偏移若与栈上一致,可继续用 0x8fa860 作对照。函数名、源路径与行号以
你本机 addr2line 输出为准(受调试信息与构建一致性约束)。