0

我在安装过程中遇到了一些 regsvr32.exe 挂起的问题。一个 DLL,我们称之为 common.dll,使用 regsvr32.exe 注册为安装过程的一部分。Common.dll 使用另一个 DLL,utility.dll。

部分实用程序.dll 包含日志记录功能。此日志记录功能使用静态“计时器”对象来定期检查日志文件大小并相应地进行拆分。Timer 对象包含它自己的线程,用于触发计时器。记录器内的计时器对象是静态的,因此它用于多个记录器实例,这些记录器使用静态流指向同一个文件。

计时器有两个事件,一个计时器(使用 CreateWaitableTimer() 创建)和一个标准同步事件 (CreateEvent()),用于触发线程关闭。线程在构造函数 (_beginthreadex()) 中启动。在线程函数内部,有一个 WaitForMultipleObjects() 调用等待计时器和关闭事件。Wait...() 是 INFINITE,当设置了关闭事件 (SetEvent()) 时,线程函数返回。

(以上是作为背景提供的,作为解决方案的一部分,它的任何部分都不能更改,所有的DLL文件、记录器和计时器都可以正常工作)。

该问题在 regsvr32.exe 运行期间出现。它加载common.dll,后者加载utility.dll,后者初始化静态计时器线程对象。线程已正确启动,并到达线程函数内部的 WaitForMultipleObjects() 调用。一旦注册完成(几乎立即),就会调用计时器析构函数。析构函数在关闭事件上调用 SetEvent(),但 WaitForMultipleObjects() 永远不会返回。作为试图解决这个问题的一部分,我在 SetEvent() 调用之后立即调用了 WaitForSingleObject() 调用,等待关闭事件。这也永远不会回来,这让我相信事件本身存在问题。我有以下可能的解释:

  1. 时间问题。注册过程很快就结束了,因此线程可能进入了尚未准备好关闭的状态?该线程确实到达了 WaitForMultipleObjects() 调用,这让我相信这不是问题所在。
  2. regsvr32.exe 正在卸载 Utility.dll。我不太了解这一切是如何工作的,但是使用 ProcessExplorer 看起来 regsvr32.exe 在挂起时仍然加载了 dll,所以我认为这不是问题所在。
  3. 关机期间 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
4

1 回答 1

0

从堆栈跟踪中可以看出,全局析构函数和构造函数是从 DllMain 调用的,并持有加载程序锁。调用 ~CThreadTimer 的线程具有加载程序锁,它正在等待设置事件。如果另一个线程需要加载器锁才能继续,它将被阻塞,直到事件被设置。如果设置事件的线程是需要加载程序锁的线程之一,那么您最终会遇到这样的死锁。当加载 dll、创建或销毁线程、卸载 dll、进程退出和启动时以及其他一些地方(例如 GetModuleHandle)时,会使用加载程序锁。

创建这样的死锁的一种简单方法是:

 static Foo { Foo() { HANDLE h = CreateThread(...); WaitForSingleObject(h, INFINITE); } g_foo;

也就是说,您暗示 SetEvent 确实被调用了。如果确实如此,那么可能还有更多事情要做。

您也可以!handle用来查看您正在等待的事件,看看您是否可以获得一些洞察力。另外,我再次尝试使用 ApplicationVerifier 运行,它可能会导致您遇到问题。

于 2011-03-17T01:04:14.760 回答