【问题标题】:.NET Windows Service crashes while dispatching a Windows Message.NET Windows 服务在分派 Windows 消息时崩溃
【发布时间】:2011-10-21 14:40:43
【问题描述】:

我有一个 .NET Windows 服务的主要问题。它运行在具有非常不同配置的多台服务器上。该服务似乎容易在某些服务器上崩溃,但在其他服务器上稳定。最近引入了不稳定性,但到目前为止情况尚不清楚。我们有运行 Windows 2003 / Windows 2003 R2 / Windows 2008 的服务器。其中大部分都已完全更新。

我们尝试针对不同的目标框架版本 (2.0 / 3.5 / 4.0) 构建服务,但这并没有什么不同。具有不稳定服务的机器在每个版本的框架中都不稳定。我已经尝试修复 .NET 框架,但这也没有什么不同。据我所知,整个服务及其依赖项都在托管代码中。

我还尝试在命令行版本中运行服务器代码。这似乎运行稳定。我们现在将其用作解决方法。但是,问题与用户帐户无关。该服务通常作为“本地服务”运行。我试图让它在本地管理员帐户下运行,这是我用来运行命令行版本的帐户。但是服务仍然不稳定。

到目前为止,我已经能够在其中一台服务器上创建可重现的情况: - 在服务器上启动服务。 - 在同一服务器上的新 RDP 会话中以域用户身份登录。 - 启动我们的客户端软件,该软件在该会话中通过 TCP 远程访问我们的服务。 - 关闭客户端和会话。 - 与服务器上的域用户打开一个新的 RDP 会话。 - 服务瞬间崩溃!

请注意,当域用户登录到新的 RDP 会话时,服务会崩溃。那时我们的客户端软件还没有在那个会话中运行。如果我在第一次会话中不打开客户端并使用 TCP 远程访问服务,则该服务在第二次登录期间不会崩溃。如果我以本地管理员身份打开会话,服务也不会崩溃。

我已经能够将本机调试器 (OllyDbg) 附加到崩溃的服务。当尝试在地址 0x4bcdcee9 处执行时,它会因访问冲突而崩溃。该地址在所有服务器和配置上都是相同的(我每次都在事件日志中看到该地址)。我查看了崩溃线程的堆栈。该线程似乎是在崩溃之前创建的。首先它尝试加载 Ole32.dll。它从 Ole32 运行一些代码,然后我看到这些函数被调用:

  • User32.SetTimer
  • User32.GetMessageW
  • User32.TranslateMessage
  • User32.DispatchMessageW

崩溃发生在 DispatchMessageW 中。我可以在堆栈上看到 DispatchMessageW 的 *MSG 参数。好像这样通过了:

  • hWnd = 0x00090082
  • 消息 = 0x0000001e
  • wParam = 0x00000000
  • lParam = 0x00000000

我试过 Spy++。但它似乎没有在 Windows 服务中检测到任何 hWnd。

所以,服务接收到这个消息,尝试解析和分发它,每次都调用 0x4bc4cee9,这是未映射的内存,然后崩溃。

编辑:根据汉斯的建议,我调查了系统事件。我调试了服务。我在我的服务可执行文件中添加了一个额外的服务,这样我就可以启动辅助服务,然后附加一个调试器,然后启动真正的服务。这样,我什至可以调试服务的 OnStart。我在 SetWindowsHookA、SetWindowsHookW、SetWindowsHookExA 和 SetWindowsHookExW 上设置了断点,但没有一个被命中!?

编辑2:我检查了我所有的笔记,发现我跳到了错误的结论,因为我的笔记有错字:-S 无论如何,崩溃的地址是0x4bc4cee9。在执行的某个时刻,msado15.dll 会在那里加载。我可以看到,当客户端与服务器断开连接时,调试器中有 2 个托管异常。不久之后,我看到一条 WM_Timer 消息,它由调度程序处理并调用 CoFreeUnusedLibraries()。这将导致卸载 msado15.dll。我在反汇编程序中打开了 msado15.dll 并从 Microsoft 加载了符号。 DLL 是 Microsoft 数据访问组件 (MDAC) 2.8 SP1 的一部分。版本为2.82.4795.0,表示为最新版本,2011年1月发布。ADOConnection和ADORecordset有Advise()和Unadvise()函数。 Advise() 调用 InitAsyncEvents() 并调用 RegisterClassEx()。传递给 RegisterClassEx() 的 WndProc 是 FireEventOnMainThread(),它位于 0x4bc4cee9!我可以看到那里的功能!应该发生的是,当对象被释放时,应该调用 Unadvise() 和 DestroyAsyncEvents() 和 UnregisterClassEx()。但不知何故,这并没有发生。 DLL 在取消注册类之前被卸载。这会导致下一个事件发生崩溃。这可能与 2 个托管异常有关。我会进一步调查。

