core文件必须完整包含所有线程的寄存器和栈信息,否则thread apply all bt会漏关键线程;常见问题是core生成不全(如仅主线程或被ulimit -c截断),需用file和gdb info threads验证线程数是否合理。

确认多线程core是否完整生成
多线程程序崩溃时,core 文件必须包含所有线程的寄存器和栈信息,否则 thread apply all bt 会漏掉关键线程。常见问题不是“没生成 core”,而是“生成了但不全”——比如只保存了主线程,或因 ulimit -c 限制太小被截断。
验证方法:file core.xxx 应显示类似 core file x86_64;再用 gdb -q ./prog core.xxx -ex "info threads" -ex "quit" 查看线程数是否合理(比如预期 20 个 worker 线程,却只看到 1–2 个,大概率是 core 不全)。
- 确保
ulimit -c unlimited在崩溃前已生效(注意:新终端需重新设置) - 避免使用
core_pattern中含%u(用户名)或%h(主机名)等可能导致路径非法的字段 - 容器环境务必挂载可写目录并显式传入
--ulimit core=-1
进入GDB后第一件事:查所有线程状态
bt 默认只显示当前线程,而多线程崩溃往往发生在某个“安静”的 worker 线程里,主线程可能还在 pthread_cond_wait 或 epoll_wait。必须立刻执行 thread apply all bt,而不是先看 bt。
输出中重点关注三类线程:
- 栈顶是
pthread_cond_wait、nanosleep、__futex_abstimed_wait_common的——大概率是正常等待,跳过 - 栈顶是业务函数(如
handle_request、process_data),且调用链清晰、无地址乱码——重点怀疑对象 - 栈顶出现
0x0000000000000000、0xffffffffffff0000、hol_entry_qcmp等明显异常符号——极可能是栈被踩坏,真正的破坏点在更早的某次内存越界或 use-after-free
定位真正出问题的线程
当 thread apply all bt 输出几十上百行时,靠肉眼扫效率低还易漏。实际做法是先过滤出非等待线程:thread apply all bt 1 只看每线程栈顶一行,快速识别哪些线程在执行业务逻辑。
然后对可疑线程逐个深入:
- 用
thread <n>切换到目标线程(n是线程编号) - 执行
bt full查看局部变量值,特别注意指针是否为NULL、是否指向已释放内存(0xdeadbeef、0xc0000000等典型填充值) - 若栈帧中有
std::vector::at或operator[]调用,检查下标是否越界(print i或print vec.size()) - 对疑似内存破坏,用
info registers看rip是否落在 libc 或未知地址,再用x/10i $rip反汇编崩溃点附近指令
常见陷阱:SIGABRT 不等于崩溃点就是 bug 点
多线程程序遇到 SIGABRT(而非 SIGSEGV)时,bt 显示的栈通常是 glibc 检测到堆损坏后主动 abort 的位置(比如 malloc_consolidate),**真正的内存越界或 double-free 往往发生在几秒甚至几分钟前**。
这时不能只盯崩溃栈,要结合:
- 线程间共享数据结构(如全局 map、队列)的访问是否加锁——检查
mutex、spinlock使用是否一致 - 是否有线程在释放内存后,其他线程仍持有该地址的指针(尤其注意
std::shared_ptr生命周期管理) - 用
info proc mappings确认崩溃时内存布局,再用x/20gx <addr>查看疑似野指针附近的内存内容是否符合预期
真正难的不是看懂 bt,而是理解哪个线程在什么时候改写了不该改的内存——这需要把多个线程的栈、变量、锁状态串起来推演,而不是孤立看一个崩溃点。


















