使用Windbg分析程序死锁小结

使用Windbg分析程序死锁小结

死锁场景描述: 
        针对之前一个版本反馈回来的问题,对数据通讯模块升级,做了精简和重构

        因为ABA问题的存在,将之前以Socket为key改为以只增的Int为key。使用的锁为临界区锁。

        修改完成,联调后进行压力测试,发现当后台的线程池满的时候会必然发生死锁。

死锁定位过程:

    第一步:精简线程模型,将授权检测线程、超时检测等辅助线程通通屏蔽。

    大压力测试,仍然死锁。

    第二步: 在第一步的基础上,将通讯工作线程减到1个。

    大压力测试,仍然死锁。

     因为涉及模块比较多,为了快速定位问题,使用windbg来检测死锁点

     第三步:使用windbg

      当应用程序挂死后,使用windbg Attach到进程。

      1. 使用 ~*kb,查看堆栈信息

        

[plain]  view plain  copy
  1. 8  Id: 368.bd4 Suspend: 1 Teb: 7ffd6000 Unfrozen ChildEBP RetAddr  Args to Child               0614fd4c 7c92df5a 7c939b23 00000604 00000000 ntdll!KiFastSystemCallRet 0614fd50 7c939b23 00000604 00000000 00000000 ntdll!NtWaitForSingleObject+0xc 0614fdd8 7c921046 0012fe08 5f401b5f 0012fe08 ntdll!RtlpWaitForCriticalSection+0x132 0614fde0 5f401b5f 0012fe08 0012fdfc 0614fedc ntdll!RtlEnterCriticalSection+0x46 0614fdf0 0040b207 ffffffff 77d2b326 03103ee0 MFC42D!CCriticalSection::Lock+0x14 0614fedc 5f438514 0012fd30 ffffffff 77d2b326 RecExtraction!CRecExtractionModule::ThreadAnalysis+0x185 [G:\Code\vc2012\VSIP\DigitalViewer_V1\RecExtraction\RecExtractionModule.cpp @ 331] 0614ff80 1020c323 0012f4f4 ffffffff 77d2b326 MFC42D!_AfxThreadEntry+0x2c4 0614ffb4 7c80b729 03103ee0 ffffffff 77d2b326 MSVCRTD!_beginthreadex+0x133 0614ffec 00000000 1020c2b0 03103ee0 00000000 kernel32!BaseThreadStart+0x37   

       其中,上图中的8是线程编号,368是进程ID,bd4是线程ID。
      可以看到ntdll!NtWaitForSingleObject ,没有拿到锁,说明死锁发生了。

[plain]  view plain  copy
  1. 0614fd4c 7c92df5a 7c939b23 00000604 00000000 ntdll!KiFastSystemCallRet  
  2. 0614fd50 7c939b23 00000604 00000000 00000000 ntdll!NtWaitForSingleObject+0xc  
  3. 0614fdd8 7c921046 0012fe08 5f401b5f 0012fe08 ntdll!RtlpWaitForCriticalSection+0x132  
  4. 这里的第一个是ebp,第二个是返回函数地址,接下来才是函数的参数。  

     要看下比如 ntdll!NtWaitForSingleObject参数定义:

[cpp]  view plain  copy
  1. NTSTATUS WINAPI NtWaitForSingleObject(   
  2.   _In_  HANDLE Handle,   
  3.   _In_  BOOLEAN Alertable,   
  4.   _In_  PLARGE_INTEGER Timeout   
  5. );   

    所以 00000604 00000000 00000000分别对应上面的Handle,Alrtable,Timeout.

2. 如果线程比较多,可以使用 !cs -l 仅显示死锁的临界区信息。3.找到了死锁的临界区Handle,就可以查找锁的持有者了。

    如上所述,handle是00000604,

[plain]  view plain  copy
  1. !cs 00000604       

再看一个网上的例子:

0:004> ~1kb
ChildEBP RetAddr  Args to Child              
0051fe48 7c92df5a 7c939b23 00000034 00000000 ntdll!KiFastSystemCallRet
0051fe4c 7c939b23 00000034 00000000 00000000 ntdll!NtWaitForSingleObject+0xc
0051fed4 7c921046 00417140 00411420 00417140 ntdll!RtlpWaitForCriticalSection+0x132
*** WARNING: Unable to verify checksum for D:\Project1\test2\Debug\test2.exe
0051fedc 00411420 00417140 00000000 00000000 ntdll!RtlEnterCriticalSection+0x46
0051ffb4 7c80b729 00000000 00000000 00000000 test2!thread1+0x50 [d:\project1\test2\test2\test2.cpp @ 10]
0051ffec 00000000 00411122 00000000 00000000 kernel32!BaseThreadStart+0x37
0:004> !cs 00417140
-----------------------------------------
Critical section   = 0x00417140 (test2!cs2+0x0)
DebugInfo          = 0x7c99ea00
LOCKED
LockCount          = 0x2
OwningThread       = 0x00001f60
RecursionCount     = 0x1
LockSemaphore      = 0x34
SpinCount          = 0x00000000

可以看出该临界区的持有者(OwningThread)是 0x00001f60

这个地址是线程在内核中的ID,需要转换为用户态的ID,使用~~[]命令。

[plain]  view plain  copy
  1. ~~[线程内核id]  

0:003> ~~[0x0000185c]
        2  Id: 1a98.185c Suspend: 1 Teb: 7ffdd000 Unfrozen
         Start: test2+0x1030 (00401030) 
         Priority class: 32; Affinity: f

通过转换,可以看到锁的持有者是2号线程。

这样就可以很明确的找到锁的持有者了,如果pdb加载正确,通过~2kb就已经可以对应到代码行了。

查找死锁很困难,但是一旦找到点,解决起来就容易多了。

以上的例子部分来自网贴,在此对原作者表示感谢。

---------------------------------

对于死锁的小结:

1. 对上层提供的函数,需要通过设计,规避操作带锁。

2.在锁中避免调用回调,因为回调的操作不可预知,易发生死锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值