介绍
我正面临着前所未有的特殊僵局。
我试图从 3 天开始调试这个死锁,但找不到如何以正确的方式修复它。希望有人能帮助我。
我们没有应用程序的源代码,因此必须通过修补 asm 代码来修复此死锁(尽管修补二进制文件没有问题)。
场景
该应用程序是一个 x86 老式在线游戏,它使用 Directx 9 来渲染图形 (d3d9.dll)。根据我的分析,有 3 个线程与此死锁相关。要命名,线程 #1 是主线程(渲染每一帧),线程 #2 似乎是一个 d3d9 工作线程,它将一些异步资源加载排队等待线程 #1 处理和提取,然后我们有神秘的线程#3,它与游戏的一个特性有关,它基本上向服务器发出http请求并下载纹理以即时显示在游戏中;每次必须绘制此纹理时,主线程都会创建此线程 #3 来处理 I/O 以下载纹理,该 I/O 将传递给线程 #2(d3d9 工作线程),最终由线程进行异步处理#1(游戏循环)。
在线程#3 将纹理资源提供给线程#2 之后,线程#3 自行关闭。这个线程 #3 是从 crt!_beginthread 创建的,然后用 crt!_endthread 关闭。
僵局
总结:线程#1(游戏循环)、线程#2(d3d9工作线程)、线程#3(从http下载纹理)。
当(出于某种未知原因)线程 #3 结束时,没有释放线程 #2 正在等待的锁,就会出现问题。线程#1 反过来等待线程#2 获取要绘制的资源。
这与我遇到的所有其他死锁不同,因为当死锁发生时,似乎导致死锁的线程不再存在。我调试过的所有其他死锁都可以直接找到问题的根源,因为在隔离死锁中的线程时,我只需要分析每个线程的调用堆栈即可准确了解发生了什么。这里的大问题是,由于线程 #3 已死,我没有它的调用堆栈来查看它创建死锁的时刻。所以最大的问题是:如果我什至看不到调用堆栈,我怎么能找到这个线程 #3 中发生的事情?
发生死锁后的一些 WinDbg 分析输出
0:001> !locks
CritSec +935a060 at 0935a060
WaiterWoken No
LockCount 2
RecursionCount 1
OwningThread 34fc
EntryCount 0
ContentionCount 21a
*** Locked
Scanned 27 critical sections
0:001> !失控
User Mode Time Thread Time
0:2888 0 days 0:00:15.234
11:2210 0 days 0:00:02.796
20:15f0 0 days 0:00:01.656
17:584 0 days 0:00:00.453
21:2860 0 days 0:00:00.140
13:8cc 0 days 0:00:00.031
7:1a70 0 days 0:00:00.031
23:373c 0 days 0:00:00.015
14:2fe0 0 days 0:00:00.015
22:1bf4 0 days 0:00:00.000
19:3a50 0 days 0:00:00.000
18:2980 0 days 0:00:00.000
16:1e0c 0 days 0:00:00.000
15:2768 0 days 0:00:00.000
12:3154 0 days 0:00:00.000
10:2cfc 0 days 0:00:00.000
9:1e40 0 days 0:00:00.000
8:1ea8 0 days 0:00:00.000
5:2b64 0 days 0:00:00.000
4:338c 0 days 0:00:00.000
1:3be8 0 days 0:00:00.000
0:001> ~0 KB
# ChildEBP RetAddr Args to Child
00 0019f640 75f48869 000006c0 00000000 0019f688 ntdll!NtWaitForSingleObject+0xc
01 0019f6b4 75f487c2 000006c0 000003e8 00000000 KERNELBASE!WaitForSingleObjectEx+0x99
02 0019f6c8 68bbac92 000006c0 000003e8 0935a040 KERNELBASE!WaitForSingleObject+0x12
03 0019f6dc 68b7d6e4 88760870 0935a040 015c6e38 d3d9!CBatchFilterI::WaitForBatchWorkerThread+0x23
04 0019f6ec 68c403d1 04f0de60 68c403b0 c9e02d57 d3d9!CBatchFilterI::FlushBatchWorkerThread+0xc
05 0019f700 68b78522 0935a040 00000000 00011001 d3d9!CBatchFilterI::LHBatchFlush1+0x21
06 0019f718 68b99daa 04f0de60 68b54020 04f0dd00 d3d9!Flush+0x36
07 0019f9bc 68b6a661 04f0de60 04f0b634 04f08ac0 d3d9!DdBltLH+0x45d8a
08 0019fa94 68be9fcc 00000000 00000000 00000000 d3d9!CSwapChain::PresentMain+0x3a7
09 0019fabc 68be9e57 00000000 00000000 00000000 d3d9!CBaseDevice::PresentMain+0x68
0a 0019faf4 10109099 04f08ac0 00000000 00000000 d3d9!CBaseDevice::Present+0x57
0b 0019fc10 10107a15 04f08ac0 00000000 00000000 DoNPatch!fIDirect3Device9::fPresent+0x2e9
0c 0019fc58 005495e0 00000001 03440be8 03448a70 DoNPatch!NKD_IDirect3DDevice_Present+0x5
0d 0019fc7c 00549367 00000000 03440be8 00000000 SD_Asgard!loc_5494D7+0x109
0e 0019fcc4 0054b7a1 0105a000 03440be8 00b200b0 SD_Asgard!loc_549367
0f 0019fef4 005bb824 00400000 00000000 01503b2d SD_Asgard!loc_54B784+0x1d
10 0019ff80 76d28744 00302000 76d28720 34573170 SD_Asgard!loc_5BB812+0x12
11 0019ff94 76f8582d 00302000 03e96be8 00000000 KERNEL32!BaseThreadInitThunk+0x24
12 0019ffdc 76f857fd ffffffff 76fa6389 00000000 ntdll!__RtlUserThreadStart+0x2f
13 0019ffec 00000000 005cc46f 00302000 00000000 ntdll!_RtlUserThreadStart+0x1b
0:001> ~11 KB
# ChildEBP RetAddr Args to Child
00 04c2fe58 76f4c07a 0935a064 00000000 00000000 ntdll!NtWaitForAlertByThreadId+0xc
01 04c2fe78 76f4bfbe 00000000 00000000 ffffffff ntdll!RtlpWaitOnAddressWithTimeout+0x33
02 04c2febc 76f4beb5 00000004 00000000 00000000 ntdll!RtlpWaitOnAddress+0xa5
03 04c2fefc 76f6b3f1 0935a040 0935a040 00000004 ntdll!RtlpWaitOnCriticalSection+0xb7
04 04c2ff1c 76f6b315 0935a040 04c2ff38 68b7d1e8 ntdll!RtlpEnterCriticalSectionContended+0xd1
05 04c2ff28 68b7d1e8 0935a060 0941a324 04c2ff60 ntdll!RtlEnterCriticalSection+0x45
06 04c2ff38 68b80753 00000001 0935a040 00000001 d3d9!CBatchFilterI::AcquireSynchronization+0x28
07 04c2ff60 68c42021 0941a320 00000001 68c41760 d3d9!CBatchFilterI::ProcessBatch+0x14b
08 04c2ff78 68c4176d 04c2ff94 76d28744 0935a040 d3d9!CBatchFilterI::WorkerThread+0x2d
09 04c2ff80 76d28744 0935a040 76d28720 308c3170 d3d9!CBatchFilterI::LHBatchWorkerThread+0xd
0a 04c2ff94 76f8582d 0935a040 07326be8 00000000 KERNEL32!BaseThreadInitThunk+0x24
0b 04c2ffdc 76f857fd ffffffff 76fa6389 00000000 ntdll!__RtlUserThreadStart+0x2f
0c 04c2ffec 00000000 68c41760 0935a040 00000000 ntdll!_RtlUserThreadStart+0x1b
结论
在上面的输出中,拥有锁定临界区 (34fc) 的线程 id 是线程 #3(发出 http 请求),它没有出现在 !runaway 列表中。在 !runaway 列表中,线程号 0 是 #1(游戏循环),线程号 11 是 #2(d3d9 批处理器)。如果您需要我可以收集的任何其他数据,请索取。在本次分析中,我使用了 IDA Pro 6.9 和 WinDbg,但我可以使用其他工具(如果可用)。
最后,对长篇大论深表歉意,并提前致谢。