如何debugging堆损坏错误?

我在Visual Studio 2008下debugging了一个(本地)multithreadingC ++应用程序。在看似随意的场合,我得到一个“Windows引发了一个断点…”的错误,并注意到这可能是由于堆。 这些错误不会总是使应用程序崩溃,尽pipe它很可能会在之后崩溃。

这些错误的主要问题是,只有在实际发生了腐败之后才会popup,这使得它们非常难以跟踪和debugging,特别是在multithreading应用程序中。

  • 什么样的事情会导致这些错误?

  • 我如何debugging它们?

提示,工具,方法,启示…是受欢迎的。

应用程序validation程序结合Windowsdebugging工具是一个了不起的设置。 您可以将它们作为Windows Driver Kit或更轻的Windows SDK的一部分 。 (在研究关于堆腐败问题的早期问题时,发现了关于应用程序validation程序的问题 。)过去我也使用了BoundsChecker和Insure ++(在其他答案中提到过),但是我惊讶于Application Verifier中有多lessfunction。

电篱笆(又名“efence”), dmalloc , valgrind等等都值得一提,但其中大多数在* nix下运行比Windows更容易。 Valgrind是非常灵活的:我用大量的堆问题debugging了大型服务器软件。

当所有其他的失败,你可以提供你自己的全球运营商新/删除和malloc / calloc / realloc重载 – 如何这样做将有所不同,取决于编译器和平台 – 这将是一个投资 – 但是从长远来看,这可能会有所回报。 理想的function列表应该从dmalloc和电篱笆里看起来很熟悉,而出乎意料的优秀的书写固体代码 :

  • 哨兵值 :在每次分配之前和之后允许更多的空间,尊重最大alignment要求; 填充幻数(有助于捕捉缓冲区溢出和下溢,偶尔的“狂野”指针)
  • alloc fill :使用一个非0的魔术值填充新的分配 – Visual C ++已经在Debug build中为你做了这个(帮助捕获未初始化的variables)
  • 自由填充(free fill) :用一个非0的魔术值填充释放的内存,如果在大多数情况下被解引用,会触发一个段错误(有助于捕获悬挂的指针)
  • 延迟释放 :不释放内存到堆中一段时间​​,让它自由填充但不可用(有助于捕获更多的悬挂指针,捕获接近双释放的内存)
  • 跟踪 :能够logging分配的位置有时可能有用

请注意,在我们的本地自制系统(针对embedded式目标)中,我们将跟踪从大部分其他内容中分离出来,因为运行时开销要高得多。


如果你有更多的理由来重载这些分配函数/运算符,看看我的答案是“任何理由超载全局运算符new和delete?” ; 无耻的自我推销,它列出了其他技术,有助于跟踪堆损坏错误,以及其他适用的工具。

您可以通过为您的应用程序启用Page Heap来检测大量的堆损坏问题。 为此,您需要使用作为Windowsdebugging工具的一部分的gflags.exe

运行Gflags.exe并在可执行文件的映像文件选项中,选中“启用页面堆”选项。

现在重新启动您的exe并附加到debugging器。 在启用页面堆的情况下,每当发生任何堆损坏时,应用程序都会进入debugging器。

一个非常相关的文章是使用Application Verifier和Debugdiagdebugging堆损坏

要真正减慢速度并执行大量运行时检查,请尝试在main()或Microsoft Visual Studio C ++中的等效项的顶部添加以下内容

 _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_CHECK_ALWAYS_DF ); 

什么样的事情会导致这些错误?

用内存做顽皮的事情,例如在缓冲区结束之后写入,或者在释放回堆之后写入缓冲区。

我如何debugging它们?

使用一种为自己的可执行程序添加自动化边界检查的工具:例如Unix上的valgrind,或者Windows上的BoundsChecker(维基百科build议使用Purify and Insure ++)等工具。

请注意,这些会减慢您的应用程序,因此如果您的应用程序是软实时应用程序,则可能无法使用。

另一种可能的debugging辅助工具可能是MicroQuill的HeapAgent。

一个快速的提示,我从检测访问释放内存是这样的:

