简介

我正面临一个从未见过的特殊死锁情况。

我试图从3天开始调试此死锁,却找不到解决方法以适当的方式。希望有人能帮到我。

我们没有应用程序的源代码,因此必须修复此死锁以修补asm代码(尽管修补二进制文件也没有问题)。

场景

该应用程序是一个x86旧在线游戏,使用DirectX 9渲染图形(d3d9.dll)。根据我的分析,有3个与此死锁相关的线程。举例来说,线程#1是主线程(渲染每一帧),线程#2似乎是d3d9工作线程,该线程将一些异步资源加载排队,以供线程#1处理和提取,然后我们将与游戏功能有关的神秘线程#3,基本上是向服务器发出http请求并下载纹理以即时显示在游戏中;每次必须绘制此纹理时,主线程都会创建此线程#3来处理I / O以下载纹理,该线程将被传递给线程#2(d3d9工作线程),并最终由该线程异步处理#1(游戏循环)。

在线程#3向线程#2提供纹理资源后,线程#3自行关闭。这个线程#3是从crt!_beginthread创建的,并使用crt!_endthread关闭。 d3d9工作线程),线程3(从http下载纹理)。

(由于某些未知原因)线程3结束而没有释放线程2等待的锁时,会发生问题。线程#1依次等待线程#2获取要绘制的资源。

这与我所遇到的所有其他死锁有所不同,因为当死锁发生时,似乎导致死锁的线程不再存在。我调试过的所有其他死锁几乎可以直接找到问题的根源,因为在死锁中隔离线程时,我只需要分析每个线程的调用堆栈以确切了解正在发生的事情。这里最大的问题是,由于线程#3死了,所以我没有它的调用堆栈来查看它创建死锁的时刻。因此,最大的问题是:如果我什至看不到调用堆栈,该如何发现3号线程内部发生了什么? /> 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>!runaway

  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,但可以使用其他工具。

评论

嗨@blabb感谢您的回答。您认为您的想法在生产阶段是否太冒险?我能想到的很多东西在做这种补丁时都会出错。在我看来,这更像是姑息治疗,而不是真正解决问题的方法。

我不知道,所以删除了评论

#1 楼

与RE相比,这更是一个SW开发问题,但是您可以尝试使用!htrace来找出互斥锁最初分配的位置(创建堆栈跟踪)。

或者,尝试找出为什么线程#3在不释放锁的情况下退出的原因。 ),差异调试可能有助于弄清代码路径的差异。

评论


嗨,@ Igor,谢谢您的回答,对不起,您的回复很晚。我忘了在文本中提及,但是我已经做了!htrace命令,以了解在这种情况下跟踪互斥锁的分配是否有用,但是我从那里什么也没得到。您是否知道是否有一种方法可以跟踪关键部分的锁定/解锁?如果可以做到,我想我可以准确地追踪3号线程在哪里获取锁,并从那里跟踪代码以查看为什么未释放它。

–安德烈·奥利维拉(AndréOliveira)
17-10-2在12:30

关于ExitThread分析,我也已经做到了。我跟踪了线程3导致死锁的执行流,至少对于高帧计数而言,没有异常发生,该线程执行其代码的方式与其他任何不会产生死锁的线程相同。问题可能出在线程的调用栈的末尾,我不知道一旦在线程的生命周期中执行了大约300k条指令(通过“ wt”命令将其捕获),如何追踪它。

–安德烈·奥利维拉(AndréOliveira)
17-10-2在12:34

好吧,然后尝试找出为什么它没有释放此出口路径中的锁。无论如何,这并不是真正的RE问题,所以我不确定还有什么建议。

–伊戈尔·斯科钦斯基♦
17-10-2在12:36

您知道我如何以实用的方式分析大约30万条指令的代码流吗?我的意思是,如果必须跟踪每个功能,我将很快分析d3d9.dll的内部工作及其错误。我以为,如果我能在重现死锁时以某种方式保存每个线程3的执行流,那么我可以比较错误线程的执行流与其他线程的区别。您知道实现此目标的方法吗? (或认为它可以显示问题?)

–安德烈·奥利维拉(AndréOliveira)
17-10-2在12:42

没有特定工具,但请检查reverseengineering.stackexchange.com/a/2567/60

–伊戈尔·斯科钦斯基♦
17年10月2日,12:45