解决随机崩溃
Posted
技术标签:
【中文标题】解决随机崩溃【英文标题】:Solving random crashes 【发布时间】:2011-03-27 03:40:34 【问题描述】:我的 C++ 应用程序随机崩溃,它可能一个月不会崩溃,然后在一小时内崩溃 10 次,有时它可能会在启动时崩溃,而有时它可能会在运行几个小时后崩溃(或根本不会崩溃)。
我在 GNU/Linux 上使用 GCC,在 Windows 上使用 MingW,因此我无法使用 Visual Studio JIT Debug...
我不知道如何继续,随机查看代码是行不通的,代码很大(而且好的部分不是我的工作,而且上面有很多遗留的东西),而且我也不知道如何重现崩溃。
编辑:很多人提到...我如何制作核心转储、小型转储或任何转储?这是我第一次需要事后调试。
EDIT2:实际上,DrMingw 捕获了一个调用堆栈,没有内存信息......不幸的是,调用堆栈对我没有多大帮助,因为在接近尾声时突然它进入了一些我没有的库(或其他东西)有调试信息,只产生一些十六进制数字......所以我仍然需要一些体面的转储来提供更多信息(特别是关于内存中的内容......具体来说,出现“访问冲突”错误的地方是什么)
另外,我的应用程序使用 Lua 和 Luabind,可能错误是由 .lua 脚本引起的,但我不知道如何调试。
【问题讨论】:
它是多线程应用程序吗? 我怀疑崩溃是随机的 原因不太可能是随机的(悬挂指针、双重删除、内存损坏),但症状将是随机的(或者更具体地说是非确定性的) Mr.Speeder,您打算如何进行?这是一个需要解决的有趣问题。 我仍然有随机崩溃,这非常罕见(即使尝试触发它们,最近也失败了......),我也开始保存所有 Dr. MingW 崩溃转储,每次它报告一个不同的东西(即使它以相同的方式崩溃)并且一旦 Dr.MingW 本身崩溃,同时试图加载一个“无限”堆栈(即:两个十六进制代码在堆栈中交替,永远......过了一会儿它开始打印它们的内存用完了,然后自己崩溃了) 【参考方案1】:试试Valgrind(它是免费的,开源的):
目前的 Valgrind 发行版 包括六种生产质量工具: 一个内存错误检测器,两个线程 错误检测器,缓存和 分支预测分析器,一个 调用图生成缓存分析器, 和一个堆分析器。它还包括 两个实验工具:一个 堆/堆栈/全局数组溢出 检测器和 SimPoint 基本模块 矢量生成器。它运行在 以下平台:X86/Linux, AMD64/Linux、PPC32/Linux、PPC64/Linux、 和 X86/Darwin (Mac OS X)。
Valgrind Frequently Asked Questions
包的Memcheck部分可能是开始的地方:
Memcheck 是一个内存错误检测器。 它可以检测以下问题 在 C 和 C++ 程序中很常见。
访问你不应该访问的内存,例如溢出和运行不足的堆 块,超出顶部 堆栈,并在它之后访问内存 已被释放。
使用未定义的值,即尚未初始化的值,或 来自其他的 未定义的值。
堆内存释放不正确,例如两次释放堆块,或 malloc/new/new[] 的不匹配使用 与免费/删除/删除[]
memcpy 和相关函数中的 src 和 dst 指针重叠。
内存泄漏。
【讨论】:
+1。 Valgrind 通常可以零努力地为您提供错误的行号。就像魔术一样。 我也会为此 +1。最近才开始使用它,我发现它几乎是必不可少的。 Valgrind 很棒,但遗憾的是不会在 Windows/MINGW 上捕获错误,因为它不存在。可能的替代品:* ***.com/questions/413477/… @Mitch:我的评论并不否认。 我的问题正是因为我不能一直使用 Valgrind 之类的... Valgrind 使程序 SLOOOOOOW,令人难以置信的 SLOOOOW。崩溃可能需要数小时或数月……我无法在 Valgrind 上运行程序一整个月……【参考方案2】:首先,您很幸运,您的进程在短时间内多次崩溃。这应该可以很容易地进行。
这就是你继续前进的方式。
获取故障转储 隔离一组潜在的可疑函数 加强状态检查 重复获取故障转储
首先,您确实需要获取故障转储。
如果您在崩溃时没有获得崩溃转储,请先编写一个生成可靠崩溃转储的测试。
使用调试符号重新编译二进制文件或确保您可以使用调试符号分析故障转储。
查找可疑函数
鉴于您有一个故障转储,请在 gdb 或您最喜欢的调试器中查看它并记住显示所有线程!有问题的可能不是您在 gdb 中看到的线程。
查看 gdb 说您的二进制文件崩溃的位置,隔离一些您认为可能导致问题的函数。
查看多个崩溃并隔离在所有崩溃中通常处于活动状态的代码部分可以真正节省时间。
加强状态检查
崩溃通常是由于某些不一致的状态而发生的。最好的方法通常是收紧国家要求。您可以通过以下方式执行此操作。
对于您认为可能导致问题的每个函数,记录输入或对象在进入该函数时必须具有的合法状态。 (对退出函数时它必须具有的合法状态执行相同操作,但这不是太重要)。
如果函数包含循环,请记录在每次循环迭代开始时它需要具有的合法状态。
为所有此类合法状态表达式添加断言。
重复
然后重复这个过程。如果它仍然在您的断言之外崩溃,请进一步收紧断言。在某些时候,进程会因断言而崩溃,而不是因为随机崩溃。此时,您可以集中精力尝试找出是什么让您的程序从进入函数时的合法状态变为断言发生时的非法状态。
如果您将断言与详细日志记录配对,则应该更容易遵循程序的操作。
【讨论】:
【参考方案3】:如果所有其他方法都失败了(尤其是在调试器下的性能不可接受的情况下),则进行大量日志记录。从入口点开始——应用程序是事务性的吗?记录每笔交易。记录您的关键对象的所有构造函数调用。由于崩溃是间歇性的,因此请记录对所有可能不会每天调用的函数的调用。
您至少会开始缩小崩溃可能发生的位置。
【讨论】:
我以前也这样做过,但我注意到,日志记录经常会导致程序执行 I/O,这有时会阻止一些错误/竞争条件的发生。我相信当你有一个确定性发生的错误时,日志记录是一种更有效的技术。 是的,你一定喜欢那些黑森虫。 我讨厌 Heisenbugs/Schrödingbugs。摆脱它们以便行为是可预测的(可能导致崩溃,但随后有一个已知原因)非常重要,因为这几乎总是会在不久之后导致完全工作的代码......【参考方案4】:在调试器下启动程序(我确定有一个调试器连同GCC和MingW),等到它在调试器下崩溃。在崩溃点,您将能够查看失败的具体操作,查看汇编代码、寄存器、内存状态 - 这通常可以帮助您找到问题的原因。
【讨论】:
我做不到,调试器下的性能太慢了,根本无法使程序有用,而且在崩溃之前可能需要 LOOOOONG 时间。所以这就要求我一直使用GDB,对于这个项目来说这是完全不合理的。 @speeder:我个人从未见过在调试器下运行时速度有任何差异。我的意思不是一步一步,我的意思是运行并让它一直运行直到它崩溃。 我通常也不会,但是我的程序调试版本二进制文件有 140Mb,它还加载了更多 100Mb 的数据(动态生成的好部分),当我的程序加载时 GDB 本身需要更多 200Mb ...这导致操作系统对页面文件发疯,而且我的内存也不是最好的(实际上它很旧,总共 2GB...) @speeder:您可以执行以下操作:使用 .pdb 和优化编译程序。这样它就不会像完整的调试版本那样臃肿,并且当它在调试器下崩溃时,您仍然可以看到调用堆栈。 没有什么能阻止您调试真正的发布模式构建。您只需要一个将从调试版本生成的 PDB 文件,您可以从那里窃取...但更好的是,设置您的发布版本以生成调试信息。但是请注意,优化会破坏您的调试体验,变量会丢失等等,但您仍然可以通过这种方式获得很多帮助。【参考方案5】:在我工作的地方,崩溃的程序通常会生成一个可以在 windbg 中加载的核心转储文件。
然后我们就有了程序崩溃时的内存图像。您无能为力,但至少它为您提供了最后一个调用堆栈。一旦你知道了崩溃的函数,你就可以追踪问题,至少你可以将问题减少到一个更可重现的测试用例。
【讨论】:
你能提供一些细节吗?我关于 mingw 的最新信息是 mingw-gcc 二进制文件无法生成核心转储,而 windbg 对 mingw 二进制文件几乎没有什么可说的,因为它们使用了 windbg 无法理解的 stabs 调试格式。 @Luther Blissett,不幸的是,核心转储文件似乎是由系统生成的(我在一家非常大的公司工作,我不是实际设置它的团队的一员)。但是我确信我的测试二进制文件(使用 mingw 创建)在崩溃时被“核心转储”了,我非常怀疑负责团队为此添加了一个特殊情况。 我相信这些被称为(MS 术语)“Minidumps”。 windbg 有一个设置来读取这些“事后”并可以揭示“东西”。【参考方案6】:听起来您的程序正在遭受内存损坏。如前所述,您在 Linux 上的最佳选择可能是 valgrind。但这里有另外两个选择:
首先使用debug malloc。几乎所有 C 库都提供了一个调试 malloc 实现来初始化内存(普通 malloc 将“旧”内容保存在内存中),检查分配块的边界是否损坏等等。如果这还不够,还有多种第三方实现可供选择。
您可能想看看 VMWare Workstation。我没有那样设置它,但是从他们的营销材料中,他们支持一种相当有趣的调试方式:在“记录”虚拟机中运行被调试者。当内存损坏发生时,在损坏的地址处设置一个内存断点,然后在虚拟机中返回时间到那块内存被覆盖的那一刻。请参阅this PDF,了解如何使用 Linux/gdb 设置重放调试。我相信 Workstation 7 有一个 15 或 30 天的演示,这可能足以从您的代码中消除这些错误。
【讨论】:
【参考方案7】:这些类型的错误总是很棘手 - 除非您可以重现错误,否则您唯一的选择是对您的应用程序进行更改,以便记录额外的信息,然后等到错误再次发生。
有一个名为 Process Dumper 的出色工具,您可以使用它来获取遇到异常或意外退出的进程的故障转储 - 您可以要求用户安装该工具并为您的应用程序配置规则。
或者,如果您不想要求用户安装其他应用程序,您可以让您的应用程序监控异常并通过调用 MiniDumpWriteDump 自行创建转储。
另一种选择是改进日志记录,但是弄清楚要记录哪些信息(而不只是记录所有内容)可能会很棘手,因此可能需要多次迭代崩溃 - change记录以找出问题。
正如我所说,这类错误总是难以诊断 - 根据我的经验,它通常需要数小时查看日志和崩溃转储,直到你突然发现一切都在发生的灵光乍现的时刻感觉 - 关键是收集正确的信息。
【讨论】:
【参考方案8】:您已经听说过如何在 linux 下处理这个问题:检查核心转储并在 valgrind 下运行您的代码。因此,您的第一步可能是在 Linux 下查找错误,然后检查它们是否在 mingw 下消失。由于这里没有人提到 mudflap,所以我会这样做:如果您的 Linux 发行版提供了 mudflap,请使用它。 mudflap 通过跟踪指针实际允许指向的信息来帮助您捕获指针滥用和缓冲区溢出:
http://gcc.gnu.org/wiki/Mudflap_Pointer_Debugging对于 Windows:有一个用于 mingw 的 JIT 调试器,称为 DrMingw:
http://code.google.com/p/jrfonseca/wiki/DrMingw【讨论】:
噢……那东西确实有效!有一个我知道如何导致的特定崩溃(但我不知道如何修复),我曾经测试过 DrMingw 太糟糕了,它没有提供有关内存的信息,只提供有关调用堆栈的信息...... :(跨度> 【参考方案9】:在valgrind
下的 Linux 上运行应用程序以查找内存错误。随机崩溃通常归结为内存损坏。
使用 valgrind 的 memcheck 工具修复您发现的所有错误,然后希望崩溃会消失。
如果整个程序在 valgrind 下运行时间过长,则将功能拆分为单元测试,然后在 valgrind 下运行那些,希望您能找到导致问题的内存错误。
如果没有,请确保启用 coredumps (ulimit -a
),然后当它崩溃时,您将能够找到 gdb
的位置。
【讨论】:
valgrind 终于可以在 Windows 上运行了吗?多年来我一直在寻找它。 @ereOn:不幸的是,它没有,但 OP 也在使用 Linux,所以它应该是他的一个选择。目前只有 Linux 和 OS X 真正得到支持,尽管 FreeBSD 和 NetBSD 有非官方的移植。见valgrind.org/info/platforms.html Valgrind 对这个项目毫无用处,因为它运行得太慢了,直到发生一些 valgrind 可以捕获的错误之前,我可能已经老死了...... 不幸的是,valgrind
或其他一些内存检查器是我能建议的最好的东西。否则你几乎必须重写应用程序。【参考方案10】:
这听起来像比赛条件之类的棘手问题。
我建议您创建一个调试版本并使用它。您还应该确保在程序崩溃时创建核心转储。
下次程序崩溃时,您可以在 coredump 上启动 gdb 并查看问题所在。这可能是一个连续的错误,但这应该可以帮助您开始。
【讨论】:
它可能是竞争条件或任何其他导致未定义行为的情况。我们没有足够的信息来进行有根据的猜测。 是的,这就是核心转储应该提供帮助的原因。他说他不想随便看源代码,我同意。核心转储应该让他开始。【参考方案11】:我要做的第一件事是使用 gdb(Windows 和 Linux)调试核心转储。第二个是运行一个程序,如 Lint、Prefast (Windows)、Clang Analyzer 或其他一些静态分析程序(为大量误报做好准备)。第三件事是某种运行时检查,例如 Valgrind(或其紧密变体)、Microsoft Application Verifier 或 Google Perftools。
还有日志记录。不必去磁盘。例如,您可以登录到全局 std::list<std::string>
,它将被修剪到最后 100 个条目。当捕获到异常时,显示该列表的内容。
【讨论】:
+1 用于应用程序验证程序。如果 valgrind 太慢,我实际上会从那里开始。【参考方案12】:开始记录。将日志记录语句放在您认为代码不稳定的地方。专注于测试代码,并重复,直到你将问题缩小到一个模块或一个函数。
在任何地方放置断言!
当你在做的时候,只在断言中放一个表达式。
为您认为失败的代码编写单元测试。这样,您就可以将代码与运行时环境的其余部分隔离开来。
编写更多自动化测试来运行有问题的代码。
不要在失败的错误代码之上添加更多代码。这只是一个愚蠢的想法。
了解如何编写小型转储文件并进行事后调试。看来这里的其他人已经解释得很好了。
从尽可能多的不同方法中运行错误代码,以便隔离错误。
使用调试版本。如果可能,在调试器下运行调试构建。
如果可能的话,通过删除二进制文件、模块等来精简您的应用程序,以便您可以更轻松地尝试重现错误。
【讨论】:
【参考方案13】:这里有很多很好的答案,但是还没有人涉及到Lua角度。
Lua 通常表现良好,但它仍然可能导致内存损坏或崩溃,例如Lua 栈上溢或下溢,或者执行了错误的字节码。
您可以做的一件简单的事情来检测许多此类错误,即在 luaconf.h 中定义 lua_assert 宏。定义这个(例如标准 C 的断言)将启用 Lua 核心内部的各种健全性检查。
【讨论】:
【参考方案14】:您可能犯了一个内存错误,您以某种方式将一些值放入未分配的空间,这是随机崩溃的一个很好的理由,很长一段时间没有人尝试使用该内存,因此不会出现错误,您可以采取看看你分配内存的地方并检查你广泛使用指针的地方。 除此之外,正如其他人指出的那样,您应该在屏幕和文件中使用大量日志记录。
【讨论】:
【参考方案15】:另一项基本检查:确保您完全重建您的项目。如果您一直在调整各种文件(尤其是头文件)并进行部分构建,那么如果您的构建依赖关系不完美,事情就会变得一团糟。完全重建只是消除了这种可能性。
对于 Windows,请查看 Microsoft 的 Debugging tools for Windows,尤其是他们的 gflags 工具。
【讨论】:
有时只是因为“错误构建”而导致崩溃。 Clean & Rebuild 将解决此问题。【参考方案16】:另外两个指针/想法(除了核心转储和 Linux 上的 valgrind):
1) 试试诺基亚的“Qt Creator”。它支持mingw,可以作为事后调试器。
2) 如果可行,也许只是在 gdb 中不断地运行应用程序?
【讨论】:
【参考方案17】:如果您的应用程序不是特定于 Windows 的,您可以尝试在其他平台上编译和运行您的程序,例如 Linux(不同的发行版、32/64 位……如果您有条件的话)。这可能有助于触发程序的错误。当然你应该使用其他帖子中提到的工具,例如gdb,valgrind等。
【讨论】:
以上是关于解决随机崩溃的主要内容,如果未能解决你的问题,请参考以下文章