regsvr32.exe 线程问题(WaitForMultipleObjects() 和 SetEvent())
我在安装过程中遇到了 regsvr32.exe 挂起的问题。 DLL(我们称之为 common.dll)是使用 regsvr32.exe 注册为安装过程的一部分。 Common.dll 使用另一个 DLL,utility.dll。
utility.dll 的一部分包含日志记录功能。此日志记录功能使用静态“计时器”对象来定期检查日志文件大小并相应地进行拆分。 Timer 对象包含它自己的线程,用于触发计时器。记录器内的计时器对象是静态的,因此它在多个使用静态 ofstreams 指向同一文件的记录器实例中使用。
计时器有两个事件,一个计时器(使用 CreateWaitableTimer() 创建)和一个用于触发线程关闭的标准同步事件(CreateEvent())。线程在构造函数中启动(_beginthreadex())。在线程函数内部有一个 WaitForMultipleObjects() 调用等待计时器和关闭事件。 Wait...() 是 INFINITE,当设置关闭事件 (SetEvent()) 时,线程函数返回。
(以上内容作为背景提供,作为解决方案的一部分,不能更改其中的任何部分,并且所有 DLL 文件、记录器和计时器都正常工作)。
该问题在 regsvr32.exe 运行期间出现。它加载 common.dll,后者加载 utility.dll,后者初始化静态计时器线程对象。线程正确启动,并到达线程函数内的 WaitForMultipleObjects() 调用。一旦注册完成(几乎立即),就会调用计时器析构函数。析构函数在关闭事件上调用 SetEvent(),但 WaitForMultipleObjects() 永远不会返回。作为尝试解决此问题的一部分,我在 SetEvent() 调用之后立即调用了 WaitForSingleObject() ,等待关闭事件。这也永远不会再出现,这让我相信事件本身存在问题。我有以下可能的解释:
- 时间问题。注册过程很快就结束了,因此线程可能正在进入尚未准备好关闭的状态?不过,该线程确实到达了 WaitForMultipleObjects() 调用,这让我相信这不是问题。
- Utility.dll 正在由 regsvr32.exe 卸载。我不太清楚这一切是如何工作的,但使用 ProcessExplorer 看起来 regsvr32.exe 在挂起时仍然加载了 dll,所以我认为这不是问题。
- 关闭期间 regsvr32.exe 内部出现紧密循环。如果 regsvr32.exe 的销毁过程发生在紧密循环中(即 while(NotShutdown()) 等),则这可能不会放弃计时器线程的任何 cpu 时间,这可以解释挂起的原因。
关于这个问题还有更多的想法吗?我搜索过互联网,但找不到与此问题相关的任何内容。
PS我知道问题的解决方案是使用静态指针并在实际需要时初始化计时器,这就是我要采用的解决方案。不过,我仍然想了解为什么发生这种情况,对我来说,SetEvent() 不起作用似乎完全荒谬。
Windbg !locks 命令的输出:
0:000> !locks
CritSec ntdll!LdrpLoaderLock+0 at 7c97e178
LockCount 2
RecursionCount 1
OwningThread d8
EntryCount 4
ContentionCount 4
*** Locked
Scanned 253 critical sections
0:000> ~*kv
. 0 Id: a40.d8 Suspend: 0 Teb: 7ffdf000 Unfrozen
ChildEBP RetAddr Args to Child
0007e5ec 7c90df5a 7c8025db 00000764 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
0007e5f0 7c8025db 00000764 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc (FPO: [3,0,0])
0007e654 7c802542 00000764 ffffffff 00000000 kernel32!WaitForSingleObjectEx+0xa8 (FPO: [Non-Fpo])
*** WARNING: Unable to verify checksum for Utilityd.dll
0007e668 00a84e37 00000764 ffffffff 0007e71c kernel32!WaitForSingleObject+0x12 (FPO: [Non-Fpo])
0007e6c8 00a2e5af 0007e798 0007e754 00aa02e0 Utilityd!CThreadTimer::~CThreadTimer+0x97 [C:\xxx\ThreadTimer.cpp @ 49]
0007e71c 00aa02ae 00fe7a18 0007e740 00aa039b Utilityd!$E177+0x3f
0007e728 00aa039b 00a10000 00000000 00000000 Utilityd!_CRT_INIT+0xde [crtdll.c @ 236]
0007e740 7c90118a 00a10000 00000000 00000000 Utilityd!_DllMainCRTStartup+0xbb [crtdll.c @ 289]
0007e760 7c91e044 00aa02e0 00a10000 00000000 ntdll!LdrpCallInitRoutine+0x14
0007e858 7c80ac97 00950000 00000000 0003415e ntdll!LdrUnloadDll+0x41c (FPO: [Non-Fpo])
0007e86c 0100214e 00950000 00000000 00020bca kernel32!FreeLibrary+0x3f (FPO: [Non-Fpo])
0007ff1c 010024bf 01000000 00000000 00020bca regsvr32!wWinMain+0xad1 (FPO: [Non-Fpo])
0007ffc0 7c817077 00000018 00000000 7ffd4000 regsvr32!wWinMainCRTStartup+0x198 (FPO: [Non-Fpo])
0007fff0 00000000 01002327 00000000 78746341 kernel32!BaseProcessStart+0x23 (FPO: [Non-Fpo])
1 Id: a40.e98 Suspend: 0 Teb: 7ffde000 Unfrozen
ChildEBP RetAddr Args to Child
0104fe34 7c90df5a 7c91b24b 00000760 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
0104fe38 7c91b24b 00000760 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc (FPO: [3,0,0])
0104fec0 7c901046 0197e178 7c913978 7c97e178 ntdll!RtlpWaitForCriticalSection+0x132 (FPO: [Non-Fpo])
0104fec8 7c913978 7c97e178 00000000 7ffde000 ntdll!RtlEnterCriticalSection+0x46 (FPO: [1,0,0])
0104ff34 7c80c136 006e0065 00560074 00fe43d8 ntdll!LdrShutdownThread+0x22 (FPO: [Non-Fpo])
*** ERROR: Symbol file could not be found. Defaulted to export symbols for MSVCRTD.DLL -
0104ff6c 1020c061 00000000 00fe43d8 0104ffb4 kernel32!ExitThread+0x3e (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
0104ff7c 1020bfd8 00000000 006e0065 00560074 MSVCRTD!endthreadex+0x41
0104ffb4 7c80b729 00fe43d8 006e0065 00560074 MSVCRTD!beginthreadex+0x178
0104ffec 00000000 1020bf20 00fe43d8 00000000 kernel32!BaseThreadStart+0x37 (FPO: [Non-Fpo])
2 Id: a40.1708 Suspend: 0 Teb: 7ffdd000 Unfrozen
ChildEBP RetAddr Args to Child
0136fc0c 7c90df5a 7c91b24b 00000760 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
0136fc10 7c91b24b 00000760 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc (FPO: [3,0,0])
0136fc98 7c901046 0197e178 7c91e3b5 7c97e178 ntdll!RtlpWaitForCriticalSection+0x132 (FPO: [Non-Fpo])
0136fca0 7c91e3b5 7c97e178 0136fd2c 00000004 ntdll!RtlEnterCriticalSection+0x46 (FPO: [1,0,0])
0136fd18 7c90e457 0136fd2c 7c900000 00000000 ntdll!_LdrpInitialize+0xf0 (FPO: [Non-Fpo])
00000000 00000000 00000000 00000000 00000000 ntdll!KiUserApcDispatcher+0x7
I'm having some issues with regsvr32.exe hanging during an installation. A DLL, let's call it common.dll, is registered as part of the installation process using regsvr32.exe. Common.dll utilises another DLL, utility.dll.
Part of utility.dll contains logging functionality. This logging functionality uses a static 'Timer' object to periodically check log file sizes and splitting accordingly. The Timer object incorporates it's own thread which it uses to fire the timer. The timer object inside the logger is a static, so it is used across multiple logger instances which use static ofstreams to point to the same file.
The timer has two events, a timer (created using CreateWaitableTimer()) and a standard synchronisation event (CreateEvent()) for triggering thread shutdown. The thread is started in the constructor (_beginthreadex()). Inside the thread function there is a WaitForMultipleObjects() call waiting on both the timer and the shutdown event. The Wait...() is INFINITE, and the thread function returns when the shutdown event is set (SetEvent()).
(The above is provided as background, no part of it can be changed as part of the solution, and all DLL files, the logger and the timer are working properly).
The issue arises during regsvr32.exe running. It loads up common.dll, which loads up utility.dll, which initialises the static timer thread object. The thread is started properly, and it reaches the WaitForMultipleObjects() call inside the thread function. As soon as registration completes (almost instantly), the timer destructor is called. The destructor calls SetEvent() on the shutdown event, but WaitForMultipleObjects() never returns. As part of trying to figure out this issue I've put a WaitForSingleObject() call immediately after the SetEvent() call, waiting on the shutdown event. This also never returns, which leads me to believe there is an issue with the event itself. I had the following possible explanations:
- A timing issue. The registration process is over very quickly, and as such maybe the thread is getting into a state where it isn't ready to shutdown? The thread does reach the WaitForMultipleObjects() call though, which leads me to believe this isn't the issue.
- Utility.dll is being unloaded by regsvr32.exe. I'm not really up on how this all works, but using ProcessExplorer it looks like regsvr32.exe still has the dll loaded while it is hanging, so I don't think this is the issue.
- A tight loop inside regsvr32.exe during shutdown. If the destruction process for regsvr32.exe is taking place in a tight loop (i.e. while(NotShutdown()) etc), maybe this isn't relinquishing any cpu time for the timer thread, which would explain the hang.
Any more thoughts on the issue? I've scoured the internet and can't find anything remotely related to this problem.
P.S. I know the solution to the problem is to use a static pointer and initialise the timer when it is actually needed, and that's the solution i'm going with. However i'd still like to understand why this is happening, as to me it seems completely ridiculous that SetEvent() would not work.
Output from windbg !locks command:
0:000> !locks
CritSec ntdll!LdrpLoaderLock+0 at 7c97e178
LockCount 2
RecursionCount 1
OwningThread d8
EntryCount 4
ContentionCount 4
*** Locked
Scanned 253 critical sections
0:000> ~*kv
. 0 Id: a40.d8 Suspend: 0 Teb: 7ffdf000 Unfrozen
ChildEBP RetAddr Args to Child
0007e5ec 7c90df5a 7c8025db 00000764 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
0007e5f0 7c8025db 00000764 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc (FPO: [3,0,0])
0007e654 7c802542 00000764 ffffffff 00000000 kernel32!WaitForSingleObjectEx+0xa8 (FPO: [Non-Fpo])
*** WARNING: Unable to verify checksum for Utilityd.dll
0007e668 00a84e37 00000764 ffffffff 0007e71c kernel32!WaitForSingleObject+0x12 (FPO: [Non-Fpo])
0007e6c8 00a2e5af 0007e798 0007e754 00aa02e0 Utilityd!CThreadTimer::~CThreadTimer+0x97 [C:\xxx\ThreadTimer.cpp @ 49]
0007e71c 00aa02ae 00fe7a18 0007e740 00aa039b Utilityd!$E177+0x3f
0007e728 00aa039b 00a10000 00000000 00000000 Utilityd!_CRT_INIT+0xde [crtdll.c @ 236]
0007e740 7c90118a 00a10000 00000000 00000000 Utilityd!_DllMainCRTStartup+0xbb [crtdll.c @ 289]
0007e760 7c91e044 00aa02e0 00a10000 00000000 ntdll!LdrpCallInitRoutine+0x14
0007e858 7c80ac97 00950000 00000000 0003415e ntdll!LdrUnloadDll+0x41c (FPO: [Non-Fpo])
0007e86c 0100214e 00950000 00000000 00020bca kernel32!FreeLibrary+0x3f (FPO: [Non-Fpo])
0007ff1c 010024bf 01000000 00000000 00020bca regsvr32!wWinMain+0xad1 (FPO: [Non-Fpo])
0007ffc0 7c817077 00000018 00000000 7ffd4000 regsvr32!wWinMainCRTStartup+0x198 (FPO: [Non-Fpo])
0007fff0 00000000 01002327 00000000 78746341 kernel32!BaseProcessStart+0x23 (FPO: [Non-Fpo])
1 Id: a40.e98 Suspend: 0 Teb: 7ffde000 Unfrozen
ChildEBP RetAddr Args to Child
0104fe34 7c90df5a 7c91b24b 00000760 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
0104fe38 7c91b24b 00000760 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc (FPO: [3,0,0])
0104fec0 7c901046 0197e178 7c913978 7c97e178 ntdll!RtlpWaitForCriticalSection+0x132 (FPO: [Non-Fpo])
0104fec8 7c913978 7c97e178 00000000 7ffde000 ntdll!RtlEnterCriticalSection+0x46 (FPO: [1,0,0])
0104ff34 7c80c136 006e0065 00560074 00fe43d8 ntdll!LdrShutdownThread+0x22 (FPO: [Non-Fpo])
*** ERROR: Symbol file could not be found. Defaulted to export symbols for MSVCRTD.DLL -
0104ff6c 1020c061 00000000 00fe43d8 0104ffb4 kernel32!ExitThread+0x3e (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
0104ff7c 1020bfd8 00000000 006e0065 00560074 MSVCRTD!endthreadex+0x41
0104ffb4 7c80b729 00fe43d8 006e0065 00560074 MSVCRTD!beginthreadex+0x178
0104ffec 00000000 1020bf20 00fe43d8 00000000 kernel32!BaseThreadStart+0x37 (FPO: [Non-Fpo])
2 Id: a40.1708 Suspend: 0 Teb: 7ffdd000 Unfrozen
ChildEBP RetAddr Args to Child
0136fc0c 7c90df5a 7c91b24b 00000760 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
0136fc10 7c91b24b 00000760 00000000 00000000 ntdll!ZwWaitForSingleObject+0xc (FPO: [3,0,0])
0136fc98 7c901046 0197e178 7c91e3b5 7c97e178 ntdll!RtlpWaitForCriticalSection+0x132 (FPO: [Non-Fpo])
0136fca0 7c91e3b5 7c97e178 0136fd2c 00000004 ntdll!RtlEnterCriticalSection+0x46 (FPO: [1,0,0])
0136fd18 7c90e457 0136fd2c 7c900000 00000000 ntdll!_LdrpInitialize+0xf0 (FPO: [Non-Fpo])
00000000 00000000 00000000 00000000 00000000 ntdll!KiUserApcDispatcher+0x7
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
从 DllMain 调用全局析构函数和构造函数,并保持加载程序锁,如您从堆栈跟踪中看到的那样。调用 ~CThreadTimer 的线程具有加载器锁,并且正在等待事件被设置。如果另一个线程需要加载器锁才能继续,它将被阻塞,直到事件被设置。如果设置事件的线程是需要加载器锁的线程之一,那么您最终会遇到像这样的死锁。当加载 dll 时、创建或销毁线程时、卸载 dll 时、进程退出和启动时以及其他一些地方(例如 GetModuleHandle)时,都会获取加载程序锁。
创建这样的死锁的一个简单方法是:
也就是说,您暗示 SetEvent 确实被调用。如果确实如此,可能还有更多事情发生。
您也可以使用
!handle
查看您正在等待的事件,看看是否可以获得一些见解。另外,我会再次尝试使用 ApplicationVerifier 运行,它可能会导致您遇到问题。Global destructors and constructors are called from DllMain with the loader lock held as you can see from your stack traces. The thread calling ~CThreadTimer has the loader lock and it is waiting for the event to be set. If another thread needs the loader lock to continue, it will be blocked until the event is set. If the thread that sets the event is one of the threads that needs the loader lock, you'll end up with a deadlock like this one. The loader lock is taken when dlls are loaded, when threads are created or destroyed, when dlls are unloaded, at process exit and startup, and a few other places (GetModuleHandle for example).
An easy way to create a deadlock like this is:
That said, you implied SetEvent was indeed being called. If it indeed is, there's probably more going on.
You can use
!handle
to take a look at the event you're waiting on as well to see if you can gain some insight. Also, again I would try running with ApplicationVerifier, it may lead you to the problem.