Linux下崩溃时获取函数名需捕获信号后调用backtrace与backtrace_symbols,但必须编译时加-rdynamic且未strip;musl环境应改用libunwind;信号处理中仅限异步信号安全操作。

崩溃时用 backtrace 和 backtrace_symbols 拿到函数名
Linux 下最直接的方式是捕获信号(比如 SIGSEGV、SIGABRT)后调用 backtrace 获取地址,再用 backtrace_symbols 转成可读符号。前提是程序没 strip,且链接时加了 -rdynamic(否则动态符号表里找不到函数名)。
常见错误现象:backtrace_symbols 返回的全是 ??,或者只有地址没函数名——八成是没加 -rdynamic,或者用了 strip 去除了符号。
- 编译必须加
-rdynamic:否则backtrace_symbols查不到动态链接库里的函数名 - 调试信息不是必须的,但加
-g后能显示行号(需配合backtrace_symbols_fd+addr2line或自己解析) - 别在信号处理函数里用
malloc、printf等非异步信号安全函数——backtrace和backtrace_symbols是安全的,但后续处理要小心
glibc 的 backtrace 在不同架构或 musl 下不工作
glibc 提供的 execinfo.h 一套只在 glibc 环境下稳定;musl libc(Alpine 默认)、部分嵌入式环境或旧内核上可能返回 0 帧,或者地址全为 0。这时候不能硬依赖它。
使用场景:你打包的程序跑在 Alpine 容器里,崩溃后调用栈为空——不是代码写错了,是 libc 不支持。
立即学习“C++免费学习笔记(深入)”;
- 确认 libc 类型:
ldd --version或cat /lib/libc.so.6 | head -n1 - musl 下可用
libunwind替代(需静态链接-lunwind),接口类似但更底层、更可靠 - Android NDK 默认用的是
libunwind,不是 glibc 那套,直接 includeunwind.h即可
释放资源前打印栈容易导致二次崩溃
信号处理函数里如果调用了析构函数、智能指针释放、日志 flush 等操作,而此时堆/全局状态已损坏,很容易触发二次 SIGSEGV,覆盖原始崩溃点,让问题更难定位。
典型表现:日志里只看到 “double free or corruption”,但看不到第一次出错的调用路径。
- 信号处理中只做最简操作:调用
backtrace→ 写入文件描述符(write)→_exit(不是exit) - 避免用
std::cout、spdlog::info、std::string构造等任何可能分配内存或锁全局资源的操作 - 把栈信息先存进预分配的固定大小 buffer(比如
char buf[8192]),再一次性write(2)到 stderr 或文件
C++ 异常未捕获时怎么补上栈回溯
std::set_terminate 可以拦截未捕获异常,但它发生时栈已经展开完毕,backtrace 拿到的是 terminate handler 自己的帧,不是抛异常的位置。
真正有用的解法是在抛异常前插桩,或者用编译器扩展(GCC/Clang 支持 -fexceptions -fasynchronous-unwind-tables)配合 libunwind 在 terminate 中还原原始栈。
- 最稳妥的做法:全局重载
operator new+__cxa_throwhook(复杂,慎用) - 轻量方案:用
__builtin_return_address(0)快速记下抛点,配合日志上下文推断(适合调试阶段) - 生产环境建议统一用
std::set_unexpected+std::set_terminate组合,并确保二者都只调用 async-signal-safe 函数
真正麻烦的从来不是“怎么打出栈”,而是“打出的栈能不能对应到源码行”——这取决于符号是否在线、编译选项是否一致、运行时加载路径有没有被 LD_PRELOAD 干扰。留个心眼:每次发布二进制,顺手保存一份 .so 和 .debug 文件。



















