Windows调试——死锁的查找

来源:互联网 发布:供应链网络优化 编辑:程序博客网 时间:2024/06/11 16:10

首先看看什么是死锁呢?死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去;如果一个线程中等待另一个线程的信号,而该线程被TerminateThread杀死后,也会造成此问题。


工具使用WinDbg。在新版本的windbg中可能部分指令不支持。需要使用的话可以切换版本到dbg_x86_6.10.3.233.msi。
如果有如下提示
NTSDEXTS: Unable to resolve ntdll!RtlCriticalSectionList
NTSDEXTS: Please check your symbols
解决方法:下载symbols
在File=>Symbol Path File 输入SRV*C:\Windows Sysmbols* http://msdl.microsoft.com/download/symbols
然后在命令框中输入.reload


1) 先用!locks查看所有的线程占用的锁 

这里可以看到有三个线程正在等待三个锁,第一个线程等待的锁是0043a620,但被5e4这条线程占用,第二个线程等待的锁是0043a844,但被5dc线程占用,第三个线程等待的锁是031d40d4,也被5e4线程占用。

2)接着,我们需要查看5e4线程和5dc线程的id,具体可以通过查看工具栏中Processes and Threads,如下图所示,5e4的线程的id为53,5dc线程的id为51。



3)分别输入~53kb和~51kb查看这个两个线程的调用栈,结果如下图所示


由数据可知,5e4线程正在等待一把0043a844的锁,而5dc线程也正在等待锁0043a620。

4)结合第一步获取的信息可知,5e4线程要去获取已经被5dc占用的锁0043a844,而5dc又要去获取已经被5e4占用的锁0043a620,如此形成环路,就产生了死锁 


当!locks出现问题的时候 
情况一 
ChildEBP RetAddr Args to Child 
065d5730 7c92df5a 7c939b23 000008e0 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0]) 
065d5734 7c939b23 000008e0 00000000 00000000 ntdll!NtWaitForSingleObject+0xc (FPO: [3,0,0]) 
065d57bc 7c921046 0079f570 7d5bd275 7d79f570 ntdll!RtlpWaitForCriticalSection+0x132 (FPO: [1,26,4]) 
065d57c4 7d5bd275 7d79f570 05cc7bc5 065d5814 ntdll!RtlEnterCriticalSection+0x46 (FPO: [1,0,0]) 
WARNING: Stack unwind information not available. Following frames may be wrong. 
065d57d8 7d5bd238 7d5980f4 05cc7bc5 05ccf050 SHELL32!ILFindLastID+0x409 
065d57f4 7d5bd833 00000000 00000000 05cc7bc5 SHELL32!ILFindLastID+0x3cc 
065d5a40 7d5bd8de 00000000 05cc7c69 065d5a7c SHELL32!ILFindLastID+0x9c7 
065d5c98 7d5bdb02 00000000 05cc7bc5 7d597034 SHELL32!ILFindLastID+0xa72 
065d5cc0 7d5bd5e2 00000000 05cc7bc5 00000000 SHELL32!ILFindLastID+0xc96 
065d5ce8 7d5bd7c2 05cbc240 05cc8340 00000000 SHELL32!ILFindLastID+0x776 
065d5d0c 7d5bb846 05cc0800 05cc7bac 00000000 SHELL32!ILFindLastID+0x956 
065d5d38 7d5bb89f 05cc7b98 00000000 7d597034 SHELL32!ILCombine+0x241 
065d5d5c 7d5bbd12 04506770 05cc7b98 00000000 SHELL32!ILCombine+0x29a 
065d5d80 7d5bc6f7 04506770 05cc7b98 00000000 SHELL32!SHGetDesktopFolder+0xba 
065d5da4 7d5bc73c 00000000 05cc72e0 7d597034 SHELL32!ILRemoveLastID+0x50 
065d5dc0 7d5c10ac 05cc72e0 7d597034 065d5df0 SHELL32!ILRemoveLastID+0x95 
065d6010 7d5c1052 05cc72e0 065d79bc 00000002 SHELL32!SHGetPathFromIDListW+0x6c 
065d6024 04f56f03 05cc72e0 065d79bc c54b6ba4 SHELL32!SHGetPathFromIDListW+0x12 
分析如下: 
0:009> !locks 
NTSDEXTS: Unable to resolve ntdll!RTL_CRITICAL_SECTION_DEBUG type 
NTSDEXTS: Please check your symbols 
0:009> dt RTL_CRITICAL_SECTION 7d79f570 
XXXXX!RTL_CRITICAL_SECTION 
+0x000 DebugInfo : 0x00153ab8 _RTL_CRITICAL_SECTION_DEBUG 
+0x004 LockCount : 1 
+0x008 RecursionCount : 1 
+0x00c OwningThread : 0x00000964 
+0x010 LockSemaphore : 0x000008e0 
+0x014 SpinCount : 0 
看来是0x964线程持有锁,对应查看具体的线程 


