我正在研究破坏堆的多线程 C ++应用程序。 找到这种腐败的常用工具似乎不适用。 源代码的旧版本(18个月之前)表现出与最新版本相同的行为,因此这已经存在了很长时间并且没有被注意到; 在缺点方面,源增量不能用于识别何时引入错误 - 存储库中存在大量代码更改。

崩溃行为的提示是在这个系统中产生吞吐量 - 数据的套接字传输,它被导入内部表示。 我有一组测试数据会定期导致应用程序异常(各种地方,各种原因 - 包括堆分配失败,因此:堆损坏)。

行为似乎与CPU功率或内存带宽有关; 机器越多,崩溃就越容易。 禁用超线程核心或双核核心可降低(但不消除)损坏的速度。 这表明与时间相关的问题。

现在,这是擦:
当它在轻量级调试环境(例如Visual Studio 98 / AKA MSVC6 )下运行时,堆损坏相当容易重现 - 在出现故障和异常(例如alloc;之前经过十或十五分钟alloc; 当在复杂的调试环境(Rational Purify, VS2008/MSVC9或甚至Microsoft Application Verifier)下运行时,系统将变为内存速度限制并且不会崩溃(内存限制:CPU未达到50% ,磁盘指示灯未亮起,该程序尽可能快,盒子消耗1.3G的2G内存)。 因此, 我可以选择能够重现问题(但不能确定原因)或能够识别原因或我无法重现的问题。

我目前最好的猜测是下一步是:

  1. 得到一个疯狂的盒子(以取代当前的开发盒: E6550 Core2 Duo中的2Gb RAM); 这样可以在强大的调试环境下运行时重现崩溃导致错误行为; 要么
  2. 重写操作符newdelete以使用VirtualAllocVirtualProtect在完成后将内存标记为只读。 MSVC6MSVC6并让操作系统捕获正在写入释放内存的坏人。 是的,这是一种绝望的迹象:谁会重写newdelete ?! 我想知道这是否会使它像Purify等人一样慢。

而且,不是:内置Purify仪器的运输不是一种选择。

一位同事刚刚走过去问“Stack Overflow?我们现在收到堆栈溢出了吗?!?”

现在,问题是: 我如何找到堆腐败者?


更新:平衡new[]delete[]似乎已经解决了问题。 而不是15分钟,应用程序现在大约两个小时崩溃。 还没有。 还有什么建议? 堆损坏仍然存在。

更新:Visual Studio 2008下的发布版本似乎要好得多; 当前的怀疑取决于VS98附带的STL实现。


  1. 重现问题。 Dr Watson将生成一个可能有助于进一步分析的转储。

我会注意到这一点,但我担心沃森博士只会在事后被绊倒,而不是当堆被踩到时。

另一个尝试可能是使用WinDebug作为调试工具,它非常强大,同时也是轻量级的。

此刻再次发生这种情况:在出现问题之前没有多大帮助。 我想抓住这个行为中的破坏者。

也许这些工具可以让您至少将问题缩小到某个组件。

我不抱太大希望,但绝望的时候要求......

并且您确定项目的所有组件都具有正确的运行时库设置( C/C++ tab ,VS 6.0项目设置中的代码生成类别)吗?

不,我不是,明天我将花费几个小时浏览工作区(其中有58个项目)并检查它们是否正在编译并链接相应的标志。


更新:这需要30秒。 在“ Settings对话框中选择所有项目,取消选择,直到找到没有正确设置的项目(它们都具有正确的设置)。

===============>>#1 票数:28 已采纳

我的第一选择是一个专用的堆工具,如pageheap.exe

重写new和delete可能很有用,但是它不会捕获较低级代码提交的alloc。 如果这是你想要的,最好使用Microsoft Detours绕过low-level alloc API

还要进行健全性检查,例如:验证运行时库是否匹配(发布与调试,多线程与单线程,dll与静态库),查找错误删除(例如,删除删除[]应该是使用过),确保你没有混合和匹配你的分配。

还可以尝试选择性地关闭线程并查看问题何时消失。

在第一个异常时调用堆栈等是什么样的?

===============>>#2 票数:11

我的工作也有同样的问题(我们有时也使用VC6 )。 并没有简单的解决方案。 我只有一些提示:

  • 尝试在生产计算机上使用自动故障转储(请参阅Process Dumper )。 我的经验表明沃森博士适合倾倒。
  • 从代码中删除所有catch(...) 他们经常隐藏严重的内存异常。
  • 检查高级Windows调试 - 对于像您这样的问题,有很多很棒的技巧。 我全心全意地推荐这个。
  • 如果您使用STL尝试STLPort并检查构建。 无效的迭代器是地狱。

祝好运。 像你这样的问题花了我们几个月来解决。 为此做好准备......

===============>>#3 票数:8

使用ADplus -crash -pn appnename.exe运行原始应用程序当弹出内存问题时,您将获得一个很好的大转储。

您可以分析转储以确定哪个内存位置已损坏。 如果幸运的话,覆盖内存是一个唯一的字符串,你可以弄清楚它来自哪里。 如果你不幸运,你将需要挖掘win32堆并计算出原始内存特征是什么。 (堆-x可能有帮助)

在您知道什么是混乱后,您可以使用特殊堆设置缩小appverifier使用率。 即,您可以指定您监视的DLL ,或要监视的分配大小。

希望这将加速监控,足以抓住罪魁祸首。

根据我的经验,我从不需要完整的堆验证模式,但我花了很多时间分析崩溃转储和浏览源。

PS:您可以使用DebugDiag来分析转储。 它可以指出DLL拥有损坏的堆,并为您提供其他有用的细节。

===============>>#4 票数:7

通过编写我们自己的malloc和免费函数,我们运气良好。 在生产中,他们只是调用标准的malloc并且是免费的,但在调试中,他们可以做任何你想做的事情。 我们还有一个简单的基类,除了覆盖new和delete操作符以使用这些函数之外什么都不做,然后你编写的任何类都可以简单地从该类继承。 如果你有大量的代码,那么替换对malloc的调用并免费使用新的malloc并且免费(不要忘记realloc!)可能是一个很大的工作,但从长远来看它非常有用。

在Steve Maguire的书“强化建议编写” (强烈推荐)中,您可以在这些例程中进行调试,例如:

  • 跟踪分配以发现泄漏
  • 分配比必要更多的内存并在记忆的开头和结尾放置标记 - 在自由例程中,您可以确保这些标记仍在那里
  • 在分配时使用标记(以查找未初始化内存的使用情况)和免费(查找free'd内存的使用情况)来记忆内存

另一个好主意是永远不要使用strcpystrcatsprintf - 总是使用strncpystrncatsnprintf 我们也编写了我们自己的这些版本,以确保我们不会写下缓冲区的末尾,这些也遇到了很多问题。

===============>>#5 票数:4

您应该使用运行时和静态分析来解决此问题。

对于静态分析,请考虑使用PREfast进行编译( cl.exe /analyze )。 它检测到不匹配的deletedelete[] ,缓冲区溢出和许多其他问题。 但是,要做好准备,通过许多千字节的L6警告,特别是如果你的项目仍然没有修复L4

PREfast可用于Visual Studio Team System, 显然也是Windows SDK的一部分。

===============>>#6 票数:3

内存损坏的明显随机性听起来非常像线程同步问题 - 根据机器速度重现错误。 如果在线程之间共享对象(内存块)并且同步(临界区,互斥体,信号量,其他)基元不是基于每个类(每个对象,每个类),那么就有可能出现这种情况其中class(内存块)在使用时被删除/释放,或在删除/释放后使用。

作为测试,您可以为每个类和方法添加同步原语。 这将使您的代码变慢,因为许多对象必须彼此等待,但如果这样可以消除堆损坏,那么堆损坏问题将成为代码优化问题。

===============>>#7 票数:3

这是在低内存条件下? 如果是这样,可能是new返回NULL而不是抛出std :: bad_alloc。 较旧的VC++编译器没有正确实现它。 有一篇文章介绍了使用VC6构建的STL应用程序崩溃的旧内存分配失败

===============>>#8 票数:1

您尝试过旧版本,但有没有理由不能继续深入了解存储库历史记录并确切了解错误何时引入?

否则,我建议添加某种简单的日志记录以帮助追踪问题,尽管我不知道你可能想要记录的具体内容。

如果您可以通过谷歌和您所获得的例外文档找出究竟可能导致此问题的原因,那么可能会进一步了解代码中需要查找的内容。

===============>>#9 票数:1

我的第一个行动如下:

  1. 在“Release”版本中构建二进制文件,但创建调试信息文件(您将在项目设置中找到这种可能性)。
  2. 在要重现问题的计算机上使用Dr Watson作为defualt调试程序(DrWtsn32 -I)。
  3. 重新生成问题。 Watson博士将生成一个可能有助于进一步分析的转储。

另一个尝试可能是使用WinDebug作为调试工具,它非常强大,同时也是轻量级的。

也许这些工具可以让您至少将问题缩小到某个组件。

并且您确定项目的所有组件都具有正确的运行时库设置(C / C ++选项卡,VS 6.0项目设置中的代码生成类别)吗?

===============>>#10 票数:1

因此,根据您拥有的有限信息,这可以是一个或多个事物的组合:

  • 坏堆使用,即双重释放,空闲后读取,空闲后写入,使用allocs设置HEAP_NO_SERIALIZE标志并从同一堆上的多个线程释放
  • 内存不足
  • 错误的代码(即缓冲区溢出,缓冲区下溢等)
  • “时机”问题

如果它是前两个但不是最后两个,你现在应该用pageheap.exe捕获它。

这很可能意味着它是由于代码如何访问共享内存。 不幸的是,追踪这种情况将会非常痛苦。 对共享内存的不同步访问通常表现为奇怪的“时序”问题。 不使用获取/释放语义来同步对共享内存的访问与标志,不适当地使用锁等等。

至少,如前所述,能够以某种方式跟踪分配将有所帮助。 至少那时你可以查看在堆损坏之前发生了什么,并尝试从中进行诊断。

此外,如果您可以轻松地将分配重定向到多个堆,您可能需要尝试查看是否可以解决问题或导致更可重现的错误行为。

当您使用VS2008进行测试时,是否使用将Conserve Memory设置为“是”的HeapVerifier运行? 这可能会降低堆分配器的性能影响。 (另外,你必须运行Debug-> Start with Application Verifier,但你可能已经知道了。)

您还可以尝试使用Windbg进行调试以及!heap命令的各种用法。

MSN

===============>>#11 票数:1

如果你选择重写new / delete,我已经完成了这个并且有简单的源代码:

http://gandolf.homelinux.org/~smhanov/blog/?id=10

这会捕获内存泄漏,并在内存块之前和之后插入保护数据以捕获堆损坏。 您可以通过将#include“debug.h”放在每个CPP文件的顶部,并定义DEBUG和DEBUG_MEM来集成它。

===============>>#12 票数:0

你认为这是竞争条件吗? 多个线程共享一个堆? 你能用HeapCreate给每个线程一个私有堆,然后它们可以用HEAP_NO_SERIALIZE快速运行。 否则,如果您使用的是系统库的多线程版本,则堆应该是线程安全的。

===============>>#13 票数:0

一些建议。 你在W4上提到了大量的警告 - 我建议花点时间修改你的代码,在警告级别4进行干净的编译 - 这将有助于防止细微的难以找到错误。

第二 - 对于/ analyze开关 - 确实会产生大量警告。 要在我自己的项目中使用此开关,我所做的是创建一个新的头文件,使用#pragma warning关闭/ analyze生成的所有其他警告。 然后在文件中,我只打开那些我关心的警告。 然后使用/ FI编译器开关强制将此头文件包含在所有编译单元中。 这应该允许您在控制输出时使用/ analyze开关

===============>>#14 票数:0

我必须解决类似问题的时间很少。 如果问题仍然存在,我建议你这样做:监控所有对new / delete和malloc / calloc / realloc / free的调用。 我使单个DLL导出一个函数来注册所有调用。 此函数接收用于标识代码源的参数,指向已分配区域的指针以及将此信息保存在表中的调用类型。 消除了所有分配/释放的对。 在您需要的最后或之后,您可以调用其他函数来创建左数据的报告。 通过这种方式,您可以识别错误的呼叫(新/免费或malloc /删除)或丢失。 如果您的代码中有任何缓冲区被覆盖,则保存的信息可能是错误的,但每个测试可能会检测/发现/包含已识别的故障解决方案。 许多运行以帮助识别错误。 祝好运。

===============>>#15 票数:0

Graeme建议定制malloc / free是一个好主意。 看看你是否可以描述一些关于腐败的模式,以便为你提供杠杆作用。

例如,如果它总是在一个相同大小的块(比如64个字节)中,那么将malloc / free对更改为总是在自己的页面中分配64个字节的块。 释放64字节块时,然后设置该页面上的内存保护位以防止读取和使用VirtualQuery(使用VirtualQuery)。 然后,任何试图访问此内存的人都会生成异常而不是破坏堆。

这确实假设未完成的64字节块的数量只是适中的,或者你需要在盒子中刻录大量的内存!

  ask by community wiki translate from so

未解决问题?本站智能推荐: