【问题标题】:Looking for ideas debugging a tricky windows service startup gremlin寻找调试一个棘手的 Windows 服务启动 gremlin 的想法
【发布时间】:2012-01-15 06:15:02
【问题描述】:

在过去的几个月里,我很少收到来自 QA 的关于我们的一项服务挂起的报告。在使用 WinDbg 检查挂起转储时,每次我都发现同样的事情:加载程序锁临界区被锁定,但找不到拥有线程。由于线程消失了,我能看到的唯一痕迹是它留下的全局临界区,我看不到线程线程上运行的代码,甚至看不到该线程来自哪个 DLL,它甚至可能不是其中之一我们的(即第三方供应商)。

此问题非常零星,在过去 6 个月中仅在野外自然发生过 3-4 次。在所有其他时间,服务运行完美。所以这让我相信这是某种时间/竞赛条件。

最近,我决定自己解决这个问题。我使用 WinTask 脚本设置了一台机器,该脚本不断启动/停止所述服务。好消息是我可以在 5-6 小时内重现问题。

现在进入下一部分:如何隔离它?

这是我迄今为止尝试过的:

  1. 使用 gflags 映像设置中的“调试器”字段在 cdb 启动时自动运行该服务。到目前为止,这已经运行了两天并且从未挂起,所以我认为调试器引入了足够的时间更改以使问题不可见。

  2. 已下载应用程序验证程序并将进程配置为使用它运行。发现了一个完全不相关的错误,我们在其中创建 CComBSTR 临时变量,将其分配给 VARIANT 并将变量传递给函数调用,即使 CComBSTR 到那时已长时间删除了分配的字符串。不要相信这个错误是相关的,因为字符串是只读的,并且它运行的线程不是正在死亡的线程。

我写这篇文章是为了让你们想到我没有考虑的事情。

我虽然有一个 Windows 实用程序人为地在 CPU 上施加负载并做了其他事情来使竞争条件弹出,我认为应用程序验证器做了这样的事情,但显然它没有。有谁知道我在做什么,还是我只是在做梦?

除非周末发生什么事情,否则我的下一步是禁用所有调试器,返回库存并破解一个 DllMains 以记录 THREAD_ATTACH/THREAD_DETACH 事件。至少我将能够拦截创建时即将死去的线程。这可能会有所启发。

【问题讨论】:

  • -1??为什么??我没有显示足够的细节吗?看起来我没有做足够的研究吗?当人们被软件开发问题难住时,他们不会问 stackoverflow 吗?
  • 是的,这是一个非常有效的问题。唯一能让它变得更好的是发布一些代码。我想这就是为什么有人开车过来投反对票的原因。
  • 这是一款已经上市 10 多年的生产应用程序。我什至不知道是什么 DLL 导致了这个问题,更不用说在网上发布闭源公司代码了,即使我可以复制/粘贴 5M 行源代码。不知道发生了什么变化,但大约 6 个月前我第一次看到这个问题。
  • 不要太担心投票者。我的印象是,有些人会否决/尝试关闭他们不知道答案的任何问题...祝您搜索顺利,希望有人会提供一些提示,尽管问题广泛 :)

标签: c++ multithreading debugging windbg


【解决方案1】:

我可能会尝试附加一个内核调试器,然后在 Appilcation Verifier 下运行该进程。 AV 在它持有 CS 时检查是否卸载 DLL,并终止仍持有 CS 的线程。所以这些断点应该在内核调试器中触发,然后希望你能在行动中抓住它。希望在 KD 下运行它不会像用户模式调试器那样减慢它的速度。

【讨论】:

  • 内核调试器...有趣。那是我从未涉足的整个领域。如果一切都失败了,可能是一个选择。这样我就可以让调试器在服务启动和停止时运行和监控事物。我需要第二台机器,对吧?我还需要串行电缆还是现在他们有其他方式?
  • 最简单的方法是在虚拟机中,然后VM将COM端口导出为windbg知道如何连接的命名管道。
  • 我尽量不要在这台机器上过度呼吸。 VM 可能会像 cdb 那样吓跑这个错误。所以假设我把它留在原处,串行电缆,对吧?
  • 在这种情况下是的,可以使用串行电缆、1394 或 USB,但您需要在谷歌上搜索一些好的说明,因为我的所有经验都是基于虚拟机的。
  • 虽然我没有尝试过你的建议,但你肯定给了我一个我没有考虑过的替代方案。在工具箱中再添加一个。谢谢。
