Heap corruption under Win32; how to locate?我正在处理破坏堆的多线程C ++应用程序。用于定位此损坏的常用工具似乎不适用。源代码的旧版本(18个月大)表现出与最新版本相同的行为,因此已经存在了很长时间,并且并未引起人们的注意。不利的一面是,源增量不能用来标识何时引入该错误-存储库中有很多代码更改。 崩溃行为的提示是在此系统中生成吞吐量-套接字传输的数据被合并到内部表示中。我有一组测试数据,这些数据会定期导致应用程序异常(各种地方,各种原因-包括堆分配失败,因此:堆损坏)。 该行为似乎与CPU功率或内存带宽有关。每台机器拥有的越多,崩溃就越容易。禁用超线程内核或双内核内核会降低(但不能消除)损坏的速度。这暗示了与计时有关的问题。
现在是问题所在: 我目前对下一步的最佳猜测是: 而且,否:不能选择使用内置的Purify仪器进行运输。 一位同事走过去,问"堆栈溢出?我们现在堆栈溢出了吗?!?" 现在,问题来了:如何找到堆破坏者?
更新:平衡
更新:Visual Studio 2008下的发行版本似乎要好得多;当前的怀疑取决于
我会记下这一点,但我担心Watson博士只会在事实发生后被绊倒,而不会在堆被踩到时绊倒。
此刻再次得到了帮助:直到出现问题之前,没有太多帮助。我想赶快行动。
我没有太大的希望,但是绝望的时刻要求...
不,我不是,明天我将花几个小时浏览工作区(其中有58个项目),并检查它们是否都已编译并与适当的标志链接。 更新:这花了30秒。在 我的首选将是专用的堆工具,例如pageheap.exe。
重写new和delete可能很有用,但是不能捕获较低级代码提交的分配。如果这是您想要的,最好使用Microsoft Detours绕过 还进行完整性检查,例如:验证运行时库是否匹配(发行版与调试版,多线程与单线程版,dll与静态lib),查找错误的删除项(例如,删除应该删除delete []的位置)使用),请确保您没有混合使用和匹配您的分配。 还可以尝试有选择地关闭线程,并查看问题何时/是否消失。 第一次发生异常时,调用堆栈等是什么样的?
我的工作中遇到相同的问题(有时我们也使用
祝好运。像您这样的问题需要我们花费数月的时间才能解决。为此做好准备...
使用
您可以分析转储以找出损坏的内存位置。
弄清问题所在之后,您可以使用特殊的堆设置来缩小应用程序的使用范围。即您可以指定要监视的 希望这将加快监视速度,以赶上罪魁祸首。 以我的经验,我不需要全堆验证程序模式,但是我花了很多时间分析故障转储和浏览源。
附言: 通过编写我们自己的malloc和free函数,我们很幸运。在生产中,他们只调用标准的malloc和free,但是在调试中,他们可以做任何您想做的事情。我们还有一个简单的基类,除了重写new和delete运算符以使用这些功能外,什么也不做,那么您编写的任何类都可以简单地从该类继承。如果您有大量的代码,将对malloc的调用替换为free并替换为新的malloc和free(不要忘了realloc!)可能是一项艰巨的工作,但是从长远来看,这非常有帮助。 在史蒂夫·马奎尔(Steve Maguire)的《编写固体代码》(强烈建议)中,有一些可以在这些例程中进行调试的示例,例如:
另一个好主意是不要使用 您应该同时使用运行时分析和静态分析来解决此问题。
对于静态分析,请考虑使用PREfast( PREfast可与Visual Studio Team System一起使用,并且显然是Windows SDK的一部分。
这是在内存不足的情况下吗?如果是这样,则可能是new返回了 内存损坏的明显随机性听起来非常像线程同步问题-根据机器速度重现错误。如果对象(内存块)在线程之间共享并且同步(关键部分,互斥体,信号量等)原语不是基于每个类(每个对象,每个类)的,则有可能出现这种情况类(内存块)在使用中被删除/释放,或在删除/释放后使用的类。 作为对此的测试,您可以向每个类和方法添加同步原语。这将使您的代码变慢,因为许多对象将不得不彼此等待,但是如果这消除了堆损坏,则您的堆损坏问题将成为代码优化问题。 如果您选择重写new / delete,我已经做到了,并在以下位置提供了简单的源代码: http://gandolf.homelinux.org/~smhanov/blog/?id=10 这样可以捕获内存泄漏,并在内存块之前和之后插入保护数据以捕获堆损坏。您可以通过将#include" debug.h"放在每个CPP文件的顶部,并定义DEBUG和DEBUG_MEM来与之集成。 因此,根据您所拥有的有限信息,这可以是以下一项或多项内容的组合:
如果它只是前两个而不是最后一个,那么您现在应该已经使用pageheap.exe捕获了它。 这最有可能意味着这是由于代码如何访问共享内存。不幸的是,追踪下来将是非常痛苦的。对共享内存的不同步访问通常表现为怪异的"定时"问题。诸如不使用获取/释放语义来同步对带有标志的共享内存的访问,不适当使用锁等之类的事情。 至少如前所述,以某种方式跟踪分配会有所帮助。至少然后您可以查看直到堆损坏之前实际发生的情况,然后尝试从中进行诊断。 另外,如果您可以轻松地将分配重定向到多个堆,则可能需要尝试一下,看看是否可以解决问题或导致可再生的错误行为。 使用VS2008进行测试时,您是否在HeapVerifier上将"保存内存"设置为"是"运行?这可能会减少堆分配器对性能的影响。 (此外,您必须使用它运行Debug-> Start with Application Verifier,但您可能已经知道这一点。) 您也可以尝试使用Windbg和!heap命令的各种用法进行调试。 MSN 我的第一个动作如下: 另一种尝试是将WinDebug用作调试工具,该工具功能强大,同时又轻巧。 也许这些工具至少可以使您将问题缩小到某些组件。 并且您确定项目的所有组件都具有正确的运行时库设置(" C / C ++"选项卡,VS 6.0项目设置中的"代码生成"类别)吗? 您尝试过使用旧版本,但是有没有理由不能继续追溯到存储库历史记录中并确切地看到引入错误的时间? 否则,我建议添加某种简单的日志记录以帮助查找问题,尽管我不知道您可能想要记录什么。 如果您可以通过google以及正在获取的异常的文件找出导致此问题的确切原因,那么也许可以进一步了解在代码中查找的内容。 一些建议。 您提到了W4的大量警告-我建议您花点时间修复代码以在警告级别4进行干净编译-这将有助于防止难以发现的错误。 其次-对于/ analyze开关-它确实会生成大量警告。 要在我自己的项目中使用此开关,我要做的是创建一个新的头文件,该文件使用#pragma warning关闭/ analyze生成的所有其他警告。 然后在文件的最下方,我仅打开我关心的那些警告。 然后使用/ FI编译器开关强制将此头文件首先包含在所有编译单元中。 这应该允许您在控制输出时使用/ analyze开关 您认为这是比赛条件吗?多个线程共享一个堆吗?您能否使用HeapCreate为每个线程提供一个专用堆,然后它们可以使用HEAP_NO_SERIALIZE快速运行。否则,如果您使用的是系统库的多线程版本,则堆应该是线程安全的。
我花了很少的时间解决类似的问题。 Graeme建议使用自定义malloc / free是一个好主意。看看您是否可以描绘出一些有关损坏的模式,以便您可以利用。 例如,如果它总是在相同大小的块中(例如64个字节),则更改您的malloc / free对以始终在其自己的页面中分配64个字节的块。释放64字节的块时,请在该页面上设置内存保护位,以防止读取和写入(使用VirtualQuery)。然后,任何尝试访问此内存的人都会生成一个异常,而不是破坏堆。 这确实是假设未完成的64字节块的数量仅是中等的,否则您有很多内存要在盒子中烧掉! |