情况二 



1 Id: 1588.1730 Suspend: 0 Teb: 7ffdd000 Unfrozen 
ChildEBP RetAddr Args to Child 
WARNING: Stack unwind information not available. Following frames may be wrong. 
009ffcd4 7759215c 00000000 00000000 00000000 ntdll!KiFastSystemCallRet 
009ffcfc 012d101d 012d3370 00000000 76d3ee1c ntdll!EtwEventEnabled+0xd9 
009ffd08 76d3ee1c 00000000 009ffd54 775c37eb lock_test!thread1+0x1d (FPO: [1,0,1]) (CONV: stdcall) [d:\技术总结\lock_test\lock_test\lock_test.cpp @ 16] 
009ffd14 775c37eb 00000000 76a98121 00000000 kernel32!BaseThreadInitThunk+0x12 
009ffd54 775c37be 012d1000 00000000 00000000 ntdll!RtlInitializeExceptionChain+0xef 
009ffd6c 00000000 012d1000 00000000 00000000 ntdll!RtlInitializeExceptionChain+0xc2

2 Id: 1588.754 Suspend: 0 Teb: 7ffdc000 Unfrozen 
ChildEBP RetAddr Args to Child 
WARNING: Stack unwind information not available. Following frames may be wrong. 
0073fed4 7759215c 00000000 00000000 00000000 ntdll!KiFastSystemCallRet 
0073fefc 012d104d 012d3388 00000000 76d3ee1c ntdll!EtwEventEnabled+0xd9 
0073ff08 76d3ee1c 00000000 0073ff54 775c37eb lock_test!thread2+0x1d (FPO: [1,0,1]) (CONV: stdcall) [d:\技术总结\lock_test\lock_test\lock_test.cpp @ 25] 
0073ff14 775c37eb 00000000 76458321 00000000 kernel32!BaseThreadInitThunk+0x12 
0073ff54 775c37be 012d1030 00000000 00000000 ntdll!RtlInitializeExceptionChain+0xef 
0073ff6c 00000000 012d1030 00000000 00000000 ntdll!RtlInitializeExceptionChain+0xc2

0:000> uf 012d3388 :显示函数的反汇编代码 
Flow analysis was incomplete, some code may be missing 
lock_test!cs1: 
012d3388 a04f3500fa mov al,byte ptr ds:[FA00354Fh]

0:000> dt RTL_CRITICAL_SECTION 012d3388 
lock_test!RTL_CRITICAL_SECTION 
+0x000 DebugInfo : 0x00354fa0 _RTL_CRITICAL_SECTION_DEBUG 
+0x004 LockCount : -6 
+0x008 RecursionCount : 1 
+0x00c OwningThread : 0x00001730 
+0x010 LockSemaphore : 0x0000003c 
+0x014 SpinCount : 0

  
2) 
0:000> uf 012d3370 显示函数的反汇编代码 
Flow analysis was incomplete, some code may be missing 
lock_test!cs2: 
012d3370 c84f3500 enter 354Fh,0 
012d3374 fa cli

0:000> dt RTL_CRITICAL_SECTION 012d3370 
lock_test!RTL_CRITICAL_SECTION 
+0x000 DebugInfo : 0x00354fc8 _RTL_CRITICAL_SECTION_DEBUG 
+0x004 LockCount : -6 
+0x008 RecursionCount : 1 
+0x00c OwningThread : 0x00000754 
+0x010 LockSemaphore : 0x00000040 
+0x014 SpinCount : 0

情况三 
最近跟踪了一个程序的界面卡死问题,用windbg载入dump文件后打印出函数调用堆栈后,一眼可以看出是临界区死锁了。 
代码: 
0:000:x86> kb 
ChildEBP RetAddr Args to Child 
0032dd0c 779ed993 00000710 00000000 00000000 ntdll_779b0000!NtWaitForSingleObject+0x15 
0032dd70 779ed877 00000000 00000000 024023f0 ntdll_779b0000!RtlpWaitOnCriticalSection+0x13e 
0032dd98 58a2fac3 02404c50 856fd57e 024023f0 ntdll_779b0000!RtlEnterCriticalSection+0x150 
0032dffc 58a0d4d7 856fea8a 00000000 001c41a0 SogouSoftware_589d0000!CDownloadListUI::UpdateDownloadListUI+0x43 
输出该临界区的信息: 
代码:

0:000:x86> !cs 02404c50

