跟踪多进程死锁
我最近处理的一个棘手的死锁问题开始。让我先解释一下表面症状,然后我们就可以找到好东西了。当运行Vista的用户尝试启动internetexplorer时,任何事情都不会发生。所以他们再次双击图标,还是什么也没发生。所以用户会重新启动并返回他们的网络活动。
经过一些基本的故障排除后发现Iexplore.exe正在运行多次,每次尝试打开浏览器都会导致另一个Iexplore.exe实例。因为没有用户界面,所以我继续收集了一些Iexplore.exe进程转储以及完整的系统转储。
进程转储没有被证明是非常有用的,所以我决定转到完整的内存转储。
因为我知道我们有多个IExplore.exe运行的进程,我想先调查一下。
0: kd>!process 0 0 <snip> PROCESS 87289d90 SessionId: 5 Cid: 0fdc Peb: 7ffdc000 ParentCid: 0968 DirBase: 7d66c520 ObjectTable: a203a178 HandleCount: 205. Image: iexplore.exe PROCESS 861a9d90 SessionId: 5 Cid: 0d34 Peb: 7ffd9000 ParentCid: 0fdc DirBase: 7d66c7a0 ObjectTable: a5651a98 HandleCount: 170. Image: ieuser.exe PROCESS 86c175b0 SessionId: 5 Cid: 1250 Peb: 7ffd9000 ParentCid: 0968 DirBase: 7d66c940 ObjectTable: a0853f10 HandleCount: 91. Image: iexplore.exe PROCESS 861ac7a8 SessionId: 5 Cid: 1024 Peb: 7ffde000 ParentCid: 0968 DirBase: 7d66c620 ObjectTable: a83f7898 HandleCount: 91. Image: iexplore.exe </snip>
我决定从列表中的第一个开始(87289d90)
0: kd> !process 87289d90 Image: iexplore.exe ElapsedTime 00:24:06.666
这个过程似乎已经有一段时间了,所以我想看看这个过程中的第一个线程,因为它负责internetexplorer中的UI。我看这个线程是因为表面问题说明当我们启动时Iexplore.exe用户界面从不显示。我在这里用了!thread显示特定线程的命令。
THREAD 870b6398 Cid 0fdc.16a4 Teb: 7ffdf000 Win32Thread: fd850b78 WAIT: (UserRequest) UserMode Non-Alertable 85e86cf0 SynchronizationEvent 85cb35e0 SynchronizationEvent Not impersonating Attached Process 87289d90 Image: iexplore.exe Wait Start TickCount 10817197 Ticks: 69571 (0:00:18:05.314) UserTime 00:00:00.015 KernelTime 00:00:00.093
根据滴答数,这个线程已经等了18分钟了。
0: kd> kvn # ChildEBP RetAddr Args to Child 00 931208d8 81a940a1 870b6398 803d2120 870b6420 nt!KiSwapContext+0x26 01 9312091c 81a8dacc 870b6398 00000000 00000002 nt!KiSwapThread+0x44f 02 93120970 81bedb4f 00000002 93120aa8 00000001 nt!KeWaitForMultipleObjects+0x53d 03 93120bfc 81bee2e3 00000002 00000001 00000000 nt!ObpWaitForMultipleObjects+0x256 04 93120d48 81a30b3a 00000002 0027e9c4 00000001 nt!NtWaitForMultipleObjects+0xcc 05 93120d48 77568254 00000002 0027e9c4 00000001 nt!KiFastCallEntry+0x12a 06 0027e970 775679fc 7708c973 00000002 0027e9c4 ntdll!KiFastSystemCallRet 07 0027e974 7708c973 00000002 0027e9c4 00000001 ntdll!NtWaitForMultipleObjects+0xc 08 0027ea10 7608ff95 0027e9c4 0027ea38 00000000 kernel32!WaitForMultipleObjectsEx+0x11d 09 0027ea64 773d3a46 00000028 0027eaac ffffffff USER32!RealMsgWaitForMultipleObjectsEx+0x13c 0a 0027ea8c 773d3b7f 0027eaac ffffffff 0027eabc ole32!CCliModalLoop::BlockFn+0x97 0b 0027eab4 774e75be ffffffff 003ddf90 0027ebc0 ole32!ModalLoop+0x5b 0c 0027ead0 774e9294 00000000 0027ebd4 00000000 ole32!ThreadSendReceive+0x12c 0d 0027eaf8 774e9100 0027ebc0 003d3538 0027ec1c ole32!CRpcChannelBuffer::SwitchAptAndDispatchCall+0x194 0e 0027ebd8 773d3c25 003d3538 0027ecfc 0027ece4 ole32!CRpcChannelBuffer::SendReceive2+0xef 0f 0027ebf4 773d3bd7 0027ecfc 0027ece4 003d3538 ole32!CCliModalLoop::SendReceive+0x1e 10 0027ec6c 77403efb 003d3538 0027ecfc 0027ece4 ole32!CAptRpcChnl::SendReceive+0x73 11 0027ecc0 76052ff4 003d3538 0027ecfc 0027ece4 ole32!CCtxComChnl::SendReceive+0x1c5 12 0027ecd8 76053055 003e50a4 0027ede4 76052e0d RPCRT4!NdrProxySendReceive+0x43 13 0027ece4 76052e0d 09ea0d59 0027f1fc 070001f3 RPCRT4!NdrpProxySendReceive+0xc
根据调用堆栈,这个线程似乎进行了OLE调用,现在正在等待一些对象发出信号。IExplore在这个线程中做什么?它正在等待另一个进程为该OLE请求提供服务。通过深入研究ole32 SendReceive调用,我们可以找到另一个进程的PID。
我看了第一个点:
0: kd> dd 003d3538 003d3538 77403a50 773f57b4 00000003 00000002 003d3548 00000000 00000000 003d2ef0 003d8840 003d3558 0038b1c0 003e05f8 77403980 00070005
这为我们提供了一个指向另一个数据结构的指针,其中包含我们正在尝试定位的信息。
0: kd> dd 003d2ef0 003d2ef0 774ee600 003d2e70 00000d34 00000000 003d2f00 862941fa c94cde09 55a88424 801a2601 003d2f10 55a88424 801a2601 0000c400 00000d34
啊,在那里!0d34是为OLE请求提供服务的进程id。我回去复习了!process 0 0的输出,找到包含进程D34的进程。我要把它扔在这里。
PROCESS 861a9d90 SessionId: 5 Cid: 0d34 Peb: 7ffd9000 ParentCid: 0fdc DirBase: 7d66c7a0 ObjectTable: a5651a98 HandleCount: 170. Image: ieuser.exe
好的,那么处理这个请求的过程是IEUSER.exe,这是Internet Explorer在Vista中以保护模式运行时使用的代理进程。这是Vista的新功能。
现在我需要深入研究IEUser.exe找出导致Iexplore等待的原因的过程。
0: kd> !process 861a9d90 PROCESS 861a9d90 SessionId: 5 Cid: 0d34 Peb: 7ffd9000 ParentCid: 0fdc DirBase: 7d66c7a0 ObjectTable: a5651a98 HandleCount: 170. Image: ieuser.exe
我发现了ieuser.exe正在处理来自Iexplore.exe的调用. 然而,这个线程也在等待对某个内容的访问才能完成。
0: kd> kvn+200 # ChildEBP RetAddr Args to Child 00 a41b0c50 81a940a1 85ecbac0 85ecbb48 81af9920 nt!KiSwapContext+0x26 01 a41b0c94 81a2ddc8 85ecbac0 00000000 85e18510 nt!KiSwapThread+0x44f 02 a41b0ce8 81bf0d9f 85e18510 00000006 00000001 nt!KeWaitForSingleObject+0x492 03 a41b0d50 81a30b3a 00000224 00000000 00000000 nt!NtWaitForSingleObject+0xbe 04 a41b0d50 77568254 00000224 00000000 00000000 nt!KiFastCallEntry+0x12a 05 0163e8a4 77567a0c 7708c7c4 00000224 00000000 ntdll!KiFastSystemCallRet 06 0163e8a8 7708c7c4 00000224 00000000 00000000 ntdll!NtWaitForSingleObject+0xc 07 0163e918 7708c732 00000224 ffffffff 00000000 kernel32!WaitForSingleObjectEx+0xbe 08 0163e92c 747e60aa 00000224 ffffffff 00000000 kernel32!WaitForSingleObject+0x12 09 0163e948 747e5fe0 0163ec48 00497b38 00000000 RASAPI32!ReadPhonebookFileEx+0x1a4 0a 0163e968 747e4e9d 0163ec48 00000000 00000000 RASAPI32!ReadPhonebookFile+0x1b 0b 0163e9a8 747e52d3 0163ec48 0048f660 0163e9e0 RASAPI32!DwEnumEntriesFromPhonebook+0x35 0c 0163ee58 747e4fd4 0163ee9c 00000001 0048f660 RASAPI32!DwEnumEntriesInDir+0x1cc 0d 0163f0ac 747e677e 00000001 0048f660 0163f0f4 RASAPI32!DwEnumEntriesForPbkMode+0xab 0e 0163f0e0 76e534b9 00000000 00004180 0048f660 RASAPI32!RasEnumEntriesW+0xb0 0f 0163f10c 76e5342a 76ecc2dc 00000000 00000000 WININET!RasEnumHelp::RasEnumHelp+0x55 10 0163f120 76e533a3 00000001 00000000 00000001 WININET!DoConnectoidsExist+0x2b 11 0163f14c 76e534ff 76ecf02c 76ecc2dc 00000000 WININET!GetRasConnections+0x34 12 0163f168 76e4196e 00000000 0163f180 6ee3228c WININET!IsDialUpConnection+0xa9 13 0163f184 76e70564 00000001 00000000 00000000 WININET!FixProxySettingsForCurrentConnection+0x31 14 0163f1a8 6ec2db3b 00000000 0163f1bc 0163f3d4 WININET!InternetInitializeAutoProxyDll+0x6c 15 0163f1b8 75fe9142 004884a0 00000202 0047e900 ieframe!CShdocvwBroker::StartAutoProxyDetection+0x4d 16 0163f1d0 76054294 6ec2daf8 0163f3d8 00000001 RPCRT4!Invoke+0x2a 17 0163f5fc 76054a49 004858d0 0046e4e0 0047e900 RPCRT4!NdrStubCall2+0x27b 18 0163f64c 774e86d7 004858d0 0047e900 0046e4e0 RPCRT4!CStdStubBuffer_Invoke+0xa0 19 0163f694 774e877d 0047e900 0047f968 00480b38 ole32!SyncStubInvoke+0x3c 1a 0163f6e0 7740249e 0047e900 0047f860 004858d0 ole32!StubInvoke+0xb9 1b 0163f7bc 774023af 0046e4e0 00000000 004858d0 ole32!CCtxComChnl::ContextInvoke+0xfa 1c 0163f7d8 774e85df 0047e900 00000001 004858d0 ole32!MTAInvoke+0x1a 1d 0163f808 774e8a18 d0908070 0046e4e0 004858d0 ole32!AppInvoke+0xaa 1e 0163f8e4 774e85ef 0047e8a8 00474118 0047ebc0 ole32!ComInvokeWithLockAndIPID+0x32c 1f 0163f930 75fe94b0 0047ebc0 08ac70b7 0047ebc0 ole32!ThreadInvoke+0x2fd
我转储传递给KeWaitForSingleObject的第一个参数,以确定对象类型。
0: kd> !object 85e18510 Object: 85e18510 Type: (85484d40) Mutant ObjectHeader: 85e184f8 (old version) HandleCount: 3 PointerCount: 5 Directory Object: 881f3030 Name: RasPbFile
好的,我们正在等待一个名为RasPbFile的互斥体。现在你脑子里最大的问题是,“谁拥有这把锁”,对吧?
要确定这一点,我将转储互斥对象的结构:
0: kd> dt _KMUTANT 85e18510 nt!_KMUTANT +0x000 Header : _DISPATCHER_HEADER +0x010 MutantListEntry : _LIST_ENTRY [ 0x870b6564 - 0x870b6564 ] +0x018 OwnerThread : 0x870b6398 _KTHREAD +0x01c Abandoned : 0 '' +0x01d ApcDisable : 0 ''
互斥体的所有者是870b6398。坏消息是这和我在Iexplore.exe过程,所以我们要看一个典型的死锁场景。Iexplore.exe已将OLE呼叫到IEuser.exe程序。IEuser进程首先需要调用它的线程所拥有的互斥体。
结果发现这是RasApi代码中的错误,而不是浏览器或IEuser进程。RasAPI代码的编写方式是,它将等待无限长的时间来获得互斥体。
0163e92c 747e60aa 00000224 ffffffff 00000000 kernel32!WaitForSingleObject+0x12