堆栈跟踪:http://pastebin.com/dsSjMe4Y

日志:http://pastebin.com/qD2MXvHd

我非常感谢您在这件事上的一些指导。比如,哪个进程可以发送这个消息?服务怎么可能完全错误地发送这个?如何避免这种情况?

谢谢你, 希斯克利夫

【问题讨论】:

  • 很长的问题,大部分信息都是有用的。有用的是在崩溃和异常信息时有一个调用堆栈。确保您已正确加载符号。
  • WM_TIMECHANGE 是广播消息。转到所有顶级窗口。听起来你曾经有一个,然后带有窗口过程的 DLL 被卸载而没有很好地关闭窗口。 .NET 中的 SystemEvents 顺便说一句,需要显式取消注册的静态事件。
  • @SevaTitov 我添加了一个堆栈跟踪。所有其他信息似乎与我有关。
  • @HansPassant 非常感谢您提供的信息!这对我来说似乎是合理的。但我在过去 2 天里试图确认它,但我没有成功。我在开篇文章中添加了更多信息。希望您能进一步帮助我。

标签: .net windows-services crash


【解决方案1】:

我发现了问题。我花了将近 8 天的时间来确定它并创建一个解决方法!

直到 6.0 的所有 ADODB 版本都有一个严重的错误! ADODB 2.8 是 MDAC 2.8 的一部分(适用于 XP 和 Win2003),ADODB 6.0 是 Vista/Win2008 的一部分,ADODB 6.1 是 Win7/Win2008R2 的一部分。核心 DLL 是 msado15.dll。实例化 Connection 或 Recordset 类时,它会使用 RegisterClass() 注册,并且它有一个称为 __FireEventOnMainThread() 的 WndProc。再次释放所有 COM 对象后,引用计数设置为 0。当调用 Ole32!CoFreeUnusedLibraries() 时,它将调用所有 COM DLL 的 DllCanUnloadNow()。 DllCanUnloadNow() 检查引用计数,当它为 0 时返回 0,表示它可以卸载。在 ADODB 6.1(仅针对 Win7 和 Win2008R2 发布)中,Microsoft 在 DllCanUnloadNow() 中实施了修复。他们检查 AsyncEventsWnd,如果它仍然存在,他们将不会卸载 DLL。但真正的 bug 仍然存在于 COM 对象处理中。引用计数减少了,但由于某种原因未调用 UnregisterClass()。当卸载 DLL 并发送广播事件时,应用程序将遇到访问冲突,因为 WndProc 不再位于内存中。碰撞!在服务的情况下,一个 Ole32!CDllHost 被实例化(不确定在哪里)。此类使用 TimerProc STAHostTimerProc() 启动一个计时器,每 300 秒触发一次。 STAHostTimerProc() 调用 CoFreeUnusedLibraries()。有许多不同的广播消息。例如,当一个新的用户会话在终端服务器上启动时,它将广播 WM_TIMECHANGE。因此,在 Windows 至 Vista/Win2008 的计算机上,当应用程序创建 ADODB.Connection 或 ADODB.Recordset 并创建 Ole32!CDllHost 然后释放所有 COM 对象,然后等待计时器卸载 msado15.dll 然后等待广播消息,该应用程序将崩溃!

微软在 MDAC 6.1 中修复了这个问题,这很糟糕,但他们没有为早期版本发布修复程序。所有旧操作系统都会受到影响。

作为一种变通方法,我们将通过创建静态 ADODB.Connection 对象来避免 ADO COM 对象的引用计数变为 0。

【讨论】:

    猜你喜欢
    • 2012-04-25
    • 1970-01-01
    • 2018-01-18
    • 1970-01-01
    • 1970-01-01
    • 2018-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多