如何在MFC应用程序中替换全局运算符new和delete(仅限调试)



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

然而,我有一个讨厌的小内存损坏错误,我非常想追踪它。我从CRT调试分配器收到消息,指示先前释放的内存被覆盖。只有在稍后的分配调用中,当它试图重用块时,才会显示此消息(无论如何,我相信这就是它的工作方式)。

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

在用尽了罪魁祸首可能在哪里的明显想法后,我只能尝试做一些更严格的事情。我突然想到,如果我能让每个释放的块变成内存的无访问页,这样写入程序就会立即被CPU的MMC捕获,那就太理想了!经过一点搜索,我发现有人已经实现了一些类似的东西:

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

他的代码被大量的"重新发明轮子"代码所掩盖,但提取核心概念非常容易,我已经做到了

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

环顾互联网,我看到了一些有希望的文章,但没有一篇文章最终对取代MFC的全局运算符new/delete有任何积极的意义。

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

我已经知道:

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

好吧,它提供了它所提供的东西,比如最初让我走上这条路的消息。我现在知道腐败正在发生,但这是非常薄弱的酱汁!

我想提供的是有保护的分配(甚至只是有保护的交易)。通过使用大量的虚拟地址空间并将每个分配都隔离开来,这显然是可能的,这非常浪费内存。好吧,是的,当这是对现在这样的特殊用途有用的仅调试代码时,看不到它的负面影响。

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

  1. 尽管CRT/MFC提供了全局运算符new/delete,但仍强制编译器与我的全局运算符进行协处理
  2. 找到另一种方法将MFC/CRT_heap_alloc_dbg链挂接到底,使用我自己的代码代替他们的代码,进行笔的最终分配(即,我将通过操作系统的VirtualAlloc/VirtualFree进行分配,为新的和/或malloc提供内存)

有人知道答案吗?或者有什么好的文章可以让我们了解如何实现这些目标

其他想法:

  1. 在运行时使用thunk技术替换CRT的新/删除
  2. 完全采用其他方法

进一步调查:

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

2012年5月3日更新:

  • 现在我发现Windows已经提供了Electric Fence的实现,可以通过GFLAGS调试工具访问http://support.microsoft.com/kb/286470这可以在正在测试的应用程序外部打开和关闭。它本质上与我感兴趣的技术相同,并且具有DUMA项目(电动围栏的一个分支-http://duma.sourceforge.net/

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个块上中断!分配,所以如果你可以得到一个合理一致的repo,所以每次都会损坏相同的数字块,那么你应该能够使用"第n次中断"功能来获得分配时间的完整调用堆栈。

您可能还发现_CrtCheckMemory有助于查明是否早就发生了损坏。只要周期性地调用它,一旦你把bug括起来(错误没有发生在一个错误中,但确实发生在另一个错误),就会把它们越来越靠近。

最新更新