死锁本身不会触发core dump,必须手动发送sigabrt(kill -6)或sigquit(kill -3)等信号强制进程生成core文件;需确保ulimit -c unlimited、core_pattern配置正确,并在gdb中用info threads和thread apply all bt分析多线程锁等待状态。

死锁本身不会触发 core dump,必须手动强制生成
core dump 是进程收到信号(如 SIGSEGV、SIGABRT)后由内核写入的内存快照,而死锁是线程互相等待、不发信号、也不崩溃——所以默认情况下,死锁进程不会自动生成 core 文件。你得主动干预:用 kill -6 <pid></pid>(即 SIGABRT)或 kill -3 <pid></pid>(SIGQUIT)向进程发信号,让它“自愿”终止并生成 core。注意:kill -11(SIGSEGV)也能触发,但可能掩盖真实死锁上下文。
常见误操作:
- 只运行
ulimit -c unlimited,却没检查/proc/<pid>/limits</pid>中Max core file size是否真为unlimited(子进程可能继承父 shell 限制) - 忽略
/proc/sys/kernel/core_pattern配置,导致 core 文件生成在不可见路径(如/var/lib/systemd/coredump/),你以为没生成 - 对 setuid 程序(如
sudo、抓包工具)未启用fs.suid_dumpable=2,系统会静默拒绝生成 core
gdb 加载 core 后,先确认是不是真死锁
启动 gdb 并加载符号文件和 core:gdb ./myapp core-myapp-1234-1726828800。进 gdb 后别急着 bt,先执行:
-
info threads—— 查看所有线程状态;死锁典型表现是多个线程都卡在__lll_lock_wait、pthread_mutex_lock、futex_wait或sem_wait -
thread apply all bt—— 对每个线程打一次 backtrace,快速扫一遍栈顶函数;重点关注哪些线程停在锁相关调用上 -
info registers和x/10i $pc—— 查看当前线程指令指针指向哪条汇编指令(即“卡在哪条指令”),但注意:这行指令本身不“导致”死锁,它只是锁等待的入口点
真正要定位的是“谁持有了 A 锁却在等 B 锁,而另一个线程持有了 B 锁却在等 A 锁”。bt 只能告诉你“停在哪”,不能自动画出锁依赖图——得靠人工比对各线程的栈帧中锁变量地址和调用顺序。
从汇编指令反推源码行号,需要符号表完整
如果 gdb 里 bt 显示的是 ?? 或只有地址(如 #0 0x00007f... in ?? ()),说明缺少调试符号。此时 x/10i $pc 看到的汇编毫无意义,因为你无法映射回 C++ 源码中的具体行或变量名。
- 编译时必须加
-g(且避免 strip);生产环境可把.debug段分离存档,core 分析时用set debug-file-directory指向它 - 若用
gcc -O2编译,内联和寄存器优化可能导致bt不完整或变量值失真;关键诊断场景建议保留-O0 -g构建版本 - 对 Rust/C++ 模板-heavy 代码,
bt full可能输出超长栈帧;优先用frame 2切到疑似加锁前的帧,再info args和print mutex_ptr查锁对象地址
单条指令无法“看出死锁”,要看多线程上下文交叉关系
死锁不是某一条指令的问题,而是多个线程在不同时间点对多个锁的获取顺序冲突。即使你用 disassemble 精确定位到 call pthread_mutex_lock@plt 这条指令,它本身完全合法——问题出在之前是否已持有另一把锁、以及别的线程此刻是否正持有它。
实际排查中最容易被忽略的点:
- 没检查
info proc mappings确认所有共享库(尤其是 libc、libstdc++)的加载基址是否匹配符号文件,否则list或info line会错位 - 误以为
bt最顶层就是“崩溃点”,其实死锁中所有线程的顶层都是锁等待函数,关键在倒数第二、三层:比如线程1栈是func_a → lock_B → lock_A,线程2是func_b → lock_A → lock_B - 忘记
set follow-fork-mode child(如果主进程 fork 后子进程死锁,你 attach 的可能是父进程)
最终能下结论的,永远不是某条汇编指令,而是多个线程栈帧中锁变量地址的交叉引用关系——这一步没法全自动,得人眼比对。