如果要快速定位错误,而不检查访问内存块的每条语句,则可以在释放块之后将内存指针设置为无效值:

 #ifdef _DEBUG // detect the access to freed memory #undef free #define free(p) _free_dbg(p, _NORMAL_BLOCK); *(int*)&p = 0x666; #endif 

我发现每一次有用的最好的工具是代码审查(有很好的代码审查人员)。

除了代码审查,我首先尝试页堆 。 Page Heap需要几秒钟的时间来设置,幸运的是它可能会指出你的问题。

如果没有页堆的运气,请从Microsoft下载debugging工具,并学习使用WinDbg。 对不起,不能给你更具体的帮助,但debuggingmultithreading堆腐败更多的是一门艺术而不是科学。 谷歌的“WinDbg堆腐败”,你应该find很多关于这个问题的文章。

您可能还想要查看是否链接到dynamic或静态C运行时库。 如果您的DLL文件链接到静态C运行时库,则DLL文件具有单独的堆。

因此,如果您要在一个DLL中创build一个对象并尝试将其释放到另一个DLL中,则会得到与上面相同的消息。 在另一个堆栈溢出问题中引用此问题, 释放在不同的DLL中分配的内存

你使用什么types的分配函数? 我最近使用Heap *样式分配函数遇到类似的错误。

原来我错误地用HEAP_NO_SERIALIZE选项创build了堆。 这基本上使得堆函数在没有线程安全的情况下运行。 如果使用正确,性能会提高,但是如果您在multithreading程序中使用HeapAlloc,则不应该使用这种改进[1]。 我只提到这个,因为你的文章提到你有一个multithreading的应用程序。 如果你在任何地方使用HEAP_NO_SERIALIZE,删除它,它可能会解决你的问题。

[1]在某些情况下,这是合法的,但是它要求你将调用序列化到Heap *,而且对于multithreading程序通常不是这种情况。

如果这些错误是随机发生的,则很有可能会遇到数据竞争。 请检查:你是否修改不同线程的共享内存指针? 英特尔线程检查器可能有助于检测multithreading程序中的这些问题。

除了寻找工具之外,还要考虑寻找可能的罪魁祸首。 有没有你正在使用的组件,可能不是你写的,它可能没有被devise和testing在multithreading环境中运行? 或者只是一个你不知道在这样的环境中运行。

最后一次发生在我身上,这是一个本地的软件包,已经成功地从批处理作业中使用了多年。 但这是该公司第一次使用.NET Web服务(它是multithreading的)。 就是这样 – 他们对线程安全的代码撒了谎。

您可以使用VC CRT堆检查macros_CrtSetDbgFlag_CRTDBG_CHECK_ALWAYS_DF_CRTDBG_CHECK_EVERY_16_DF .. _CRTDBG_CHECK_EVERY_1024_DF

我想补充我的经验。 在过去的几天里,我在应用程序中解决了这个错误的一个实例。 在我的具体情况下,代码中的错误是:

  • 在迭代时删除STL集合中的元素(我相信在Visual Studio中有debugging标志来捕获这些东西;在代码审查过程中我发现它)
  • 这一个更复杂,我将分步骤:
    • 从本机C ++线程,callback到托pipe代码
    • 在受pipe理的Control.Invoke ,调用Control.Invoke并configuration一个托pipe对象,该对象包装callback所属的本地对象。
    • 由于对象在本地线程内仍然活着(它将在callback调用中保持阻塞,直到Control.Invoke结束)。 我应该澄清,我使用boost::thread ,所以我使用成员函数作为线程函数。
    • 解决方法 :使用Control.BeginInvoke (我的GUI是用Winforms创build的),以便本地线程可以在对象被销毁之前结束(callback的目的是正确地通知线程结束并且对象可以被销毁)。

我有一个类似的问题 – 它随机popup。 也许在构build文件中有些东西是腐败的,但我最终通过先清理项目然后重build来修复它。

所以除了给出的其他答案外:

什么样的事情会导致这些错误? 在构build文件中损坏的东西。

我如何debugging它们? 清理项目和重build。 如果它是固定的,这可能是问题。