Critical section = 0x0000000002404c50 (+0x2404C50) 
DebugInfo = 0x0000000000611e08 
LOCKED 
LockCount = 0xFFFFFFFF 
WaiterWoken = Yes 
OwningThread = 0x0000000000000710 
RecursionCount = 0x1A38 
LockSemaphore = 0x2433B08 
SpinCount = 0x0000000000000000 
Windbg指示是0x710号线程占有该临界区在,于是看看0x710号线程是那一个,结果发现报错,一般这种情况是线程已经退出或者被终止掉了。 
代码: 
0:000:x86> [710] 
^ Illegal thread error in '
[710]’ 
该临界区死锁的位置部分代码如下: 
代码: 
void CDownloadListUI::UpdateDownloadListUI() 

m_vctLock.Lock(); 
vector vecDeleteItems(GetCount()); // record index to be delete 
std::iota(vecDeleteItems.begin(), vecDeleteItems.end(), 0); 

m_vctLock是ATL一个对临界区简单封装的类,仔细对m_vctLock所有加锁的位置进行检查,发现除了主线程外只有另外一个工作线程会使用,用windbg查了下改工作线程并未退出,线程ID也不为 0x710,难道又被Windbg给忽悠了?打印下该临界区的数据结构看看: 
代码: 
0:000:x86> dt _RTL_CRITICAL_SECTION 02404c50 
DuiLib!_RTL_CRITICAL_SECTION 
+0x000 DebugInfo : 0x00611e08 _RTL_CRITICAL_SECTION_DEBUG 
+0x004 LockCount : 0n-6 
+0x008 RecursionCount : 0n1 
+0x00c OwningThread : 0x00001a38 Void 
+0x010 LockSemaphore : 0x00000710 Void 
+0x014 SpinCount : 0 
发现这里显示的拥有者线程号和上面不一致,试试看是那个线程: 
代码: 
0:000:x86> ~~[1a38] 
6 Id: 2058.1a38 Suspend: 0 Teb: 7ef94000 Unfrozen 
Start: SogouSoftware_589d0000!_threadstartex (58a5192d) 
Priority: 0 Priority class: 32 Affinity: f 
0:000:x86> ~6s 
ntdll_779b0000!ZwWaitForMultipleObjects+0x15: 
779d019d 83c404 add esp,4 
0:006:x86> kb 
ChildEBP RetAddr Args to Child 
0370fa5c 768615f7 00000002 0370faac 00000001 ntdll_779b0000!ZwWaitForMultipleObjects+0x15 
0370faf8 773519f8 0370faac 0370fb20 00000000 KERNELBASE!WaitForMultipleObjectsEx+0x100 
0370fb40 773541d8 00000002 7efde000 00000000 kernel32!WaitForMultipleObjectsExImplementation+0xe0 
0370fb5c 589f6ba0 00000002 0370fb84 00000000 kernel32!WaitForMultipleObjects+0x18 
0370fbd4 58a51907 58aab894 862df68e 00000000 SogouSoftware_589d0000!CThreadQueue::ThreadProc+0x100 
0370fc0c 58a51991 00000000 0370fc24 7735336a SogouSoftware_589d0000!_callthreadstartex+0x1b [f:\dd\vctools\crt_bld\self_x86\crt\src\threadex.c @ 314] 
0370fc18 7735336a 023f5170 0370fc64 779e9882 SogouSoftware_589d0000!_threadstartex+0x64 [f:\dd\vctools\crt_bld\self_x86\crt\src\threadex.c @ 292] 
0370fc24 779e9882 023f5170 771cc6bb 00000000 kernel32!BaseThreadInitThunk+0xe 
0370fc64 779e9855 58a5192d 023f5170 00000000 ntdll_779b0000!__RtlUserThreadStart+0x70 
0370fc7c 00000000 58a5192d 023f5170 00000000 ntdll_779b0000!_RtlUserThreadStart+0x1b 
6号线程正处于等待任务中,对照代码,6号线程有一个观察者的回调函数会调用到CDownloadListUI类中的m_vctLock锁,但是该回调函数已经执行完毕了那么只有一种可能,就是锁泄露了,即Lock后没有解锁。再去看该回调函数,果然发现有一个很少出现的分支下没有调用Unlock释放临界区而直接返回了,这就造成了经典的锁泄露而引起死锁的bug。 
但 !cs 命令在此种情况下却给出了错误的信息,很容易造成误导而怀疑是一个拥有该锁的线程退出了而引起的。这应该算是Windbg的一个bug吧。 
进一步测试该情况,发现是当64位系统下的32位进程产生的dump会有此问题,32位系统下 !cs 命令执行正常。