【问题标题】:Application crash with no explanation应用程序崩溃没有解释
【发布时间】:2011-03-07 19:21:04
【问题描述】:

我想提前道歉,因为这不是一个很好的问题。

我有一个在专用 Windows 服务器上作为服务运行的服务器应用程序。非常随机地,此应用程序崩溃并且没有提示导致崩溃的原因。

当它崩溃时,事件日志中有一个条目说明应用程序失败,但没有提供原因的线索。它还提供了有关故障模块的一些信息,但似乎不太可靠,因为故障模块通常在每次崩溃时都不同。比如最新的说是ntdll,之前的说是libmysql,之前的说是netsomething,以此类推。

应用程序中的每个线程都包装在try/catch (...)(从异常处理程序抛出/未专门捕获的任何内容)、__try/__except(结构化异常)和try/catch(特定 C++ 异常)中。应用程序是使用 /EHa 编译的,因此 catch all 也会捕获结构化异常。

所有这些异常处理程序都做同样的事情。首先,创建故障转储。其次,将条目记录到磁盘上的新文件中。第三,在应用程序日志中记录一个条目。在这些崩溃的情况下,这一切都没有发生。最底层的异常处理程序(try/catch (...))什么都不做,它只是终止线程。主应用线程处于休眠状态,没有机会抛出异常。

应用程序日志文件只是停止记录。不久之后,监视服务器的进程注意到它不再响应,发送警报,然后再次启动它。如果服务器监视器注意到服务器仍在运行,但只是没有响应,它会转储进程并报告此情况,但这并没有发生。

除了未捕获的异常,我能想到的唯一其他原因是调用exit 或类似的。搜索代码不会调用任何可能终止进程的函数。我还确保程序没有正常终止(即来自服务管理器的停止请求)。

我们已经尝试在附加windbg的情况下运行它(没有机会使用Visual Studio,开销太高),但是发生崩溃时它没有报告任何内容。

什么会导致应用程序像这样崩溃?我们开始用尽选项,并认为这可能是硬件故障,但这对我来说似乎不太可能。

【问题讨论】:

  • 日志文件流是否被刷新?
  • 您是否验证过您的线程异常处理程序确实在服务器上工作?他们可能正在尝试生成故障转储和其他日志记录,但缺乏写入其目标位置等的权限......
  • 是的,当然。他们发现了一些错误。

标签: c++ crash


【解决方案1】:

如果您的应用正在蒸发且未生成转储文件,则很可能正在生成您的应用无法(或无法)处理的异常。这可能在两种情况下发生:

1) 生成了一个顶级异常,并且该异常类型没有匹配的 catch 块。

2) 您有一个匹配的 catch 块(例如 catch(...)),但您正在该处理程序中生成异常。发生这种情况时,Windows 将从您的程序中剔除骨骼。您的应用程序将不复存在。不会生成转储,并且几乎不会记录任何内容,这是 Windows 为防止流氓程序破坏整个系统而做出的最后努力。

关于catch(...) 的注释。这显然是邪恶的。生产代码中应该(几乎)永远不会有catch(...)。写catch(...) 的人通常会争论以下两件事之一:

“我的程序不应该崩溃。如果发生任何事情,我想从异常中恢复并继续运行。这是一个服务器应用程序!ZOMG!”

-或-

“我的程序可能会崩溃,但如果真的崩溃了,我想在崩溃的过程中创建一个转储文件。”

前者是一种幼稚且危险的态度,因为如果您确实尝试处理并从每一个异常中恢复,那么您将对您的运营足迹做一些不好的事情。也许你会咀嚼堆,保持应该关闭的资源打开,创建死锁或竞争条件,谁知道呢。您的程序最终将遭受致命的崩溃。但到那时,调用堆栈将与导致实际问题的原因不再相似,并且任何转储文件都无法帮助您。

后者是一种高贵而强大的方法,但它的实施比看起来要困难得多,而且充满危险。问题是您必须避免在异常处理程序中生成任何进一步的异常,并且您的机器已经处于非常不稳定的状态。通常完全安全的操作突然变成了手榴弹。 newdelete、任何 CRT 函数、字符串格式化,甚至像 char buf[256] 这样简单的基于堆栈的分配都可能使您的应用程序运行 >POOF

