在Win 10中卸载DLL时调试崩溃,但不是Win 7

时间:2016-12-07 01:42:12

标签: c windows debugging com c++builder-6

我还没确定我已经把这个问题钉了下来,但这就是我所看到的以及我认为正在进行的事情。

我有一个Win32程序,主要用C语言加载C ++ DLL。该DLL通过COM对象将数据从C程序传递到另一个应用程序 - 这可能是由DLL本身实例化的。所有这一切显然在至少Windows XP和Windows 7(可能是Win95和Win98,我需要回顾代码历史以找出何时引入此接口)上工作正常,但在Windows 10中程序崩溃在FreeLibrary()调用此DLL期间。

在调试器中检查时,DLL_DETACH_PROCESS似乎处理成功(处理该消息时不执行代码)。崩溃发生在离开入口点的代码之后(或同时)。

如果我继续Step In,我最终会出现一个名为utilcls.h的头文件,它似乎是Borland C Builder 6头文件之一。我相信其中的模板代码与被拆除的COM对象有关。一个Unbind()调用传递,这是我在崩溃之前可以执行的最后一行代码。

如果我使用调试器的CPU窗口并继续步进,那么剩下的一切似乎都与崩溃前的内存释放有关,但是要实现这一目标需要相当多的CPU步骤。

崩溃引发APPCRASH,异常为0xc0000602,返回Combase.dll。

只是不为该DLL调用FreeLibrary允许应用程序成功关闭,但我的假设是FreeLibrary调用很重要。

在FreeLibrary()调用之前,数据共享应用程序释放COM对象,允许该应用程序关闭。我现在的假设是,在较新的操作系统中,这种取消链接的某些方式发生了不同,这导致了崩溃,但我不知道如何确定。

我的问题:

  • 如果对于那些更了解自己正在做什么的其他人来说很明显,是什么导致这次崩溃?

  • 尝试调试此操作的后续步骤是什么?我已经用尽了我所使用的调试环境的知识,并且不太了解COM或DLL,知道下一个问题是什么。

RbMm请求的一些调试器输出:

0:000:x86> t
ntdll_77b40000!RtlIsCriticalSectionLockedByThread+0x1b:
77b7256b c20400          ret     4
0:000:x86> t
combase!DecrementMTAUsageHelper+0x5b:
7527a2d6 85c0            test    eax,eax
0:000:x86> r eax
eax=00000001
0:000:x86> t
combase!DecrementMTAUsageHelper+0x5d:
7527a2d8 0f859a000000    jne     combase!DecrementMTAUsageHelper+0xfd (7527a378) [br=1]
0:000:x86> t
combase!DecrementMTAUsageHelper+0xfd:
7527a378 e89e9e0f00      call    combase!CrashProcessWithWERReport (7537421b)

此时,堆栈看起来大致如下:

ChildEBP RetAddr  Args to Child              
0019f9b8 7527a37c 063f4248 753d8448 00000000 combase!CrashProcessWithWERReport+0x35
0019f9e8 75292bfc 753d8448 7529257e 00000000 combase!DecrementMTAUsageHelper+0x101
(Inline) -------- -------- -------- -------- combase!DecrementMTAUsage+0x9
0019f9f0 7529257e 00000000 00000000 00000000 combase!CDllHost::MTAUninitializeApartmentOnly+0xe
0019fa08 7527543a 00000000 063f4248 00712410 combase!CDllHost::ClientCleanupFinish+0x4d
0019fa30 75276361 00000000 0019fa8c 00000000 combase!DllHostProcessUninitialize+0xa0
0019fa58 7527a452 000d06f6 00712410 00000000 combase!ApartmentUninitialize+0xe4
0019fa70 752c2a1e 000d06f6 00712e18 00712e80 combase!wCoUninitialize+0xd0
0019fa94 74ed3e58 00000003 74c17ff1 a6d0e607 combase!CoUninitialize+0x7e
0019fa9c 74c17ff1 a6d0e607 000b0792 74ed48f0 imm32!CtfImmCoUninitialize+0x48
0019fb7c 74809ea6 00050004 000d06f6 00000000 msctf!TF_Notify+0x581
0019fb98 748080dc 00050004 000d06f6 00000000 user32!CtfHookProcWorker+0x36
0019fbe0 74807fa6 0019fc34 0019fc24 00000000 user32!CallHookWithSEH+0x5c
0019fc08 77bb0006 0019fc24 00000018 0019fc80 user32!__fnHkINDWORD+0x26
0019fc38 710623fb 000b0792 04ff11aa 05480e70 ntdll!KiUserCallbackDispatcher+0x36
0019fc50 050364e4 000b0792 050376d8 05480e70 apphelp!DWM8AND16BitHook_DestroyWindow+0x2b
0019fc8c 05051007 00000000 05055034 00000001 myDLL!myCOMObject_tlbFinalize+0x408a4
0019fcb4 050511c6 0019fcd0 00000001 04ff1318 myDLL!myCOMObject_tlbFinalize+0x5b3c7
0019fcd8 04ff13d3 05055034 77badcce 04ff0000 myDLL!myCOMObject_tlbFinalize+0x5b586
0019fd00 77b807c6 04ff1318 04ff0000 00000000 myDLL+0x13d3
0019fd50 77b6aa5e 00000000 00000000 259704e5 ntdll!LdrpCallInitRoutine+0x43
0019fdb8 77b6e6c8 00000000 0071dd60 00000000 ntdll!LdrpProcessDetachNode+0xbb
0019fdd8 77b6e5af 25970745 0071e560 c000022d ntdll!LdrpUnloadNode+0x100
0019fe18 77b6e4f6 004afcc4 004ae3a4 04ff0000 ntdll!LdrpDecrementModuleLoadCountEx+0xa7
0019fe38 746e9d56 04ff0000 006e33c5 00000000 ntdll!LdrUnloadDll+0x86
0019fe4c 0049261c 04ff0000 00000000 00493034 KERNELBASE!FreeLibrary+0x16
0019fe64 00441895 004afc98 fffffffe 0019fee8 rpopdbg!_GetExceptDLLinfo+0x914bf

