最轻量解析函数地址为文件行号的方案是 addr2line,需可执行文件含调试信息(-g 编译且未 strip),配合 -f 和 -c 参数显示函数名与 demangle c++ 符号,共享库地址须用对应 so 文件并减去 aslr 偏移。

Linux 下用 addr2line 解析函数地址为文件行号
直接能用、不依赖源码编译方式、最轻量的方案就是 addr2line。它读符号表(debug info),把运行时拿到的地址转成 filename:line,前提是你的可执行文件带调试信息(-g 编译)且没被 strip 过。
常见错误现象:addr2line -e ./a.out 0x401234 输出全是 ?? —— 十有八九是没调试符号,或者地址本身不在代码段(比如栈上地址、动态库地址没指定 -f 和 -C)。
- 必须加
-f才能显示函数名,加-C才能 demangle C++ 符号(否则看到_ZSt6bitsetILm128EE这种) - 如果地址来自共享库,得用
addr2line -e /path/to/libxxx.so 0x7ffff7bcb123,不能只查主程序 - 地址要减去加载基址(ASLR 开启时),实际得先从
/proc/pid/maps或dladdr()拿到模块加载偏移再相减
C++ 运行时用 backtrace() + backtrace_symbols() 获取符号名
这是 glibc 提供的最简运行时堆栈捕获接口,但注意:backtrace_symbols() 只返回函数名(demangled 后),不带文件和行号 —— 它压根不查 debug info,只靠 ELF 的 symbol table,所以你看到的可能是 my_func,也可能是 ??(优化后内联或符号被丢)。
使用场景:快速打点、日志里记个函数名就够了;不适合精准定位 bug。
-
backtrace()返回的是程序计数器地址(void*数组),不是源码行号 -
backtrace_symbols()输出字符串里不含路径和行号,连std::string::append这种都可能显示为std::string::append而非完整签名 - 跨平台差:Windows 不支持,macOS 上行为不一致(有时返回空)
用 libdw(elfutils)在 C++ 里做真正符号化解析
想在程序里自己把地址转成 file.cc:42,就得深入 debug info,libdw 是目前 Linux 上最靠谱的选择(比自己啃 DWARF 格式现实得多)。它能读 .debug_line 段,精确匹配地址到源码位置。
性能影响小,但链接麻烦、依赖强:要 -ldw -lelf,且运行时需确保目标文件没被 strip,debug info 在同一目录或通过 DEBUGINFOD_URLS 可访问。
- 关键函数是
dwarf_getsrcfiles()和dwarf_getsrclines(),配合dwarf_addrdie()定位 CU - 别直接用
dwarf_getsrcinfo()——它只对函数入口有效,对栈帧内任意地址可能失败 - 地址必须是“重定位后”的虚拟地址(即
dladdr()返回的dli_fbase+ 偏移),不是原始backtrace()地址
为什么 __cxa_demangle 不能替代文件行号解析
__cxa_demangle 只干一件事:把 _ZSt3minIiEET_S2_S2_ 变成 std::min<int></int>。它完全不碰源码位置,也不读 ELF 或 DWARF —— 函数名和文件行号是两套独立信息,混用会掉坑里。
容易踩的坑:有人拿 backtrace_symbols() 结果喂给 __cxa_demangle,以为就能“还原源码”,结果发现连 main 都没行号,更别说模板实例化位置。
-
__cxa_demangle输入必须是 mangled name,不是地址,也不是backtrace_symbols()返回的已 demangle 字符串 - 它不检查有效性,传错字符串可能直接
free()野指针导致崩溃 - 无文件/行号能力,和
addr2line或libdw属于不同层级的工具
真要落地,得先用 backtrace() 拿地址,再用 dladdr() 查模块,然后选 addr2line(离线)或 libdw(在线)做映射。中间少一环,就只剩问号。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











