应用程序崩溃,没有任何解释

6

提前道歉,因为这不是一个非常好的问题。

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

当它崩溃时,事件日志中有一条记录说明应用程序失败了,但没有任何线索表明原因。它还提供了一些关于故障模块的信息,但似乎不太可靠,因为每次崩溃时故障模块通常都不同。例如,最新的说是ntdll,之前的说是libmysql,再之前的说是netsomething等等。

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

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

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

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

我们尝试使用windbg附加(无法使用Visual Studio,开销太大),但当崩溃发生时它没有报告任何内容。

什么原因会导致应用程序像这样崩溃?我们开始没有更多的选择,考虑可能是硬件故障,但这对我来说似乎有点不太可能。


日志文件流是否被刷新? - James
你是否验证了服务器上的线程异常处理程序是否实际起作用?它们可能会尝试生成崩溃转储和额外日志,但缺乏写入目标位置的权限等。 - Jon
当然可以。他们发现了一些漏洞。 - Collin Dauphinee
5个回答

9
如果您的应用程序正在消失而没有生成转储文件,那么很可能是正在生成一个异常,而您的应用程序无法处理。这种情况可能发生在两种情况下:
1)生成顶级异常并且没有匹配该异常类型的catch块。
2)您有一个匹配的catch块(例如catch(...)),但是您在该处理程序内部生成了异常。当发生这种情况时,Windows将从您的程序中删除骨头。您的应用程序将简单地停止存在。不会生成任何转储,并且几乎没有记录任何内容。这是Windows最后一次努力防止流氓程序使整个系统崩溃。
关于catch(...)的说明。这是绝对邪恶的。生产代码中几乎永远不应该有catch(...)。编写catch(...)的人通常会争论以下两点:
“我的程序永远不应该崩溃。如果发生任何事情,我希望从异常中恢复并继续运行。这是服务器应用程序!ZOMG!”
-或-
“我的程序可能会崩溃,但是如果确实如此,我希望在崩溃时创建一个转储文件。”
前者是一种天真而危险的态度,因为如果您尝试处理和恢复每个异常,您将会对操作足迹做出一些糟糕的事情。也许您会吞噬堆,保持应该关闭的资源打开,创建死锁或竞争条件,谁知道呢。您的程序最终将遭受致命崩溃。但是到那时,调用堆栈将与实际问题的原因毫不相似,并且永远不会有转储文件帮助您。
后者是一种高尚而强大的方法,但其实现比看起来更困难,并且充满了危险。问题在于,您必须避免在异常处理程序中生成任何进一步的异常,而您的机器已经处于非常不稳定的状态。通常完全安全的操作突然变成手榴弹。new、delete、任何CRT函数、字符串格式化,甚至简单的基于堆栈的分配(例如char buf [256])都可能使您的应用程序消失。您必须假设堆栈和堆都处于废墟之中。没有分配是安全的。
此外,有一些异常情况是catch块无法捕获的,例如SEH异常。因此,我总是编写未处理异常处理程序,并通过SetUnhandledExceptionFilter向Windows注册它。在我的异常处理程序中,我通过静态分配为程序启动前所需的每一个字节都进行了分配。在此处理程序中最好(最强大)的做法是触发一个单独的应用程序启动,该应用程序将从应用程序外部生成MiniDump文件。但是,如果您非常小心不直接或间接地调用任何CRT函数,则可以从处理程序本身生成MiniDump。基本上,如果您调用的不是API函数,则可能不安全。

最让我困扰的是底层的 catch-all 没有实际内容,只是为了防止整个应用程序崩溃,这样我们至少可以看到线程的缺失。未捕获的异常不应该导致应用程序崩溃。即使更高级别的 catch/except 抛出异常,也应该被捕获。 - Collin Dauphinee
空的 catch-all 可能只是掩盖了真正的问题。例如,如果某些东西导致堆栈损坏,你的 catch-call 将让程序继续运行,但它只在两个轮子上运行,并最终会崩溃。你最好摆脱所有空的 catch-all,并用我上面提到的未处理异常处理程序替换它们,在处理程序中生成 minidump。 - John Dibling
我相信问题是堆损坏。你有什么提示可以找到它发生的位置吗?内存分析工具和类似工具并不有用,因为我们不能在生产服务器上使用它们,而且这个问题在我们的测试服务器上也没有出现。 - Collin Dauphinee
你可能会发现pageheap.exe很有用。请参阅以下两个链接:http://support.microsoft.com/default.aspx?scid=kb;en-us;286470 http://blogs.msdn.com/b/akshayns/archive/2007/11/24/page-heap-dumps.aspx - John Dibling
1
感谢您的帮助。对于未来阅读此内容的任何人,PageHeap已被GFlags替代。据我所知,应用程序验证器也具有等效功能。 - Collin Dauphinee

1
我曾经见过这样的崩溃是由于内存损坏导致的。你是否已经在像Purify这样的内存调试器下运行了你的应用程序,以查看可能存在问题的区域?

我们在Purify下运行了它,但我们不能在生产中使用它。尽管在我们的测试服务器上没有发现任何问题。 - Collin Dauphinee
附注:这是一个64位应用程序。我认为我们必须创建一个32位构建,因为Purify无法检测64位进程。 - Collin Dauphinee
@dauphic,我假设你已经净化了发布版本?32位构建不应该有任何区别,除非有人在指针上“聪明”(例如将指针转换为太小以容纳64位指针的整数类型,然后将其转换回指针)。 - Timo Geusch
是的,这是一个发布版本。代码都是相当不错的C++编写的,并且经过了比我能数得上来的更多的静态分析工具,不应该有像那样的不安全操作。 - Collin Dauphinee

1

明天我会尝试为SIGABRT添加信号处理程序,感谢您的建议。 - Collin Dauphinee

1

这不是一个很好的答案,但希望它能帮到你。

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

解引用可能无效的指针会禁用其他线程的堆栈扩展。当堆栈扩展被禁用时,一个耗尽其堆栈的线程会导致父进程立即终止,而没有弹出错误窗口或诊断信息。

事实证明,由于一些线程之间的设计不良的数据共享,我的某个线程最终会解引用更多或更少的随机指针 - 当然有时会命中堆栈顶部附近的区域。追踪这些指针非常有趣。

Raymond Chen在IsBadXxxPtr应该真正被称为CrashProgramRandomly中提供了一些技术背景。


0
回复有点晚,但或许对某些人有帮助:每个 Windows 应用程序都有一个限制,即同时打开的句柄数量。我们遇到了一个服务在某些情况下没有释放句柄,该服务会在几天或几周后消失(取决于服务的使用情况)。 找到泄漏问题非常有趣 :D (使用任务管理器查看线程计数、句柄计数、GDI 对象等)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接