info threads 可列出所有线程id、状态、函数名和源码位置,星号标记当前活跃线程;若调试信息完整(编译带 -g 且未 strip),会显示文件与行号,否则可能显示 ?? 或汇编地址。

gdb里怎么一眼看到所有线程正在哪行代码上运行
直接用 info threads,它会列出所有线程 ID、状态、函数名和源码位置。但默认只显示函数名和文件名,不带行号——除非你编译时加了 -g 且没 strip,而且当前帧能解析出调试信息。
常见卡点:线程停在系统调用(比如 pthread_cond_wait)或内联函数里,info threads 显示的“当前行”可能是汇编地址或 ??。这时候得配合 thread apply all bt -1 看每个线程栈顶那一帧。
- 确保编译时用了
g++ -g -O0(-O2可能导致行号错位或内联掩盖真实位置) - 启动 gdb 后先执行
set print thread-events off,避免线程创建/退出消息刷屏干扰 -
info threads输出中星号*标记的是当前活跃线程,不是“主线程”
想持续监控所有线程位置,别每次手动敲命令
用 thread apply all bt -1 是最贴近“实时位置”的方案:它对每个线程只打栈顶一帧,输出紧凑,能看到函数+文件+行号(如果调试信息完整)。
但注意:bt -1 不等于“当前执行行”——如果栈顶是内联函数或优化掉的帧,实际执行点可能在调用它的上层函数里。这时得用 thread apply all frame 看当前帧的 pc 和源码行映射。
- 加个快捷命令:在
~/.gdbinit里写define tbt1\nthread apply all bt -1\nend,之后直接输tbt1 - 如果线程数多(比如 50+),
thread apply all会变慢,可先用info threads | head -20人工筛选关键线程再单查 - 某些场景下(如线程刚创建还没进用户代码),
bt -1会显示start_thread或clone,说明还没执行到你的std::threadlambda 或函数对象
为什么 display 或 watch 对线程位置无效
display 只作用于当前线程的表达式,watch 监控的是内存地址变化,两者都不反映“哪个线程在哪行”。试图 display $pc 只会在你切线程时刷新当前线程的程序计数器,不会自动轮询所有线程。
真正能“实时”更新的只有命令驱动方式:比如用 shell 循环调 gdb 的 batch 模式,但代价是暂停整个进程——这反而破坏了“实时性”。所以实践中,所谓“实时”其实是快速采样,而非连续流式输出。
- 不要尝试
watch *(int*)$pc:$pc 是寄存器值,类型转换非法,gdb 会报Cannot access memory -
display /i $pc可以看当前线程的反汇编指令,但无法跨线程;切换线程后需手动redisplay - 若需长期跟踪,建议改用
perf record -e sched:sched_switch配合perf script,它从内核事件出发,不依赖符号表也不暂停进程
多线程调试时容易忽略的符号与加载问题
即使有 -g,gdb 也可能找不到部分线程的源码位置——尤其是动态链接的 libstdc++.so 或你自己 dlopen 的模块。这时 info threads 会显示 ??,bt -1 只能打出函数名(如 std::this_thread::sleep_for),没有文件行号。
解决路径很具体:确认对应 so 文件是否带调试符号(file /usr/lib/x86_64-linux-gnu/libstdc++.so.6 看有没有 “with debug_info”),并检查 set debug-file-directory 是否指向正确的 .debug 路径。
- Ubuntu/Debian 上装
libstdc++6-<version>-dbg</version>包才能看到标准库内部行号 - 自己编译的 shared library 必须用
g++ -g -fPIC,且不能 strip;加载后用info sharedlibrary确认状态为Yes - 如果线程卡在
__lll_lock_wait这类 futex 调用里,说明它正阻塞在 mutex 上,此时看持有锁的线程比看“位置”更有价值——用thread apply all p $_stl_mutex_owner(需自定义辅助函数)或结合pstack外部分析
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











