必须编译时加-g -rdynamic,否则backtrace_symbols只能返回地址;-g保留调试符号,-rdynamic导出所有符号;若用dlopen加载so,需so带-g且主程序链接加-Wl,--no-as-needed。

Linux 下用 backtrace 和 backtrace_symbols 获取符号化堆栈
在 Linux 环境下,C++ 没有内置异常堆栈追踪,但 GNU libc 提供了 execinfo.h 中的 backtrace 系列函数。关键点是:必须编译时加 -g -rdynamic,否则 backtrace_symbols 只能返回地址,无法解析函数名。
常见错误现象:日志里全是 ./a.out[0x4012ab] 这类无意义地址,根本看不出哪一行出问题。
-
-g保留调试符号(必要) -
-rdynamic(等价于--export-dynamic)让动态链接器导出所有符号,否则backtrace_symbols查不到函数名 - 若程序用了
dlopen加载的 so,需确保 so 也带-g,且主程序链接时加-Wl,--no-as-needed防止被优化掉
简单示例:
void log_stacktrace() {
void* buffer[100];
int nptrs = backtrace(buffer, 100);
char** strings = backtrace_symbols(buffer, nptrs);
for (int i = 0; i < nptrs; ++i) {
fprintf(stderr, "%s\n", strings[i]); // 实际写日志时替换为你的 logger
}
free(strings);
}Windows 上用 StackWalk64 配合 PDB 解析堆栈
Windows 没有 backtrace,得靠 Windows SDK 的 DbgHelp.h。核心是 StackWalk64 + SymFromAddr,但必须加载 PDB 符号——否则同样只有地址。
立即学习“C++免费学习笔记(深入)”;
容易踩的坑:默认情况下 SymInitialize 不会自动找 PDB,需显式调用 SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_UNDNAME) 并传入 PDB 路径(或设环境变量 _NT_SYMBOL_PATH)。
- Release 版本务必生成 PDB(项目属性 → C/C++ → General → Debug Information Format 设为
Program Database (/Zi),Linker → Debugging → Generate Debug Info 设为Yes (/DEBUG)) - 调用
SymInitialize时第二个参数传NULL是错的,应传 PDB 所在目录路径(如"./"或绝对路径) -
StackWalk64需要准确构造CONTEXT,在异常处理中可用GetThreadContext;在普通位置则用RtlCaptureContext
跨平台封装要注意信号与线程安全
想在段错误(SEGV)、非法指令(ILL)等崩溃时自动打堆栈?别直接在信号处理函数里调用 backtrace_symbols 或 fprintf——它们不是 async-signal-safe。
典型错误:注册 SIGSEGV handler 后调用 std::cout 或 malloc 相关函数,导致二次崩溃。
- 信号 handler 内只能调用 async-signal-safe 函数(如
write、sigaltstack),backtrace是安全的,但backtrace_symbols不是(内部用 malloc) - 正确做法:先用
backtrace存到预分配的 buffer,再用write输出原始地址;符号化解析留到主线程或 crash dump 工具(如 breakpad)里做 - 多线程环境下,
backtrace默认只抓当前线程,若需全栈需遍历线程并ptrace(Linux)或SuspendThread(Windows),代价高,慎用
生产环境建议用 libbacktrace 或 abseil 替代手写
自己封装容易漏掉符号加载、demangle、路径查找等细节。GNU libbacktrace(GCC 自带)比 execinfo.h 更健壮,支持 DWARF 解析;Google absl::debugging 提供了跨平台 GetStackTrace,底层自动处理 Linux/Windows/macOS 差异。
比如用 absl:
#include "absl/debugging/stacktrace.h"
#include "absl/debugging/symbolize.h"
<p>absl::InitializeSymbolizer(argv[0]); // 传入可执行文件路径
void* stack[100];
int nptrs = absl::GetStackTrace(stack, 100, 1);
for (int i = 0; i < nptrs; ++i) {
char tmp[1024];
if (absl::Symbolize(stack[i], tmp, sizeof(tmp))) {
LOG(INFO) << "#" << i << " " << tmp;
}
}注意:absl::InitializeSymbolizer 必须在程序启动早期调用,且传入的是真实可执行路径(/proc/self/exe 或 argv[0]),不能是相对路径或软链接。
最常被忽略的一点:符号解析依赖运行时路径上的二进制和 PDB/DWARF 是否匹配——换机器、strip 过、版本不一致都会导致堆栈变地址。上线前务必验证日志里是否真能打出函数名和行号。


















