默认只显示当前线程调用栈,需用thread apply all bt批量查看所有线程栈;先info threads获gdb线程编号,再thread 切换并bt或bt full查看详情。

gdb -p 后怎么查每个线程的栈
直接 attach 进程后,默认只显示当前线程(通常是主线程)的调用栈,bt 命令不会自动展开所有线程。必须显式列出线程、切换或批量执行命令。
关键步骤是三步走:
-
info threads:列出所有线程,注意每行开头的编号(如1、2)和 LWP ID(即系统线程 ID) -
thread <n></n>:用编号切换到目标线程(不是 PID/LWP,是info threads输出的第一列数字) -
bt或bt full:查看该线程当前栈帧;若需局部变量,再补一句info locals
常见错误:把 /proc/<pid>/task/<tid>/stack</tid></pid> 里的 LWP ID 直接当 thread 参数用——GDB 内部编号和系统 LWP ID 不同,必须依赖 info threads 输出的编号。
thread apply all bt 是最省事的批量查法
想一眼扫完所有线程在哪卡着,thread apply all bt 是首选,等价缩写 t a a bt。
它会逐个线程执行 bt,输出带线程编号前缀,例如:
(gdb) thread apply all bt Thread 2 (LWP 12345): #0 0x00007f8c45... in futex_wait () from /lib/libc.so.6 #1 0x00007f8c12... in pthread_cond_wait () from /lib/libpthread.so.0 Thread 1 (LWP 12344): #0 0x00007f8c45... in poll () from /lib/libc.so.6 #1 0x00005555... in event_loop () at server.c:210
注意点:
- 如果某线程正在执行信号处理函数,
bt可能只显示sigprocmask或中断上下文,不一定是业务代码——这时得结合info registers看rip/pc地址反查 - 若输出中大量出现
??,说明缺少调试符号(-g编译)或动态库路径未设置(set sysroot或set solib-search-path) - 加
full(即thread apply all bt full)会多打局部变量,但可能因栈损坏而崩溃或卡住,生产环境慎用
pstack 更快,但信息更简略
不需要进 GDB 交互环境,pstack <pid></pid> 是现网快速排查的利器,本质是封装了 gdb -p <pid> -ex "thread apply all bt" -batch</pid>。
优势明显:
- 秒出结果,无交互等待
- 自动包含所有线程,每段以
#0 ...开头,LWP ID 清晰可见 - 不依赖源码路径,也不报错“no debugging symbols”
局限也很实在:
- 看不到局部变量、参数值(
bt full那部分) - 无法切换帧、无法
print变量、不能继续调试 - 某些容器环境或低权限进程可能被
ptrace限制,执行失败
遇到 pstack: failed to attach to <pid>: Operation not permitted</pid>,就只能退回 gdb -p <pid></pid> + thread apply all bt。
为什么 info threads 显示的线程数和 ps -eLf | grep 不一致
这是常被忽略的兼容性坑:info threads 只显示 GDB 当前能感知到的线程,而 ps -eLf 显示的是内核视角下的全部 LWP。
典型原因有:
- 线程刚创建、还没完成
pthread_create的初始化流程,GDB 尚未注册它 - 线程已退出但资源未完全回收(Zombie LWP),
ps还能看到,info threads已过滤掉 - 程序用了
clone()而非pthread_create()创建轻量级进程(如某些协程库),GDB 默认不将其识别为“线程”
此时 /proc/<pid>/task/</pid> 下的子目录数量才是真实线程数,cat /proc/<pid>/task/<tid>/stack</tid></pid> 可确认任意 LWP 的内核态栈(比如是否卡在 futex_wait 或 epoll_wait)。











