解决随机崩溃问题
我在我的C++应用程序上遇到了随机崩溃问题,有时可能一个月都不会崩溃,而有时可能会在一个小时内崩溃10次,有时可能会在启动时崩溃,有时可能是在几个小时的运行后崩溃(或根本不崩溃)。
我在GNU / Linux上使用GCC,在Windows上使用MingW,因此无法使用Visual Studio JIT Debug ...
我不知道该怎么办,随机查看代码不会起作用,代码非常庞大(其中很大一部分不是我的工作),而且我也不知道如何重现崩溃。
编辑:很多人提到了......我如何制作核心转储、迷你转储或任何转储?这是我第一次需要进行事后调试。
编辑2:实际上,DrMingw捕获了一个调用堆栈,没有内存信息......不幸的是,调用堆栈对我帮助不大,因为接近结尾时它突然进入了一些我没有调试信息的库(或其他内容),导致只有一些十六进制数字......因此,我仍然需要一些良好的转储,提供更多信息(特别是关于存储器中的内容......具体来说,是在给出“访问冲突”错误的位置中的内容)
此外,我的应用程序使用Lua和Luabind,也许错误是由.lua脚本引起的,但我不知道如何调试它。
原文链接 https://stackoverflow.com/questions/3437809
试试 Valgrind(它是免费的开源工具):
Valgrind distribution 目前包含六个生产级别的工具:
- 内存错误检测器
- 两个线程错误检测器
- 缓存和分支预测分析器
- 产生调用图的缓存分析器
- 堆分析器
它还包括两个实验性的工具:
- 堆/栈/全局数组越界检测器
- SimPoint 块向量生成器
可以在以下平台上运行:X86/Linux、AMD64/Linux、PPC32/Linux、PPC64/Linux 和 X86/Darwin(Mac OS X)。
可能从Memcheck入手:
Memcheck 是一种内存错误检测器,可以检测C和C++程序中常见的以下问题:
- 访问不应访问的内存,例如溢出、越界堆块、堆栈顶溢出以及在释放后访问内存。
- 使用未定义的值,即未初始化的值,或派生自其他未定义的值。
- 不正确的堆内存释放操作,例如重复释放堆块以及 malloc/new/new[] 与 free/delete/delete[] 不匹配。
- 在 memcpy 和相关函数中存在 src 和 dst 指针重叠。
- 内存泄漏。
在我的工作中,崩溃的程序通常会生成可在 windbg 中加载的核心转储文件。
当时的内存映像随之产生。虽然你无法做太多事情,但至少它可以提供最后的调用堆栈。一旦你知道了导致崩溃的函数,你可能就能追踪问题,或至少将问题缩小到更可重现的测试用例。
在 Linux 下使用 valgrind
运行应用程序以查找内存错误。随机崩溃通常是由于损坏内存而导致的。
使用 valgrind 的 memcheck 工具修复您发现的每个错误,然后希望崩溃会消失。
如果整个程序在 valgrind 下运行时间太长,则将功能拆分为单元测试,并在 其中 在 valgrind 下运行,希望您能找到导致问题的内存错误。
如果没有,那么请确保启用了核心转储 (ulimit -a
),然后当程序崩溃时,您将能够使用 gdb
找到原因。
这听起来像是一个棘手的问题,就像竞争条件一样。
我建议你创建一个调试版本并使用它。你还应该确保程序崩溃时创建一个核心转储文件。
下次程序崩溃时,你可以在核心转储文件上启动 gdb 并查看问题所在。这可能是一个连续的故障,但这应该能让你开始解决问题。
如果一切都失败了(特别是如果在调试器下的性能不可接受),请进行广泛的日志记录。从入口点开始——应用程序是否是事务性的?记录每个事务。记录关键对象的构造函数调用。由于崩溃是如此间歇性的,记录所有可能不会每天调用的函数的调用。
至少您会开始缩小崩溃可能发生的范围。
你可能犯了内存错误,在某些情况下给未分配的空间赋值,这是不稳定的原因之一,长时间没有使用该内存就不会出现错误,你可以查看分配内存的位置,并检查在哪些地方使用了指针。
除此之外,正如其他人指出的,你应该使用广泛的日志记录,在屏幕和文件中都要记录。
这种错误总是很棘手——除非您能重现错误,否则您唯一的选择就是更改应用程序以记录额外的信息,然后等待错误再次发生。
有一个名为 Process Dumper 的优秀工具可用于获取遇到异常或意外退出的进程的崩溃转储。您可以要求用户安装并为您的应用程序配置规则。
或者,如果您不想要求用户安装其他应用程序,您可以让您的应用程序监视异常并通过调用 MiniDumpWriteDump 自己创建一个崩溃转储。
另一种选择是改善日志记录,但是确定要记录什么信息(而不仅是记录一切)可能很棘手,因此可能需要几次 崩溃 - 更改日志记录 迭代才能找到问题。
正如我所说,这种错误始终很棘手——根据我的经验,通常需要花费数小时阅读日志和崩溃转储,直到突然出现那个顿悟时刻,一切都变得清晰明了——关键在于收集正确的信息。
首先,你很幸运在短时间内出现了多次进程崩溃。这将使进程跟踪变得容易。
你可以按照以下方式进行处理:
- 获取崩溃转储文件
- 隔离潜在可疑函数集
- 加强状态检查
- 重复执行
获取崩溃转储文件
首先,你真的需要获取崩溃转储文件。
如果进程崩溃时没有崩溃转储文件,开始编写一个可以生成可靠崩溃转储文件的测试。
重新编译二进制文件以加入调试符号或确保您可以使用调试符号分析崩溃转储文件。
查找可疑函数
鉴于你有崩溃转储文件,请在 gdb 或者你最喜欢的调试器中查看它,并记得展示所有线程!可能不是在 gdb 中看到的线程出现问题。
查看 gdb 报告中二进制文件的崩溃位置,隔离一些可能导致问题的函数集。
查看多个崩溃和隔离在所有崩溃中常用的代码段可以节省时间。
加强状态检查
进程崩溃通常发生在某个不一致状态下。最好的处理方式通常是加强状态要求。你可以按照以下方式处理。
对于认为可能导致问题的每个函数,记录输入或对象在进入函数时必须具备的合法状态(对于退出函数时必须具备的合法状态进行相同处理,但不太重要)。
如果函数包含循环,请记录它在每个循环迭代开始时需要具备的合法状态。
为所有这些合法状态表达式添加断言。
重复执行
然后重复执行该过程。如果它仍然在断言之外崩溃,请进一步加强断言。在某个时刻,该进程将因为断言而崩溃,而不是因为某个随机的崩溃。此时,你可以专注于尝试找出程序在哪里从进入函数时的合法状态到达断言点时的非法状态。
如果将断言与详细的日志记录配对,那么跟踪程序的执行路径应该更容易。
除了在 Linux 中的核心转储和 valgrind 之外,还有两个指针/想法:
1)尝试使用诺基亚的“Qt Creator”。它支持 mingw 并可作为事后调试器。
2)如果可行,也许只需在 gdb 中持续运行该应用程序?
第一件事是使用 gdb (Windows 和Linux都适用)调试核心转储。然后运行像 Lint、Prefast(Windows)、Clang Analyzer 或其他一些静态分析程序(准备好接受很多误报)。第三个事情是运行时检查,如 Valgrind(或其类似变体)、Microsoft Application Verifier 或Google Perftools。
还有日志记录。它不必写入磁盘。例如,您可以将日志记录到全局 std::list<std::string>
中,该列表将被修剪为最后的100个条目。当捕获到异常时,显示该列表的内容。
如果你的应用程序不是只限于 Windows,你可以尝试在其他平台上编译和运行你的程序,如 Linux(不同的发行版,32/64位等,如果你有这个需要的话)。这可能有助于触发程序的错误。当然,你应该使用其他帖子中提到的工具,如 gdb、valgrind 等。
似乎你的程序遭受了内存损坏。如其他人所述,在Linux上你最好的选择可能是使用valgrind。但是这里有另外两个选项:
首先使用调试malloc。几乎所有的C库都提供了一个调试malloc实现,它可以初始化内存(普通的malloc会保留内存中的“旧”内容),检查分配块的边界是否被损坏等。如果这还不够,还有很多第三方实现可供选择。
你可能想看一下VMWare Workstation。我没有这样设置过,但从他们的营销材料中可以看出,他们支持一种相当有趣的调试方式:在一个“记录”的虚拟机中运行受调试程序。当内存损坏发生时,在损坏的地址处设置一个内存断点,然后在VM中回溯到那个片段被覆盖的那个瞬间。请参阅此PDF ,了解如何在Linux/gdb上设置重放调试。我相信Workstation 7有15或30天的演示版本,这可能足以使你的代码摆脱这些错误。
你已经听说过如何在 Linux 下处理这个问题:检查核心转储并在 valgrind 下运行您的代码。因此,您的第一步可能是在 Linux 下找到错误,然后检查它们是否在 mingw 下消失。由于没有人在此处提到 在 Linux 下使用 mudflap,所以我会做出这样的建议:如果您的 Linux 分发版本提供了 mudflap,则使用 mudflap。 mudflap 通过跟踪指针实际允许指向的信息帮助您捕捉指针误用和缓冲区溢出:
至于在 Windows 上:有一个针对 mingw 的即时调试器,称为 DrMingw:
开始记录日志。在你认为代码不稳定的地方放置日志语句,并专注于测试代码,直到你把问题明确到一个模块或函数为止。
到处放置断言!
在你这么做的时候,只在断言中放置一个表达式。
为你认为有问题的代码编写单元测试。这样你就可以在与运行环境隔离的情况下测试代码。
编写更多自动化测试来测试有问题的代码。
不要在出现问题的代码上再添加更多代码。这只是一个愚蠢的想法。
学会如何编写迷你转储文件并进行死后调试。看起来其他人已经解释得很清楚了。
尝试以尽可能多的不同方式测试出现问题的代码,以便你可以隔离错误。
使用调试版本进行测试。尽可能在调试器中运行调试版本。
如果可能的话,通过删除二进制文件、模块等来修剪应用程序,以便更容易复现错误。
另一个基本检查:确保进行完整的重建项目。如果您一直在调整各种文件(特别是标头文件)并进行局部构建,那么如果您的构建依赖关系不完美,事情可能会变得混乱。完整重建只能消除这种可能性。
另外,对于Windows,请查看Microsoft的Windows调试工具,尤其是它们的gflags 工具。
Lua 角度的讨论
这里有很多好的回答,但是还没有人触及 Lua 的角度。
Lua 通常表现良好,但是如果 Lua 栈溢出或者下溢,或者执行了错误的字节码,它仍然可能会导致内存损坏或崩溃。
你可以很容易地做一件事情来检测许多这样的错误,那就是在 luaconf.h 中定义 lua_assert 宏。将其定义为标准 C 的 assert,将启用在 Lua 核心内部进行各种健全检查。
- 如何在roblox studio中1:1导入真实世界的地形?
- 求解,lua_resume的第二次调用继续执行协程问题。
- 【上海普陀区】内向猫网络招募【Skynet游戏框架Lua后端程序员】
- SF爱好求教:如何用lua实现游戏内调用数据库函数实现账号密码注册?
- Lua实现网站后台开发
- LUA错误显式返回,社区常见的规约是怎么样的
- lua5.3下载库失败
- 请问如何实现文本框内容和某个网页搜索框内容连接,并把网页输出来的结果反馈到另外一个文本框上
- lua lanes多线程使用
- 一个kv数据库
- openresty 有没有比较轻量的 docker 镜像
- 想问一下,有大佬用过luacurl吗
- 在Lua执行过程中使用Load函数出现问题
- 为什么 neovim 里没有显示一些特殊字符?
- Lua比较两个表的值(不考虑键的顺序)
- 有个lua简单的项目,外包,有意者加微信 liuheng600456详谈,最好在成都
- 如何在 Visual Studio 2022 中运行 Lua 代码?
- addEventListener 返回 nil Lua
- Lua中获取用户配置主目录的跨平台方法
- 如何编写 Lua 模式将字符串(嵌套数组)转换为真正的数组?
在调试器下启动程序(我相信 GCC 和 MingW 都有自带的调试器),等待直到程序崩溃。在崩溃点,您将能够查看是哪个特定的操作失败,查看汇编代码、寄存器、内存状态 - 这通常会帮助您找到问题的原因。