修剪延迟的空闲队列时堆损坏

Posted

技术标签:

【中文标题】修剪延迟的空闲队列时堆损坏【英文标题】:Heap corruption when trimming delayed free queue 【发布时间】:2011-06-24 07:22:49 【问题描述】:

我目前正在尝试在我们的代码库中追踪堆损坏的来源,当全页堆跟踪打开时不会出现(因此只有正常的页面跟踪)。

我正在使用应用程序验证程序来打破损坏,并获得一个不太有用的停止代码 00000008:

APPLICATION_VERIFIER_HEAPS_CORRUPTED_HEAP_BLOCK (8) 损坏的堆块。 如果无法将堆块中的损坏置于更具体的类别中,则会发出此一般错误。

======================================== VERIFIER STOP 00000008: pid 0xD30: 损坏的堆块。

00000000 :调用中使用的堆句柄。 0861C000:操作中涉及的堆块。 0000043C : 堆块的大小。 00000000 : 保留

========================================

我不得不删减报告以保护无辜者,但请耐心等待。调用堆栈显示:

1000c540 00000008 00000000 vrfcore!VerifierStopMessageEx+0x543
00000008 7c969624 00000000 vrfcore!VfCoreRedirectedStopMessage+0x81
00000000 00000009 0861c000 ntdll!RtlpDphReportCorruptedBlock+0x101
04a680ee 01001002 03ce1000 ntdll!RtlpDphTrimDelayedFreeQueue+0x84
03ce1000 01001002 04a680ee ntdll!RtlpDphNormalHeapFree+0xc0
03ce0000 01001002 137a0040 ntdll!RtlpDebugPageHeapFree+0x79
03ce0000 01001002 137a0040 ntdll!RtlDebugFreeHeap+0x2c
03ce0000 01001002 137a0040 ntdll!RtlFreeHeapSlowly+0x37
03ce0000 00000000 137a0040 ntdll!RtlFreeHeap+0xf9
137a0040 137a0040 030dfe61 msvcrt!free+0xc3

现在,最初,我将注意力集中在对free() 的调用上,假设我试图释放的内存是堆损坏的罪魁祸首。情况可能仍然如此,但我不再相信。看着0x137a0040,当我逐步执行删除调用时,内存似乎被RtlpDphNormalHeapFree() 的调用正确释放了。我总结它已被正确释放,因为从 0x137a0040 到其上限约 76mb 的内存仅包含 f0,将 here 定义为释放内存。

所以我的注意力转向了在呼叫RtlpDphReportCorruptedBlock()RtlpDphTrimDelayedFreeQueue() 之前的呼叫。传递给RtlpDphReportCorruptedBlock() 的参数将向我表明(只是猜测,我找不到关于这些函数声明的任何提示)是损坏的块。对该区块的调查显示如下:

0861c000 f0 f0 f0 f0 4f f0 f0 f0 f0 f0 f0 f0 f0 f0 f0 f0 f0 f0 f0 ....O..............

为什么这第 5 个字节是 4f,而其他的都是 f0(已经释放)? RtlpDphTrimDelayedFreeQueue() 是做什么的?问题(如果这是问题)是这个函数试图释放显然已经释放的内存,还是这个函数期望这个内存已经是空闲的,并且在遇到第 5 个字节时丢失了绘图?

(第5个字节是唯一的奇数,0x0861c0000x0861c43cf0

不幸的是,虽然我可以 100% 地重现堆损坏,但每次我在其上放置数据断点时,地址似乎都会发生变化。

我在 Windows XP SP3 上运行,应用程序是用 VC++6 编写的

有什么想法吗?

【问题讨论】:

升级你的编译器。 VC6 太老了。 @DeadMG:不幸的是,这不是一个选项。 【参考方案1】:

这表明您在释放块后修改了它 - 可能来自不同的线程,或者因为某些东西仍然有指向它的指针。 (当你释放它时,运行时将它设置为全 F0,保持一段时间,然后检查它是否仍然全 F0;它不是,所以它必须在释放后被修改。)

如果损坏在块中的恒定偏移处,您可以在该位置放置一个断点,在调用free() 时更改。

【讨论】:

感谢您的信息,这就是我认为正在发生的事情。不幸的是地址一直在变化,所以数据断点不是一个选项。 块内损坏的偏移量是否改变(而不是块本身的地址)? 不,它总是相同的偏移量。 然后,如果你在代码块被释放的地方设置断点,你就可以计算出将被破坏的地址,并可以在那里设置数据断点。除非停在免费的地方当然会改变行为 - 我已经发生了这种情况。 不幸的是,重新编译将堆损坏移到其他地方,不再被捕获(可怕)。我接受这个作为答案,因为它回答了我最初的大部分问题。【参考方案2】:

C 还是 C++?

如果是 C++,也许你可以覆盖 new & delete 并自己找到它。只是永远不要真正释放内存,而是放入您的银行。分配内存之前和之后的毒域,当内存在你的银行时,将毒药放在内存上,并一直检查毒药。

如果是 C,你也许可以用#define malloc 做类似的事情。我还会搜索 VC6 是否允许您放入处理程序而不是 malloc 和 free。

【讨论】:

C++。我正在考虑覆盖 new 和 delete,但我不确定哪个(多个)DLL 正在分配此内存。我可能弄错了,但我必须在每个 DLL 中单独覆盖才能跟踪它? @Jack 你可能是对的。由于 C++ 标准不以任何方式引用 DLL,因此如果覆盖 new 是否在进程范围内,它可能没有在任何地方指定。但是,您应该给它一个测试程序并检查。也许只在一个地方覆盖就足够了,它适用于所有 DLL。无论如何,一旦编写了检查代码,让它在任何 DLL 上工作应该不会太难。【参考方案3】:

看起来您正在处理堆损坏,并且几乎可以肯定损坏发生在您发布的调用堆栈实际崩溃之前的某个时间。 Rtl...() 函数不会导致损坏,它们只是强制检测它。

This MSDN message 描述了与您的问题类似的问题以及一些调试方法。还有this MS-KB article 描述了VC6 中的堆损坏。这两个链接(以及我发现的其他一些链接)都提到了多线程,这是检查您是否正在使用它的东西。

还有来自 MS 的 PageHeap 应用程序,尽管它可能与 Application Verifier 做同样的事情。

【讨论】:

谢谢,我会查看链接。 AFAIK App Verifier 只是 PageHeap/gflags.exe 的一个接口。

以上是关于修剪延迟的空闲队列时堆损坏的主要内容,如果未能解决你的问题,请参考以下文章

从工厂函数返回右值引用时堆/内存损坏

通过 DllImport 调用非托管函数时堆损坏

BZOJ_2343_[Usaco2011 Open]修剪草坪 _单调队列_DP

[BZOJ2442][Usaco2011 Open]修剪草坪 dp+单调队列优化

bzoj2442[Usaco2011 Open]修剪草坪 单调队列优化dp

P2627 修剪草坪[dp][单调队列]