还不完全确定我是否已经解决了这个问题,但这是我所看到的,我认为正在发生的事情。
我有一个Win32程序,主要是用C编写的,它加载了一个C++ DLL。该DLL通过COM对象将数据从C程序传递到另一个应用程序--这个对象可能是由DLL本身实例化的。所有这些显然都在Windows和Windows7中运行良好(可能是Win95和Win98,我需要更深入地回顾代码历史,以找出这个接口是什么时候引入的),但是在Windows 10中,程序在FreeLibrary()调用这个DLL时崩溃。
在调试器中检查这一点时,似乎成功地处理了DLL_DETACH_PROCESS (在处理该消息时没有执行任何代码)。崩溃发生在(或同时)离开入口点的代码之后。
如果我继续介入,我最终会进入一个名为utilcls.h的头文件,它似乎是Builder 6头文件之一。我相信其中的模板代码与被拆除的COM对象有关。取消绑定()调用会传递,这是我在崩溃之前可以处理的最后一行代码。
如果我使用调试器的CPU窗口并继续执行步骤,那么在崩溃之前,剩下的一切似乎都与释放内存有关,但是要达到这个目的还需要相当多的CPU。
崩溃引发的APPCRASH异常为0xc0000602,引用Combase.dll。
简单地说,不为DLL调用FreeLibrary允许应用程序成功关闭,但我的假设是,FreeLibrary调用很重要。
COM对象由数据共享应用程序在FreeLibrary()调用之前释放,该调用允许该应用程序关闭。目前我的假设是,在较新的操作系统中,有些断开链接的情况有所不同,这是造成崩溃的原因,但我不知道如何确定。
我的问题是:
一些调试器输出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?
发布于 2016-12-07 12:39:33
崩溃引发的APPCRASH异常为0xc0000602,引用Combase.dll
combase.dll
只使用0xc0000602
(STATUS_FAIL_FAST_EXCEPTION
)代码
void CrashProcessWithWERReport();
CrashProcessWithWERReport
只在2种条件下从DecrementMTAUsageHelper
调用-- CoDecrementMTAUsage
调用的次数超过CoIncrementMTAUsage
或(我几乎肯定在此原因中)在调用线程时调用DecrementMTAUsageHelper
持有加载器关键部分--所以在DLL加载或卸载过程中。来自MSDN
不要在进程关闭期间( CoDecrementMTAUsage )调用或在dllmain中调用。您可以在调用启动关闭过程之前调用CoDecrementMTAUsage。
所以我猜-一些代码在DLL卸载过程中调用CoDecrementMTAUsage
(当您调用FreeLibrary
时)
您的DLL不能直接调用CoIncrementMTAUsage
/ CoDecrementMTAUsage
,因为这个新API从win 8开始存在(我认为win 8.1上的代码也会崩溃),但是这个api可以从其他系统组件间接调用。
我可以假设您的DLL不直接释放一些已使用的资源,或者当DLL仍然持有某些资源时调用FreeLibrary
(因此,您在调用FreeLibrary
时没有正确的清除调用),因此这些资源在卸载过程中开始释放(CoDecrementMTAUsage
)。
在尝试调试这一点时,接下来的步骤是什么?
您需要对符号文件使用调试(例如使用winDbg)。在DecrementMTAUsageHelper
、CoDecrementMTAUsage
上设置断点,并且可能是CoIncrementMTAUsage
--对RtlIsCriticalSectionLockedByThread
返回TRUE
的调用正确吗(这个api从DecrementMTAUsageHelper
开始调用)。
无论如何,在DecrementMTAUsageHelper
调用点(崩溃前)发布线程调用堆栈,在CoIncrementMTAUsage
上也有可能。
通过查看堆栈跟踪,可以看到DLL从DestroyWindow
调用DllMain。
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
因此,CoUninitialize是从DllMain调用的!
来自MSDN
不要从CoInitialize函数调用DllMain、CoInitializeEx或CoUninitialize。
此处,在 CoUninitialize中调用DecrementMTAUsage
,它通过调用RtlIsCriticalSectionLockedByThread
和CrashProcessWithWERReport
来确定加载程序中的锁。
溶液?
当然,最好的是修复DLL,但是如果这是不可能的,那么考虑下一个"hack“就是工作了。
HRESULT hr = CoInitialize(0); // asume that we in STA
FreeLibrary(hDLL);
if (0 <= hr) CoUninitialize();
使用此CoUninitialize,当然会从imm32!CtfImmCoUninitialize
调用,但这将是,而不是最终的,未初始化,因此不会调用DecrementMTAUsage
https://stackoverflow.com/questions/41008037
复制相似问题