利用glibc中锁结构的信息解决死锁问题

本文讲述了作者在遇到缓存服务器中由于获取哈希表读写锁导致的死锁问题,通过分析glibc源码,打印锁结构信息,最终定位到进程在获取读锁和写锁时的冲突。通过gdb调试发现,两个进程分别持有不同锁并尝试获取对方持有的锁,导致死锁。问题源于某函数在失败时不正确释放锁。
   首先非常感谢老丁和老李同学的帮助,没有他们这个问题估计又得搞很久。遇见这个问题,真是头疼。不熟悉代码、不熟悉流程,但是领导还是把活给排下来了(实在不解),只能硬着头皮找了。
问题是这样的,cache服务器中有一个存储对象的哈希表,每次访问哈希表时都要获取hash_rwlock读写锁,现在进程在获取读锁时死锁。使用gdb进入3个worker进程,发现死锁的位置都一样,都是在获取hash_rwlock读锁时阻塞住了。遇见这样的问题,加上对代码不熟,真是各种犯二。因为reload的关系,进程其实总共有9个,但是3个是一组的,所以只看了其中的一组,没有全部查看,如果全部查看的话,估计可以更早发现问题。
在同事的提醒下,先在阻塞的位置打印锁的结构,如下所示:

找了glibc的源码来看,发现pthread_rwlock_t结构的成员竟然完全没有注释,只好去看加锁、解锁的函数。在获取读锁时,只有在__writer成员为0并且__nr_writers_queued为0,即没有进程获取写锁或等待获取写锁时,才会获取锁成功。获取读锁成功后,会将__nr_readers成员加1.这么看来的话,__nr_readers存储的是当前获取读锁的进程或线程数量。在获取写锁时,只有在__writer成员为0,并且__nr_readers为0,即没有进程或线程获取写锁或读锁时,才会获取写锁成功。获取写锁成功后,会将__writer成员设置为线程ID(如果是单进程,则为进程ID),如下所示:
rwlock->__data.__
<think>好的,用户想了解如何使用GDB分析由锁问题导致的核心转储文件,特别是因锁竞争或死锁引起的情况。我需要结合之前的讨论和提供的引用来构建一个清晰的步骤指南。 首先,用户之前的问题是关于堆栈崩溃的分析,其中涉及到了递归锁解锁超调导致的问题,现在他们想进一步了解如何调试这类问题。引用中提到了生成core文件的方法和用GDB加载core文件的命令,比如gdb -q dump core_*,这可能对回答有帮助。 我需要考虑用户可能的实际场景:他们可能在开发多线程应用时遇到了死锁或锁竞争,导致程序崩溃,并生成了core文件。用户需要知道如何利用GDB分析这个core文件,找出问题根源。 接下来,用户的需求是明确的,但可能需要更详细的步骤,例如如何检查线程状态、查看锁信息、回溯堆栈等。需要确保回答结构清晰,分步骤说明,并包含实际命令示例,方便用户操作。 还要注意引用之前的回答中提到的预防措施,比如使用PTHREAD_MUTEX_ERRORCHECK,可能在调试过程中也会用到。此外,用户可能对如何检测死锁或锁状态的具体方法不太清楚,需要详细解释GDB中的相关命令,如info threads,thread apply all bt,以及如何检查互斥锁的状态。 需要结合引用[3]中提到的GDB性能分析的方法,比如设置断点、查看变量值等,但这里更侧重于锁问题的调试。可能还需要提到检查锁的持有者、等待队列等信息,这需要了解glibc的锁实现或特定系统的调试支持。 另外,用户可能对如何复现问题或捕获core文件的条件不太了解,需要简要提及生成core文件的配置,如ulimit -c unlimited,但引用[1]和[2]已经提到过,可以适当引用。 需要确保回答覆盖以下关键点: 1. 加载core文件和可执行文件到GDB。 2. 检查所有线程的堆栈跟踪。 3. 查看锁的状态和持有者信息。 4. 分析死锁的可能性,如循环等待。 5. 使用GDB命令检查内存和变量状态。 6. 结合之前的预防措施,如错误检查锁类型。 可能还需要提到一些高级技巧,比如查找锁地址、检查锁的计数器(对于递归锁)等。此外,引用[3]中的调试符号和编译选项很重要,要提醒用户编译时加上-g选项。 要避免过于技术化的术语,但保持专业性,确保步骤明确,用户能逐步跟随操作。同时,注意引用之前的引用内容,如引用[2]中的GDB命令示例,可以整合到回答中。 最后,考虑用户可能的后续问题,比如如何处理没有调试符号的情况,或者如何预防此类问题,但当前问题聚焦在分析core文件,所以需集中在调试步骤上。</think>#### **使用 GDB 分析锁问题导致的核心转储文件** --- ### **一、准备工作** 1. **生成调试符号**: 编译代码时需添加 `-g` 选项以保留调试信息,例如: ```bash gcc -g -pthread your_program.c -o your_program ``` 2. **加载核心转储文件**: 使用 GDB 加载可执行文件与核心转储文件(假设核心文件为 `core.1234`): ```bash gdb -q your_program core.1234 ``` 若核心文件路径不在当前目录,需指定完整路径: ```bash gdb -q your_program /path/to/core.1234 ``` --- ### **二、关键分析步骤** #### **1. 查看崩溃时的线程状态** - **列出所有线程**: ```gdb (gdb) info threads ``` 输出示例: ``` Id Target Id Frame * 1 Thread 0x7f... pthread_mutex_lock () at pthread_mutex_lock.c:67 2 Thread 0x7f... __lll_lock_wait () at lowlevellock.c:52 ``` **重点关注**: - 处于 `pthread_mutex_lock` 或 `__lll_lock_wait` 的线程(可能正在等待锁) - 调用 `abort()` 或 `malloc_printerr` 的线程(可能因锁状态损坏触发崩溃) - **切换线程并查看堆栈**: ```gdb (gdb) thread 2 # 切换到线程2 (gdb) bt # 打印完整堆栈回溯 ``` #### **2. 分析锁状态** - **检查锁的持有者**(需调试符号支持): 若锁结构为 `pthread_mutex_t`,可通过 GDB 查看其内部字段: ```gdb (gdb) p mutex_variable ``` 输出示例: ``` $1 = {__data = {__lock = 2, __count = 0, __owner = 1234, ...}, ...} ``` **关键字段**: - `__lock`:锁的当前状态(0=未锁定,1=锁定,2=等待队列非空) - `__owner`:持有锁的线程 ID(对应 `info threads` 中的线程 ID) - `__count`:递归锁的计数器(仅对 `PTHREAD_MUTEX_RECURSIVE` 有效) - **定位死锁**: 若多个线程互相等待对方持有的锁,可通过以下方法验证: 1. 检查每个阻塞线程的堆栈,确认其等待的锁地址(如 `mutex@0x6010a0`)。 2. 查找这些锁的持有者线程: ```gdb (gdb) p *(pthread_mutex_t*)0x6010a0 ``` 3. 若线程A持有锁L1并等待L2,线程B持有锁L2并等待L1,则构成死锁。 #### **3. 检查锁操作历史** - **反汇编与寄存器分析**: 若堆栈显示崩溃发生在 `pthread_mutex_unlock`,检查调用时的参数: ```gdb (gdb) disassemble /m # 反汇编当前函数 (gdb) info registers # 查看寄存器值(锁地址通常保存在寄存器中) (gdb) x/10a $rsi # 查看锁内存内容 ``` --- ### **三、典型案例分析** #### **场景1:解锁次数超过加锁次数(递归锁)** - **现象**: 崩溃堆栈指向 `pthread_mutex_unlock`,且锁计数器 `__count` 为负数。 - **调试步骤**: 1. 查找所有调用 `pthread_mutex_unlock` 的代码路径,确认是否严格配对。 2. 使用条件断点跟踪锁操作: ```gdb (gdb) break your_code.c:100 if mutex.__count == 0 # 在可能超调的位置设断点 ``` #### **场景2:死锁(多线程互相等待)** - **现象**: 多个线程阻塞在 `pthread_mutex_lock`,且持有的锁形成循环依赖。 - **调试步骤**: 1. 通过 `info threads` 和 `bt` 列出所有线程的等待锁地址。 2. 绘制锁依赖图,确认是否存在循环等待。 --- ### **四、高级调试技巧** 1. **启用锁调试功能**: 编译时添加 `-D_POSIX_THREADS` 和 `-D_GNU_SOURCE`,使用 `PTHREAD_MUTEX_ERRORCHECK` 类型锁: ```c pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); ``` 此类锁会在解锁非持有锁时立即返回 `EPERM` 错误[^1]。 2. **动态修改锁状态**: 在 GDB 中强制释放锁(仅用于测试): ```gdb (gdb) set mutex_variable.__lock = 0 # 重置锁状态(谨慎使用!) ``` --- ### **五、对比表:常见锁问题与调试线索** | **问题类型** | **GDB 调试线索** | **关键命令** | |-----------------------|-------------------------------------------|----------------------------------| | 死锁 | 多个线程阻塞在 `pthread_mutex_lock` | `info threads`, `bt`, `p mutex` | | 双重释放 | 崩溃堆栈包含 `malloc_printerr` | `x/10a [锁地址]` | | 递归锁超调 | 锁计数器 `__count` 为负数 | `p mutex.__count` | --- #### **相关问题** 1. 如何通过 `gdb` 动态修改线程状态以绕过死锁? 2. 在多线程程序中如何捕获未处理的 `EPERM` 错误? 3. `glibc` 中 `pthread_mutex_t` 的内存结构如何解析? --- **参考资料** [^1]: `PTHREAD_MUTEX_ERRORCHECK` 的错误检测机制 [^2]: GDB 核心转储分析流程与线程状态检查 [^3]: 递归锁计数器的实现与超调风险
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值