如何调试堆损坏错误?

162 c++ windows debugging heap

我在Visual Studio 2008下调试一个(本机)多线程C++应用程序.在看似随机的场合,我得到一个"Windows触发了一个断点......"错误,并注意到这可能是由于堆.这些错误并不会立即使应用程序崩溃,尽管它很可能会在之后崩溃.

这些错误的一个大问题是它们只在实际发生损坏后弹出,这使得它们很难跟踪和调试,尤其是在多线程应用程序上.

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

  • 我该如何调试它们?

提示,工具,方法,启发......欢迎.

lea*_*der 126

Application VerifierWindows调试工具相结合是一个了不起的设置.您可以将它们作为Windows驱动程序工具包或较轻的Windows SDK的一部分.(在研究关于堆损坏问题早期问题时,发现了关于Application Verifier的问题.)我过去也使用过BoundsChecker和Insure ++(在其他答案中提到过),尽管我对Application Verifier中有多少功能感到惊讶.

电动围栏(又名"efence"),dmalloc,valgrind等等都值得一提,但其中大多数都比*nix更容易在Windows下运行.Valgrind非常灵活:我调试了大型服务器软件,使用它有很多堆问题.

当所有其他方法都失败时,您可以提供自己的全局运算符new/delete和malloc/calloc/realloc重载 - 如何执行此操作会因编译器和平台而有所不同 - 这将是一项投资 - 但从长远来看,它可能会得到回报.理想的功能列表应该看起来很熟悉dmalloc和electricfence,以及令人惊讶的优秀书籍编写固体代码:

  • 哨兵值:在每次分配前后允许多一点空间,尊重最大对齐要求; 填充幻数(有助于捕获缓冲区溢出和下溢,以及偶尔的"狂野"指针)
  • alloc fill:用一个非魔术非0值填充新的分配 - Visual C++已经在Debug版本中为你做这个(帮助捕获未初始化的vars的使用)
  • 自由填充:使用魔法非0值填充释放的内存,如果在大多数情况下解除引用,则触发段错误(有助于捕获悬空指针)
  • 延迟释放:不要将释放的内存返回堆中一段时间​​,保持它自由填充但不可用(有助于捕获更多悬空指针,捕获近似双重释放)
  • 跟踪:能够记录分配的位置有时是有用的

请注意,在我们的本地自制软件系统中(对于嵌入式目标),我们将跟踪与大多数其他内容分开,因为运行时开销要高得多.


如果您对更多重载这些分配函数/运算符的理由感兴趣,请查看我的答案"任何理由重载全局运算符new和delete?" ; 除了无耻的自我提升,它列出了有助于跟踪堆损坏错误的其他技术,以及其他适用的工具.

  • 应用程序验证程序值得注意的一件小事:您必须在符号搜索路径中的微软符号服务器符号之前注册Application Verifier的符号,如果您使用它...让我进行一些搜索以找出原因!avrf不是找到它需要的符号. (3认同)

Can*_*pus 35

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

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

现在重新启动exe并附加到调试器.启用页堆后,只要发生堆损坏,应用程序就会进入调试器.


小智 12

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

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_CHECK_ALWAYS_DF );
Run Code Online (Sandbox Code Playgroud)


Chr*_*isW 8

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

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

我该如何调试它们?

使用一种工具,为您的可执行文件添加自动边界检查:即Unix上的valgrind,或Windows上的BoundsChecker(维基百科也建议使用Purify和Insure ++)等工具.

请注意,这些会降低您的应用程序速度,因此如果您的应用程序是软实时应用程序,它们可能无法使用.

另一种可能的调试辅助工具可能是MicroQuill的HeapAgent.

  • 使用调试运行时(/MDd 或 /MTd 标志)重建应用程序将是我的第一步。它们在 malloc 和 free 上执行额外的检查,并且通常在缩小错误位置方面非常有效。 (2认同)

Sta*_*ked 8

我从检测对释放内存的访问获得的一个快速提示是:

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

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


Jar*_*Par 5

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

事实证明,我错误地使用该HEAP_NO_SERIALIZE选项创建了堆。这本质上使得堆函数在没有线程安全的情况下运行。如果使用得当,它会提高性能,但如果您在多线程程序中使用 HeapAlloc,则不应使用它 [1]。我之所以提到这一点,是因为您的帖子提到您有一个多线程应用程序。如果您在任何地方使用 HEAP_NO_SERIALIZE,请将其删除,它可能会解决您的问题。

[1] 在某些情况下这是合法的,但它要求您序列化对 Heap* 的调用,并且对于多线程程序通常不是这种情况。


Shi*_*Yip 5

我发现每次都有用和工作的最好的工具是代码审查(有很好的代码审查员).

除了代码审查,我首先尝试Page Heap.页面堆需要几秒钟的时间来设置,运气好的话可能会确定您的问题.

如果页面堆没有运气,请从Microsoft 下载Windows调试工具并学习使用WinDbg.抱歉无法为您提供更具体的帮助,但是多线程堆损坏更多的是艺术而不是科学.谷歌的"WinDbg堆腐败",你应该找到很多关于这个主题的文章.