用 gdb -p pid 附加卡死进程后执行 thread apply all bt 可导出所有线程调用栈;若无源码行号需检查编译是否加 -g 且未 strip;配合 info threads 和 cat /proc/pid/stack 可快速定位锁阻塞点。

用 gdb 附加运行中卡死进程并导出所有线程调用栈
程序卡在死锁时通常不崩溃,也不会自动生成 core 文件,这时必须手动介入。最直接有效的方式是用 gdb 附加到正在运行的进程,查看每个线程当前阻塞在哪一行代码、持有哪些锁、等待哪些锁。
常见错误现象:执行 gdb ./myapp PID 报错 “Permission denied” 或 “No such process” —— 原因通常是权限不足(非 root 用户无法附加到其他用户的进程),或进程已退出(但实际只是卡住,ps aux | grep myapp 仍能查到)。
- 先确认进程存活:
ps aux | grep myapp,记下 PID - 确保你有权限附加:如果是自己启动的进程,且没切用户,一般没问题;否则加
sudo - 运行:
gdb -p PID(注意不是gdb ./myapp PID) - 进 gdb 后立即执行:
thread apply all bt—— 这是关键命令,它会打印所有线程的完整调用栈 - 若需保存到文件,先在 gdb 中执行:
set logging on,再运行thread apply all bt,最后set logging off,日志默认写入gdb.txt
为什么 bt 看不到源码行号?检查编译是否带 -g 和符号表是否被 strip
如果 thread apply all bt 输出全是 ?? 或只有函数名没有文件/行号,说明调试信息缺失。这不是 gdb 的问题,而是构建环节没保留符号。
典型表现:#0 0x00007f... in __pthread_mutex_lock () from /lib64/libpthread.so.0 —— 这只能告诉你卡在 pthread 锁上,但不知道是哪行 C++ 代码调用了 lock_guard 或 mtx.lock()。
- 编译时必须加
-g:例如g++ -g -std=c++11 -pthread main.cpp -o myapp - 上线前别用
strip myapp,否则符号全丢;如需减小体积,可用objcopy --strip-debug myapp仅剥离调试段,保留符号表 - 验证符号是否存在:
nm -C myapp | grep mtx1(假设你有个叫mtx1的全局互斥量),有输出说明符号还在
用 info threads 和 thread <n></n> 快速定位“静默等待”的线程
死锁中往往有 2–3 个线程处于 pthread_mutex_lock 阻塞状态,但它们在 thread apply all bt 里混在几十行堆栈中容易被忽略。需要人工筛选真正“停住”的线程。
使用场景:当 thread apply all bt 输出太长,想快速聚焦——哪些线程卡在锁函数里,哪些还在干活。
- 在 gdb 中先执行:
info threads,看线程列表和状态(pthread_mutex_lock附近通常标为Blocked或无明显运行态) - 对可疑线程逐个切换:
thread 2(数字是info threads显示的 ID),再执行bt - 重点观察栈顶是否为:
__pthread_mutex_lock、__lll_lock_wait或std::mutex::lock()—— 这说明它正等着拿锁,没超时、没返回 - 配合
frame 2(或更高层帧)往上翻,找到你自己的函数调用点,这才是死锁源头
不依赖 gdb:用 cat /proc/PID/stack 快速抓内核态阻塞点
这个方法不能替代 gdb,但它零依赖、秒出结果,适合在容器或受限环境里快速初筛。它只显示内核态调用栈,所以看不到 C++ 源码,但能确认是否真卡在锁上。
性能影响几乎为零,也不需要调试符号,只要进程没被 ptrace 阻止(默认允许)。
- 执行:
cat /proc/PID/stack,会看到类似: [<ffffffff810a1234>] __mutex_lock_slowpath+0x123/0x250</ffffffff810a1234>[<ffffffff810a10ab>] mutex_lock+0x2b/0x40</ffffffff810a10ab>- 出现
__mutex_lock_slowpath或__lll_lock_wait就基本坐实是用户态互斥锁阻塞 - 注意:该文件只对 root 或进程所有者可读;普通用户需确保
/proc/sys/kernel/yama/ptrace_scope≤ 1(多数发行版默认为 0)
真正难的不是导出调用栈,而是从几十行 pthread 底层帧里准确识别出哪两个线程在争哪两把锁——这要求你对代码里所有 std::mutex、std::shared_mutex、甚至 pthread_mutex_t 的生命周期和调用顺序心里有数。符号表不丢、锁命名不模糊、获取顺序不随意,比任何调试技巧都管用。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