现在正在处理其余部分,但我的猜测是我需要弄清楚如何在COM对象上正确清理?也许是为了响应DLL_DETACH_PROCESS?

1 个答案:

答案 0 :(得分:2)

  

崩溃引发APPCRASH,异常为0xc0000602,返回参考   Combase.dll

combase.dll仅使用来自

0xc0000602STATUS_FAIL_FAST_EXCEPTION)代码

void CrashProcessWithWERReport();

(使用此代码调用RaiseFailFastException

CrashProcessWithWERReport仅在2个条件下从DecrementMTAUsageHelper调用 - CoDecrementMTAUsage调用次数超过CoIncrementMTAUsage或(,我几乎可以肯定DecrementMTAUsageHelper在调用线程保持Loader临界区时调用 - 所以在DLL加载或卸载过程中。来自MSDN

  

请勿在进程关闭或内部时调用CoDecrementMTAUsage   的DllMain 即可。您可以在调用start之前调用CoDecrementMTAUsage   关机过程。

所以我的猜测 - 你的DLL卸载过程中的一些代码调用CoDecrementMTAUsage(当你调用FreeLibrary时)

你的DLL无法直接调用CoIncrementMTAUsage / CoDecrementMTAUsage因为这个新的API存在从win 8开始(也检查你的代码在win 8.1上 - 我认为也会崩溃),但是这个api可以从其他系统组件间接调用。

我可以假设您的DLL不直接释放一些使用过的资源,或者当DLL仍然保留一些资源时调用FreeLibrary(因此您在没有正确清理DLL的情况下调用FreeLibrary)并因此获得此资源在卸载过程中开始免费(CoDecrementMTAUsage

  

尝试调试它的后续步骤是什么?

你需要使用符号文件调试(比如winDbg)。在DecrementMTAUsageHelperCoDecrementMTAUsage设置断点,可能是CoIncrementMTAUsage - 我正确地呼吁RtlIsCriticalSectionLockedByThread返回TRUE(这个api从{{1}开始调用}})。

无论如何在DecrementMTAUsageHelper呼叫点(就在崩溃之前)发布线程调用堆栈,也可能在DecrementMTAUsageHelper上发布

----------------------编辑--------------------- ----

通过视图堆栈跟踪可见您的DLL从DllMain调用CoIncrementMTAUsage

DestroyWindow

这只是出于两个原因的错误 - 首先 - 阅读this article -

  

获取DLL_PROCESS_DETACH通知的线程不是   必须是获得DLL_PROCESS_ATTACH通知的那个。您   在您的DLL_PROCESS_ATTACH或中,无法对线程相关性做任何事情   DLL_PROCESS_DETACH处理程序,因为您无法保证哪些   将调用线程来处理这些进程通知。该   这个经典的例子,我告诉开发人员支持团队   进入惊人的频率,是一个在其中创建窗口的DLL   DLL_PROCESS_ATTACH处理程序并在其DLL_PROCESS_DETACH中销毁它   处理程序。

但是你的崩溃是由于另一个原因,未在文章中列出 - DllMain有很多restrictions,其内部无法调用。尽管apphelp!DWM8AND16BitHook_DestroyWindow没有直接列在这里,但是如你所知 - 这是非法的调用(即使我们调用同一个线程,在这个窗口上创建) - 当你的窗口被销毁时DestroyWindow被调用

imm32.CtfImmNotify(msctf!TF_Notify)

结果CoUninitialize从DllMain 调用

来自MSDN

  

不要从中调用CoInitialize,CoInitializeEx或CoUninitialize   DllMain功能。

此处名为0019fa9c 74c17ff1 a6d0e607 000b0792 74ed48f0 imm32!CtfImmCoUninitialize+0x48 0019fb7c 74809ea6 00050004 000d06f6 00000000 msctf!TF_Notify+0x581 0019fb98 748080dc 00050004 000d06f6 00000000 user32!CtfHookProcWorker+0x36 0019fbe0 74807fa6 0019fc34 0019fc24 00000000 user32!CallHookWithSEH+0x5c FINAL CoUninitialize内部,它通过调用DecrementMTAUsageRtlIsCriticalSectionLockedByThread确定我们在加载程序锁定内部。

解决方案?

当然最好的是修复DLL,但如果这是不可能的 - 想想下一个“hack”将会起作用

CrashProcessWithWERReport

这个CoUninitialize当然会从HRESULT hr = CoInitialize(0); // asume that we in STA FreeLibrary(hDLL); if (0 <= hr) CoUninitialize(); 调用,但这将是 NOT FINAL 未初始化,结果imm32!CtfImmCoUninitialize将不会被调用