异常引起的死锁

介绍

一个json异常导致的死锁分析。
同事某天求助帮忙分析一个死锁dump,按照之前查找临界区的方法,他已经具体定位到了某个线程,但是线程打印的堆栈信息却令他没有头绪。

准备工作

dump下载地址(提取码:ndel)

步骤

1

按照之前的方法
1)、~*kv 打印所有线程堆栈
2)、遍历线程内容,找到可疑点
3)、可疑线程为52、53、54……

2

通过查看临界区的OwningThread 找到线程 56
切换到~56 线程。

3

查看56线程堆栈(kv)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
0:056> kv
ChildEBP RetAddr Args to Child
08ccd8d4 76ddd846 00000000 00000000 00000000 user32!NtUserWaitMessage+0x15 (FPO: [0,0,0])
08ccd910 76ddda5c 06f400d2 00000000 00000000 user32!DialogBox2+0x222 (FPO: [Non-Fpo])
08ccd93c 76e0f7d0 76da0000 09f6fe90 00000000 user32!InternalDialogBox+0xe5 (FPO: [Non-Fpo])
08ccd9f0 76e0faac 00012010 00000000 ffffffff user32!SoftModalMessageBox+0x757 (FPO: [Non-Fpo])
08ccdb48 76e0fbaf 08ccdb54 00000028 00000000 user32!MessageBoxWorker+0x269 (FPO: [Non-Fpo])
08ccdbb4 76e0fc2e 00000000 0084acf8 09ee8d38 user32!MessageBoxTimeoutW+0x52 (FPO: [Non-Fpo])
08ccdbe8 76e0fd81 00000000 08ccdd84 0028f764 user32!MessageBoxTimeoutA+0x76 (FPO: [Non-Fpo])
08ccdc08 76e0fdc6 00000000 08ccdd84 0028f764 user32!MessageBoxExA+0x1b (FPO: [Non-Fpo])
*** WARNING: Unable to verify checksum for XYSoapClient.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for XYSoapClient.dll -
08ccdc24 0028cdd9 00000000 08ccdd84 0028f764 user32!MessageBoxA+0x18 (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
08ccde24 0028bf49 0000000a 00289e54 0028ba37 XYSoapClient!DllUnregisterServer+0x6bae
08ccde5c 769a03bb 08ccdf14 9616d04a 00000000 XYSoapClient!DllUnregisterServer+0x5d1e
08ccdee4 77d65be7 08ccdf14 77d65ac4 00000000 kernel32!UnhandledExceptionFilter+0x127 (FPO: [Non-Fpo])
08ccdeec 77d65ac4 00000000 08ccfe1c 77d1c620 ntdll!__RtlUserThreadStart+0x62 (FPO: [SEH])
08ccdf00 77d65951 00000000 00000000 00000000 ntdll!_EH4_CallFilterFunc+0x12 (FPO: [Uses EBP] [0,0,4])
08ccdf28 77d53529 fffffffe 08ccfe0c 08cce064 ntdll!_except_handler4+0x8e (FPO: [Non-Fpo])
08ccdf4c 77d534fb 08cce014 08ccfe0c 08cce064 ntdll!ExecuteHandler2+0x26 (FPO: [Uses EBP] [5,3,1])
08ccdf70 77d5349c 08cce014 08ccfe0c 08cce064 ntdll!ExecuteHandler+0x24 (FPO: [5,0,3])
08ccdffc 77d00143 01cce014 08cce064 08cce014 ntdll!RtlDispatchException+0x127 (FPO: [Non-Fpo])
08ccdffc 00000000 01cce014 08cce064 08cce014 ntdll!KiUserExceptionDispatcher+0xf (FPO: [2,0,0]) (CONTEXT @ 00000008)

疑惑

同事按照方法找到了导致死锁的线程,但从堆栈中却看不出原因,都是系统函数,有点无从下手。
从这个堆栈信息大致猜测是该线程触发了一个异常并弹出了异常框导致线程阻塞(由于是服务模式所以看不到这个异常框),但是锁资源却没有释放,从而导致了其他线程也出现了死锁。
接下来,我们需要从这个堆栈信息切换到异常发生时的堆栈信息。

切换异常上下文

函数原型

1
KiUserExceptionDispatcher( PEXCEPTION_RECORD pExcptRec, CONTEXT * pContext )

KiUserExceptionDispatcher 的第二个参数其实就是上下文地址

1
08ccdffc 00000000 01cce014 08cce064 08cce014 ntdll!KiUserExceptionDispatcher+0xf (FPO: [2,0,0]) (CONTEXT @ 00000008)

可以通过如下命令,切换上下文

1
.cxr 08cce064

最终得到的异常堆栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
0:056> .cxr 08cce064 
eax=08cce4c8 ebx=00792f90 ecx=00000003 edx=00000000 esi=00f3de44 edi=08cce570
eip=7767c54f esp=08cce4c8 ebp=08cce518 iopl=0 nv up ei pl nz ac po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212
KERNELBASE!RaiseException+0x58:
7767c54f c9 leave
0:056> kv
*** Stack trace for last set context - .thread/.cxr resets it
ChildEBP RetAddr Args to Child
08cce518 6f799339 e06d7363 00000001 00000003 KERNELBASE!RaiseException+0x58 (FPO: [Non-Fpo])
08cce558 00ee5f9b 08cce570 00f3de44 419ffac3 msvcr120!_CxxThrowException+0x5b (FPO: [Non-Fpo]) (CONV: stdcall) [f:\dd\vctools\crt\crtw32\eh\throw.cpp @ 152]
08cce5a0 00ee3852 08cce660 419ff9eb 00f1be7d gssssvr!Json::throwLogicError+0x7b
08cce688 00ee42c4 00f1be7c 00f1be83 00792f88 gssssvr!Json::Value::find+0x82
08cce6a0 00e1d5a5 00f1be7c 419fe277 09f03cd0 gssssvr!Json::Value::isMember+0x24
08ccfd14 00e1bc40 007c5b50 09f04018 0084eb60 gssssvr!CGameServer::OnSendSysMsgToServer+0x985 (FPO: [Non-Fpo]) (CONV: thiscall) [d:\program files (x86)\jenkins\workspace\publish_gssssvr\gssssvr\server.cpp @ 675]
08ccfd44 00ed9124 007c5b50 09f04018 00792f90 gssssvr!CGameServer::OnRequest+0x90 (FPO: [Non-Fpo]) (CONV: thiscall) [d:\program files (x86)\jenkins\workspace\publish_gssssvr\gssssvr\server.cpp @ 162]
08ccfd6c 00eccf1b 00000000 045f7f30 008295e8 gssssvr!CIocpWorker::DoWorkLoop+0xa4
08ccfd84 00ecceeb 08ccfdc4 6f7ac01d 00792f88 gssssvr!CBaseWorker::WorkerThreadProc+0x2b
08ccfd8c 6f7ac01d 00792f88 961623f0 00000000 gssssvr!CBaseWorker::WorkerThreadFunc+0xb
08ccfdc4 6f7ac001 00000000 08ccfddc 7696336a msvcr120!_callthreadstartex+0x1b (FPO: [Non-Fpo]) (CONV: cdecl) [f:\dd\vctools\crt\crtw32\startup\threadex.c @ 376]
08ccfdd0 7696336a 008295e8 08ccfe1c 77d29902 msvcr120!_threadstartex+0x7c (FPO: [Non-Fpo]) (CONV: stdcall) [f:\dd\vctools\crt\crtw32\startup\threadex.c @ 354]
08ccfddc 77d29902 008295e8 ab5803ad 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
08ccfe1c 77d298d5 6f7abfb4 008295e8 ffffffff ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])
08ccfe34 00000000 6f7abfb4 008295e8 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

这个堆栈信息就可以看清楚触发异常的具体代码,结合windbg的‘watch’‘local’功能查看变量信息,找到具体原因。