此外,可能会发生catch 块根本无法捕获的异常,例如 SEH 异常。出于这个原因,我总是编写一个未处理的异常处理程序,并通过SetUnhandledExceptionFilter 将它注册到 Windows。在我的异常处理程序中,我通过静态分配分配了我需要的每个字节,甚至在程序启动之前。在此处理程序中要做的最好(最强大)的事情是触发一个单独的应用程序启动,这将从您的应用程序外部生成一个 MiniDump 文件。但是,如果您非常小心不要直接或间接调用任何 CRT 函数,则可以从处理程序本身中生成 MiniDump。基本上,如果它不是您调用的 API 函数,它可能是不安全的。

【讨论】:

  • 最困扰我的是底层的catch-all没有主体,存在只是为了防止整个应用程序宕机,所以我们至少可以看到线程的缺失。未捕获的异常不应该导致应用程序关闭。即使更高级别的 catch/except 抛出异常,也应该被捕获。
  • 空洞的包罗万象可能只是掩盖了真正的问题。例如,如果某些事情导致堆损坏,您的 catch-call 将使程序继续运行,但它仅在 2 个轮子上运行,并且最终会崩溃。你最好摆脱所有空的包罗万象并用我上面提到的未处理异常处理程序替换它们,在处理程序中生成一个小型转储。
  • 我很确定问题是堆损坏。你有什么提示可以找到它发生的地方吗?内存分析器和类似的东西没有用,因为我们不能在生产服务器上使用它们,而且我们的测试服务器上也不会出现这个问题。
  • 您可能会发现 pageheap.exe 很有用。见以下两个链接:support.microsoft.com/default.aspx?scid=kb;en-us;286470blogs.msdn.com/b/akshayns/archive/2007/11/24/…
  • 感谢您的帮助。对于将来阅读本文的任何人,pageheap 已被 gflags 取代。据我所知,Application Verifier 也有同等的功能。
【解决方案2】:

我见过由于内存损坏而发生的此类崩溃。您是否在 Purify 之类的内存调试器下运行您的应用程序,看看这是否能揭示潜在的问题区域?

【讨论】:

  • 我们在 Purify 下运行它,但不能在生产中使用它。不过,它在我们的测试服务器上没有发现任何问题。
  • 旁注,这是一个 64 位应用程序。我认为我们必须构建 32 位版本,因为 Purify 无法检测 64 位进程。
  • @dauphic,我假设您已经净化了发布版本?除非有人“聪明”地使用指针(例如将指针转换为太小而无法容纳 64 位指针的整数类型,然后再将其转换回指针),否则 32 位构建应该没有任何区别。
  • 是的,这是一个发布版本。代码都是用 C++ 编写的,而且经过了我数不清的静态分析工具,应该没有这样的不安全操作。
【解决方案3】:

【讨论】:

  • 明天我会尝试为 SIGABRT 添加信号处理程序,谢谢您的建议。
【解决方案4】:

这不是一个很好的答案,但希望它可以帮助你。

我曾经遇到过这些症状,在花了一些痛苦的时间寻找原因之后,我发现了一个关于 Windows 的有趣的事情(来自MSDN):

取消引用可能无效 指针可以禁用堆栈扩展 在其他线程中。一根筋很累 它的堆栈,当堆栈扩展有 被禁用,导致 立即终止父母 进程,不弹出错误窗口 或诊断信息。

事实证明,由于线程之间的一些错误设计的数据共享,我的一个线程最终会取消引用或多或少的随机指针 - 当然有时它会碰到堆栈顶部附近的区域。追踪这些指针非常有趣。

Raymond Chen的IsBadXxxPtr should really be called CrashProgramRandomly有一些技术背景

【讨论】:

    【解决方案5】:

    响应较晚,但可能对某人有所帮助:每个 Windows 应用程序都对随时可以打开的句柄数量有限制。我们有一项服务在某些情况下没有释放句柄,该服务会在几天后或有时几周后消失(取决于服务的使用情况)。 发现漏洞非常有趣:D(使用任务管理器查看线程数、句柄数、GDI 对象等)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-15
      • 1970-01-01
      • 1970-01-01
      • 2011-06-03
      • 2015-02-18
      相关资源
      最近更新 更多