thread apply all bt 是最直接有效的方式,可一次性打印所有线程的完整调用栈,不依赖源码和完整符号,只要调试信息未被strip即可显示函数名和偏移地址。

thread apply all bt 是最直接有效的方式
只要进程还在运行或已 core dump,进 gdb 后执行 thread apply all bt 就能一次性打印每个线程的完整调用栈。它不依赖源码是否在本地、符号是否全,只要调试信息没被 strip 掉,就能看到函数名和偏移地址。
常见误操作是只输 bt ——那只会显示当前线程(通常是主线程)的栈,多线程卡死时大概率漏掉真凶。
-
thread apply all bt是标准写法,兼容所有 GDB 版本 -
t a a bt是缩写,但部分旧版 GDB 不识别,别图省事 - 如果输出太长看不清,可先用
set height 0关闭分页,再执行
info threads 必须配合 thread 切换才能看单个线程详情
info threads 只列出线程 ID、LWP ID 和状态(比如 * 3 Thread 0x7ffff77bf700 (LWP 1235)),星号标出当前活跃线程。但它本身不展示栈,只是“地图”。
要查某个线程具体卡在哪,得先 thread 3(换成对应编号),再 bt。漏掉这步就等于有地图却不导航。
- 线程编号来自
info threads输出的第一列,不是系统 PID 或 LWP ID - 切换后
frame 0能确认是否真跳进去了,避免误判当前上下文 - 如果某线程状态是
Blocked或长时间停在futex_wait,大概率在等锁
pstack 更适合快速现场快照
当没权限启 gdb、或只想秒级判断“进程是不是挂死了”,pstack <pid></pid> 是最轻量的选择。它本质是 gdb -p <pid></pid> + thread apply all bt 的封装,但启动快、无交互、不依赖调试符号。
缺点是看不到局部变量和源码行号,纯靠函数名和系统调用猜逻辑。比如看到一堆 poll 或 epoll_wait,基本确定在事件循环里空转;全是 pthread_mutex_lock,就得怀疑死锁。
- 必须确保
pstack已安装(通常在gdb包里,CentOS/RHEL 默认有,Ubuntu 需装gdb) - 对刚 fork 出还没 exec 的子进程无效,因为还没加载符号表
- 输出中每个线程以
#0 ... #1 ...分隔,注意别把不同线程的帧混读
崩溃后用 core 文件分析,bt full 才暴露关键变量
程序 segfault 生成 core 后,用 gdb ./a.out core.xxx 加载,此时 bt 只能告诉你哪一行崩了,但不知道“为什么崩”。真正有用的是 bt full ——它会逐帧打出参数值和局部变量。
比如崩溃点在 func_b(p=0x0),bt full 可能显示上层 func_a 里 ptr = 0x0,立刻定位空指针源头。而仅靠 bt 只能看到函数名,还得手动 frame 1 + print ptr,效率低一倍。
-
bt full依赖编译时加-g,且不能开-O2以上优化(变量可能被优化掉) - 若
bt full某帧显示value optimized out,说明该变量不可见,得降级重编译 - core 文件权限需与原进程一致,否则 gdb 读不到内存映射,
bt可能报No stack.
多线程堆栈最易忽略的其实是线程状态语义:info threads 里标 Running 不代表真在 CPU 上跑,可能是内核态阻塞;Stopped 也不一定挂了,可能是被 gdb 暂停。真正要盯的是栈顶函数——它比任何状态标记都诚实。











