c语言 - 在 Win 10 中卸载 DLL 时调试崩溃,但不在 Win 7 中



不完全确定我是否已经确定了这个问题,但这是我所看到的和我认为正在发生的事情。

我有一个主要用 C 编写的 Win32 程序,它加载了一个C++ DLL。 该 DLL 通过 COM 对象将数据从 C 程序传递到另一个应用程序,该对象可能由 DLL 本身实例化。 所有这些显然至少在Windows XP和Windows 7(可能是Win95和Win98,我需要更深入地回顾代码历史才能找出这个接口被引入的时间)中运行良好,但是在Windows 10中,程序在FreeLibrary()调用此DLL期间崩溃。

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

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

如果我使用调试器的 CPU 窗口并继续单步执行,则剩余的所有内容似乎都与崩溃来临之前释放内存有关,但要到达那里需要相当多的 CPU 步进。

崩溃引发了APPCRASH,但0xc0000602除外,参考了Combase.dll。

简单地不为该DLL调用FreeLibrary就可以成功关闭应用程序,但我的假设是FreeLibrary调用很重要。

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

我的问题:

  • 如果其他人更清楚自己在做什么,那么是什么导致了这次崩溃?

  • 尝试调试此内容的后续步骤是什么? 我已经用尽了我正在使用的调试环境的知识,并且对 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?

> 崩溃引发 APPCRASH,但0xc0000602除外,引用回 Combase.dll

combase.dll仅从0xc0000602(STATUS_FAIL_FAST_EXCEPTION) 代码

中使用void CrashProcessWithWERReport();

(使用此代码调用RaiseFailFastException)

CrashProcessWithWERReport仅在 2 个条件下从DecrementMTAUsageHelper调用 -CoDecrementMTAUsage调用的次数比CoIncrementMTAUsage或(我几乎可以肯定)在调用线程时调用DecrementMTAUsageHelper加载器关键部分 - 所以在 DLL 加载或卸载过程中。 与 MSDN 相比

不要在进程关闭期间或内部调用 CodecrementMTAUsage德曼。您可以在呼叫开始之前致电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 从DecrementMTAUsageHelper年初调用)。

在任何情况下DecrementMTAUsageHelper调用点(就在崩溃之前)发布线程调用堆栈,也可以在CoIncrementMTAUsage上发布

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

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

apphelp!DWM8AND16BitHook_DestroyWindow

这是错误,原因有两个 - 首先 - 阅读这篇文章 -

获取DLL_PROCESS_DETACH通知的线程不是 必须是收到DLL_PROCESS_ATTACH通知的那个。你 无法对DLL_PROCESS_ATTACH中的线程相关性执行任何操作,或者 DLL_PROCESS_DETACH处理程序,因为您无法保证哪个 将调用线程来处理这些进程通知。这 这方面的经典例子,我被告知开发人员支持团队运行 以惊人的频率进入,是一个在其中创建窗口的 DLL DLL_PROCESS_ATTACH处理程序并在其DLL_PROCESS_DETACH中销毁它 处理器。

但是你在这里崩溃是另一个原因,没有在文章中列出 - DllMain 有很多限制,里面不能叫什么。 尽管DestroyWindow这里没有直接列出,但正如显示您的情况 - 这是非法调用(即使我们在创建此窗口的同一线程上调用) - 而您的窗口被破坏imm32.CtfImmNotify(msctf!TF_Notify)被调用

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

结果,从DllMain 调用CoUninitialize!

来自 MSDN

不要从 Dll主函数。

在 FINALCoUninitialize 中调用DecrementMTAUsage它确定我们在加载器内部通过调用锁定RtlIsCriticalSectionLockedByThreadCrashProcessWithWERReport调用。

溶液?

当然,最好的是修复DLL,但如果这是不可能的 - 想想下一个"黑客"将起作用

HRESULT hr = CoInitialize(0); // asume that we in STA
FreeLibrary(hDLL); 
if (0 <= hr) CoUninitialize();

有了这个 CoUninitialize 当然会从imm32!CtfImmCoUninitialize调用,但这不会是最终的 取消初始化,因此DecrementMTAUsage不会被调用

最新更新