7

多年来,我一直避免尝试对 operator new 做任何事情,因为我觉得它在 Windows 上是一个泥潭(尤其是使用 MFC)。更不用说,除非有一个非常令人信服的理由来搞乱全局(甚至类)new 和 delete,否则不应该这样做。

但是,我有一个令人讨厌的小内存损坏错误,我非常想追踪它。我从 CRT 调试分配器收到消息,表明先前释放的内存已被覆盖。此消息仅在稍后的分配调用期间显示,当它尝试重用一个块时(我相信这就是它的工作方式,无论如何)。

由于有问题的代码部分,错误消息和损坏点非常不相关。我所知道的是“某处某处覆盖了一些以前用单个空字节释放的内存”。(我通过使用调试器并在几次不同的运行中观察调试堆引用的内存来确定这一点)。

在用尽了关于罪魁祸首可能在哪里的明显想法之后,我只能尝试做一些更严格的事情。我突然想到,如果我能让每个被释放的块变成一个不可访问的内存页面,这样写者就会立即被 CPU 的 MMC 捕获,那将是理想的!后来稍微搜索了一下,我发现有人按照这些思路实现了一些东西:

http://www.codeproject.com/Articles/38340/Immediate-memory-corruption-detection

他的代码隐藏在大量重复发明的代码中,但提取核心概念非常容易,我已经做到了。

我现在遇到的问题是 MFC 将 new 重新定义为 DEBUG_NEW,然后进一步定义了一系列到 CRT 的调试接口。此外,它确实定义了全局运算符 new 和 delete。因此,就 C++ 而言,“用户”试图替换全局运算符 new 并删除两次,因此我得到一个链接器错误,其效果为“符号已定义”。

环顾互联网,SO,我看到了一些有前途的文章,但没有一个最终对替换全球运营商 new/delete 为 MFC 有任何积极的看法。

如何正确替换全局新和删除运算符
是否可以在 MFC 应用程序的调试版本中替换内存分配器?

我已经意识到:

  • MFC/CRT 已经为内存分配提供了丰富的调试工具。

好吧,它提供了它所提供的东西——比如让我一开始就沿着这条路走下去的信息。我现在知道正在发生腐败,但这是非常弱的酱汁!

我想提供的是保护分配(甚至只是保护释放)。这显然可以通过使用大量虚拟地址空间并将每个分配隔离起来,这非常浪费内存。好的,是的,当这是对像现在这样的特殊用途的时刻有用的仅调试代码时,看不到不利的一面。

因此,我正在拼命寻求以下解决方案

  1. 尽管 CRT/MFC 提供了一个,但强制编译器与我的全局运算符 new/delete 合作。
  2. 找到另一种方法来挂钩 MFC/CRT _heap_alloc_dbg 链,以使用我自己的代码代替他们的代码,以进行倒数第二次分配(即,我将通过操作系统的 VirtualAlloc/VirtualFree 分配以为 new 和/或马尔洛克)。

有没有人知道答案,或者阅读的好文章可能会阐明如何实现这些?

其他想法:

  1. 使用 thunk 技术在运行时替换 CRT 的 new/delete。
  2. 完全是其他一些方法?!

进一步的调查:

  • 这篇文章很酷……它为我提供了一种在运行时修补全局 new/delete 运算符的方法。但是,正如文章指出的那样,它有点骇人听闻(但是,因为我只需要它来进行调试构建,这没什么大不了的)http://zeuxcg.blogspot.com/2009/03/fighting-against-crt- heap-and-winning.html
    • 因此,尽管这达到了我想要的效果(一种替换 CRT 内存分配函数的机制),但这个实现已经过时了,到目前为止,我试图让它工作的尝试遇到了无数问题。我认为它对最初创建的版本太黑了,仅用于相对简单的控制台使用(即 C,甚至不是 C++,并且放弃了 microsoft CRT 提供的大多数调试功能)。因此,尽管这是一个非常酷的想法,但最终将花费大量时间来与当前的 VS2010 开发工作室合作,因此不值得(对我而言)。
  • 显然,这个想法有一个众所周知的版本:http ://en.wikipedia.org/wiki/Electric_Fence不幸的是,即使是 Windows 端口,我也找到了http://code.google.com/p/electric-fence-win32 /未能正确覆盖 CRT,但要求您修改所有源代码以访问电子围栏堆分配代码。:(

2012 年 5 月 3 日更新:

4

1 回答 1

4

MSVCRT 调试堆实际上非常好,并且有一些您可以使用的有用功能,例如第 n 次分配的断点等。

http://msdn.microsoft.com/en-us/library/974tc9t1(v=VS.80).aspx

除其他外,您可以插入一个分配挂钩,该挂钩输出可用于调试此类问题的调试信息等。

http://msdn.microsoft.com/en-us/library/z2zscsc2(v=vs.80).aspx

在您的情况下,您真正​​需要做的就是输出每个分配的地址、文件和行。然后当你遇到一个损坏的块时,找到地址紧接在它之前的那个块,几乎可以肯定是那个被溢出的块。您可以使用 Visual Studio 调试器中的内存视图来查看已损坏的内存地址并查看前面的块。这应该告诉你所有你需要知道的,以找出它何时被分配。

调试堆在每个分配的块上也有一个数字分配 ID,并且可以在第 n 次中断!分配,因此,如果您可以获得合理一致的重现,因此每次都损坏相同的数字块,那么您应该能够使用“第 n 次中断”功能来获得分配时的完整调用堆栈。

您还可能会发现_CrtCheckMemory了解腐败是否发生得更早很有用。只需定期调用它,一旦您将错误括起来(错误没有发生在一个中,确实发生在另一个中),将它们越来越靠近。

于 2012-05-02T16:11:26.690 回答