【解决方案2】:

事实证明,我比我意识到的更接近解决方案。随着服务在 cdb 下运行,它改变了时间,然后使用应用程序验证程序运行它,这进一步改变了时间(启用页堆使分配变慢),我缺少的秘密成分是 prime95.exe。以高于正常的优先级运行 prime95.exe,真的搞砸了我试图不改变的任何时间,但它让问题在 15 分钟内出现。

原因:

用于从硬件板获取数据的第三方 SDK。当我们的服务启动时,我们会查询不同的捕获组件的功能。查询完成后,我们释放组件实例。显然,这个 DLL 启动了一个单独的线程,该线程获取了一个加载程序锁,然后在该线程中进行了一系列初始化。如果在此期间,我们的能力查询完成并且我们释放了组件,他们的代码将在另一个线程上调用 TerminateThread(),从而使加载程序锁永久锁定。 Prime95 减慢了一切,刚好让我能够捕捉到这种竞争条件并收到以下验证者停止消息:

=======================================
VERIFIER STOP 00000200: pid 0x1A8C: Thread cannot own a critical section. 

0000091C : Thread ID.
77E17340 : Critical section address.
00000000 : Critical section debug information address.
00000000 : Critical section initialization stack trace.

有趣的是,这个线程“消失”了,没有任何异常,所以调试器甚至不会第一次抓住任何机会。谁使用 TerminateThread????

谢谢大家的建议和支持。实际上,我开始期待在午餐时间开车去 Radioshack 购买串行电缆,然后花几天时间和 KD 一起玩。看来得等下一次了:)

【讨论】:

    【解决方案3】:

    一些随机的想法:如果附加调试器没有帮助,那么下一步就是检测(您的最后一点)。但是一个线程怎么能在不关闭整个进程的情况下死掉,你是否在某个地方捕捉到异常?您可能也想在那里登录。如果有帮助,您还可以将 WinDbg 设置为中断所有第一次机会异常。无论如何,WinDbg 输出窗口将显示第一次机会异常,即使您没有中断。

    【讨论】:

    • 它只在启动时发生,而且很少发生,所以我不能附加windbg。但这正是我试图用 cdb 实现的,它是同一调试器引擎的控制台版本。我们肯定会登录我们控制的线程,但我知道其他 API/DLL 会启动它们自己的线程,但我不知道它们在做什么。有趣的东西。
    【解决方案4】:

    我会尝试使用非侵入式调试器,看看效果如何,虽然您无法停止进程,但您应该能够看到任何调试消息以及启动和停止的任何线程,并且它应该对过程性能的影响最小。我通常使用 windbg 进行调试,但我认为 cbd 也有类似的选项。这很可能让您了解过程中发生的情况,并至少开始帮助缩小范围。您可能要确保做的一件事是重定向输出(windbg 中的 .logopen)以确保没有任何内容超出缓冲区。

    【讨论】:

    • 非侵入式调试器是什么意思? WinDbg 和 cdb 本质上是同一个调试器。 WinDbg 有 UI,cdb 是控制台,但在所有其他方面它们是相同的。我在 cdb 下启动了我的服务,它打开了一个 tcp 端口,所以我以后可以使用 windbg 连接到它。另外,正如您提到的,我使用 .logopen 来确保 cdb 看到的任何内容,我也可以通过日志文件看到。
    • 使用 -pv 命令让 cdb 以非侵入方式连接。您将无法停止线程或执行任何实际操作,但您应该能够看到更多诊断信息,并且希望不会导致任何阻止问题发生的性能问题。这是非侵入式调试的链接msdn.microsoft.com/en-us/library/ff552274(v=vs.85).aspx
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多