GDB默认不跟踪fork子进程,需提前设置follow-fork-mode和detach-on-fork;catch fork可拦截子进程创建,info inferiors与info threads须分进程查看,切换inferior后需手动刷新上下文。

gdb默认不跟踪fork子进程,必须显式配置
直接用 gdb ./a.out 启动多线程+多进程混合程序时,GDB 只会调试主线程所在的父进程,fork() 创建的子进程完全逃逸出调试控制——哪怕子进程内部还调用了 std::thread,GDB 也不会自动感知或接管它。
这不是 bug,而是 GDB 的默认安全策略:避免因意外跟踪大量派生进程导致失控。关键点在于,follow-fork-mode 和 detach-on-fork 这两个设置必须在 fork() 调用发生前就生效,否则毫无作用。
-
set follow-fork-mode child:fork 后立即切换调试焦点到子进程(父进程继续运行但不再被单步/断点控制) -
set detach-on-fork off:让父子进程都保留在 GDB 管理下,不分离;此时需配合info inferiors查看进程列表,用inferior 2切换到子进程 - 两者组合效果:
follow-fork-mode child+detach-on-fork off→ 子进程被跟踪,父进程暂停在fork()返回处,可分别调试
info inferiors 和 info threads 要分开看,别混了
info inferiors 显示的是进程级上下文(每个 fork 出来的独立地址空间),而 info threads 显示的是当前 inferior 内部的线程列表。一个子进程里可能有多个 std::thread,但它们只属于该 inferior,不会出现在父进程的 info threads 输出中。
常见错误是切到子进程后,直接敲 info threads 却看不到线程——其实是因为子进程还没创建线程(比如 std::thread 构造发生在 fork() 之后、且尚未执行到那行代码)。这时要先 continue 或单步走到线程创建点,再查。
立即学习“C++免费学习笔记(深入)”;
- 子进程刚 fork 完时,
info threads只显示一个线程(主线程),和父进程无关 - 若子进程用
pthread_create启动线程,GDB 通常能识别;若用std::thread,栈帧名可能带std::thread::_State_impl,但函数入口仍可定位 -
thread apply all bt只作用于当前 inferior,不是所有进程;要查所有进程的所有线程,得循环inferior+thread apply all bt
子进程太快退出?别靠 sleep 拖时间,用 catch fork 更可靠
在子进程开头加 sleep(10) 等你手动 ps 找 PID 再 attach,既污染逻辑又不可靠——尤其在线程已启动、资源已释放的场景下,sleep 位置稍错就错过关键状态。
更干净的做法是让 GDB 主动拦截 fork 事件:
-
catch fork:GDB 在每次fork()系统调用返回前中断,此时子进程已创建但尚未执行用户代码,可立刻set follow-fork-mode child并继续 -
catch syscall clone:对std::thread更有效,因为其底层依赖clone();注意不同内核版本 syscall 名可能为clone3 - 设好
catch后,用r启动,GDB 会在 fork 点停住,此时info inferiors就能看到新 inferior 已注册
切换 inferior 后,寄存器、栈、变量全要重读
GDB 不会自动同步上下文。从父进程 inferior 1 切到子进程 inferior 2 后,bt、info registers、info locals 全是旧进程的残留数据,必须手动刷新。
- 切完 inferior 后第一件事:执行
bt确认当前栈帧,再info registers看真实寄存器值(否则还是上个进程的) - 子进程的内存地址空间与父进程独立(写时复制),所以
print &global_var在父子进程中地址大概率不同,不能跨 inferior 直接比较指针值 - 若子进程崩溃产生 core 文件,要用
gdb ./a.out core.<pid>单独加载,此时info inferiors只有一个,但info threads可看到崩溃时所有存活线程
最易忽略的一点:GDB 的 follow-fork-mode 设置是会话级的,但 inferior 的生命周期独立——如果子进程 exit 了,对应 inferior 仍保留在列表里(状态为 exited),下次 run 会重用编号,而不是新建。别误以为 info inferiors 里没看到活跃 inferior 就代表没 fork 成功。


















