catch throw是gdb中唯一能停在c++异常抛出瞬间的命令,此时栈帧完整、局部变量有效、尚未栈展开;需配合-g -o0编译、完整调试符号及多线程检查(如thread apply all bt)确保捕获准确。

用 catch throw 捕获异常抛出点
默认情况下,GDB 不会在 C++ 异常抛出时中断,所以你根本看不到“抛出前”的状态。必须主动设置异常捕获点:catch throw 是唯一能停在 throw 语句执行**瞬间**的命令——此时栈帧完整、局部变量有效、尚未开始栈展开(unwind)。
常见错误是误用 catch catch:它只停在异常被处理的位置,那时调用栈可能已被截断(尤其跨 shared library 时),原始抛出上下文早已丢失。
-
catch throw后运行程序,一旦有throw执行,GDB 立即中断在那一行 - 若只想捕获特定类型异常,可用
catch throw std::runtime_error(注意:需调试符号完整,且类型名拼写严格匹配) - 不建议加
-r(正则)参数盲目匹配,容易漏掉模板实例化后的实际类型名
中断后立刻执行 bt 查看原始调用链
停在 throw 处时,调用链就是“异常抛出前”的真实路径。此时 backtrace(或简写 bt)输出的是从 throw 行一直回溯到 main 或线程入口的完整帧序列,每一层都保留着当时的参数和局部变量。
容易忽略的关键点:如果程序启用了优化(如 -O2),编译器可能内联函数或删减帧信息,导致 bt 显示不全。务必用 -g -O0 或至少 -g -O1 重新编译调试版。
-
bt full可同时显示各帧的局部变量值,比单纯bt更直观 - 若某帧显示为
(inlined by),说明该函数被内联,需结合源码和info registers推断上下文 - 避免在中断后先执行
step或next—— 这会触发栈展开,帧立刻销毁,再查bt就只剩顶层了
多线程下必须先 thread apply all bt 定位问题线程
C++ 异常是线程局部的,catch throw 默认只对当前线程生效。如果崩溃发生在 worker 线程,而你在主线程下断点,会完全错过。
正确做法是:启动 GDB 后先 run,等异常发生导致程序终止(或卡死),再用 thread apply all bt 扫描所有线程的最终状态。真正抛出异常的线程,其栈顶通常停在 __cxa_throw 或类似符号上,而非你的业务函数——这是线索。
- 若想提前监控所有线程的 throw,需在
run前执行set follow-fork-mode child和set schedule-multiple on,再配合catch throw -
info threads要配合thread <n></n>切换后单独bt,不能只信单一线程的输出 - 某些异常(如
std::terminate触发的SIGABRT)可能不走标准 throw 流程,此时要改用catch signal SIGABRT
为什么 bt 有时只显示一层或全是 ??
这几乎总是调试信息缺失或符号未加载导致的,不是 GDB 本身的问题。
典型场景包括:可执行文件没带 -g 编译、动态链接的 .so 没部署对应 debuginfo 包、或者用了 strip 命令清除了符号。此时 bt 只能靠栈地址硬猜,自然大量显示 ??。
- 用
info sharedlibrary检查所有 so 是否已加载调试符号;缺失的需手动add-symbol-file /path/to/libxxx.so.debug 0xaddr -
readelf -S ./a.out | grep debug可确认二进制里是否真有.debug_*段 - 不要依赖 IDE 图形界面的“自动解析”,很多 IDE 的 GDB 插件会静默跳过符号加载失败的提示
catch throw 真正生效,并确保那一刻的栈帧不被优化或符号缺失破坏。











