找出堆内存损坏的位置

时间:2015-11-24 10:45:32

标签: c++ windows

我知道已经存在许多类似的问题和答案,但我无法解决我的问题。

在我的大应用程序中,堆在某处被破坏,我无法找到它。我也使用像gflags这样的工具,但没有运气。

我在下面的示例中尝试了gflags,它会破坏堆的目的:

char* pBuffer = new char[256];
memset(pBuffer, 0, 256 + 1);
delete[] pBuffer;

第2行会覆盖堆,但如何通过gflags,windbg等工具找到它。可能是我没有正确使用gflags。

4 个答案:

答案 0 :(得分:2)

如果自动化工具(如电栅栏或valgrind)没有做到这一点,并专心盯着你的代码试图找出它可能出错的地方并没有帮助,并且禁用/启用各种操作(直到你得到堆腐败的存在和之前已执行或未执行的操作之间的关联)来缩小它似乎不起作用,你总是可以尝试这种技术,试图尽早发现腐败,以便更容易追查来源:

创建自己的自定义new和delete运算符,在分配的内存区域周围放置明显的防护区域,如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <new>

// make this however big you feel is "big enough" so that corrupted bytes will be seen in the guard bands
static int GUARD_BAND_SIZE_BYTES = 64;

static void * MyCustomAlloc(size_t userNumBytes)
{
    // We'll allocate space for a guard-band, then space to store the user's allocation-size-value,
    // then space for the user's actual data bytes, then finally space for a second guard-band at the end.
    char * buf = (char *) malloc(GUARD_BAND_SIZE_BYTES+sizeof(userNumBytes)+userNumBytes+GUARD_BAND_SIZE_BYTES);
    if (buf)
    {
       char * w = buf;
       memset(w, 'B', GUARD_BAND_SIZE_BYTES);          w += GUARD_BAND_SIZE_BYTES;
       memcpy(w, &userNumBytes, sizeof(userNumBytes)); w += sizeof(userNumBytes);
       char * userRetVal = w;                          w += userNumBytes;
       memset(w, 'E', GUARD_BAND_SIZE_BYTES);          w += GUARD_BAND_SIZE_BYTES;
       return userRetVal;
    }
    else throw std::bad_alloc();
}

static void MyCustomDelete(void * p)
{
    if (p == NULL) return;   // since delete NULL is a safe no-op

    // Convert the user's pointer back to a pointer to the top of our header bytes
    char * internalCP = ((char *) p)-(GUARD_BAND_SIZE_BYTES+sizeof(size_t));

    char * cp = internalCP;
    for (int i=0; i<GUARD_BAND_SIZE_BYTES; i++)
    {
        if (*cp++ != 'B')
        {
            printf("CORRUPTION DETECTED at BEGIN GUARD BAND POSITION %i of allocation %p\n", i, p);
            abort();
        }
    }

    // At this point, (cp) should be pointing to the stored (userNumBytes) field
    size_t userNumBytes = *((const size_t *)cp);
    cp += sizeof(userNumBytes);  // skip past the user's data
    cp += userNumBytes;

    // At this point, (cp) should be pointing to the second guard band
    for (int i=0; i<GUARD_BAND_SIZE_BYTES; i++)
    {
        if (*cp++ != 'E')
        {
            printf("CORRUPTION DETECTED at END GUARD BAND POSITION %i of allocation %p\n", i, p);
            abort();
        }
    }

    // If we got here, no corruption was detected, so free the memory and carry on
    free(internalCP);
}

// override the global C++ new/delete operators to call our
// instrumented functions rather than their normal behavior
void * operator new(size_t s)    throw(std::bad_alloc)   {return MyCustomAlloc(s);}
void * operator new[](size_t s)  throw(std::bad_alloc)   {return MyCustomAlloc(s);}
void operator delete(void * p)   throw()                 {MyCustomDelete(p);}
void operator delete[](void * p) throw()                 {MyCustomDelete(p);}

......上面的内容足以让你获得Electric-Fence风格的功能,因为如果有任何内容写入两个64字节&#34;保护频带中的任何一个&#34;在任何新的/删除内存分配的开头或结尾,然后当分配被删除时,MyCustomDelete()会注意到损坏并使程序崩溃。

如果这还不够好(例如,因为在删除发生以来已经发生了很多事情,因为很难说出导致腐败的原因),你可以更进一步让MyCustomAlloc()将分配的缓冲区添加到单个/全局双向链接的分配列表中,并让MyCustomDelete()从同一个列表中删除它(如果你的程序是多线程的,请确保序列化这些操作!)。这样做的好处是,您可以添加另一个称为例如CheckForHeapCorruption()将迭代该链表并检查链表中每个分配的保护带,并报告其中是否有任何损坏。然后,您可以在整个代码中调用CheckForHeapCorruption(),这样当发生堆损坏时,将在下次调用CheckForHeapCorruption()时检测到它,而不是在稍后的某个时间。最终你会发现一个调用CheckForHeapCorruption()的调用以飞扬的颜色传递,然后下一次调用CheckForHeapCorruption(),几行之后就检测到了损坏,此时你就知道腐败是由执行之间执行的任何代码引起的。两次调用CheckForHeapCorruption(),然后你可以研究那个特定的代码来弄清楚它做错了什么,和/或在必要的时候将更多的CheckForHeapCorruption()调用添加到该代码中。

重复直到bug变得明显。祝你好运!

答案 1 :(得分:1)

如果同一个变量一直被破坏,数据断点是查找负责更改的代码的快速而简单的方法(如果您的IDE支持它们)。 (调试 - >新断点 - &gt; MS Visual Studio 2008中的新数据断点...)。如果您的堆损坏更随机,他们将无法帮助(但我认为,如果它有帮助,我会分享简单的答案)。

答案 2 :(得分:0)

我认为在Windows上也支持一种称为电围栏的工具。

基本上,它的作用是劫持malloc和co,使每个分配在页面边界处结束并标记下一页不可访问。

效果是在缓冲区溢出时出现seg错误。

它可能还有缓冲区欠载的选项。

答案 3 :(得分:0)

请阅读此链接 Visual Studio - how to find source of heap corruption errors

Is there a good Valgrind substitute for Windows?

它讲述了在Windows上查找堆问题的技巧。

但另一方面,您总是可以编写(如果您正在编写新代码)内存管理器。 要做的是:使用你的包装api,它将调用malloc / calloc等。

假设你有api myMalloc(size_t len); 然后在你的函数里面,你可以尝试配置HEADER + len + FOOTER。 在您的标题保存信息,如分配大小或可能是更多信息。在页脚,添加一些神奇的数字,如deadbeef。并从myMalloc返回ptr(来自malloc)+ HEADER。

当使用myfree(void * ptr)释放它时,然后只需执行ptr -HEADER,检查len,然后跳转到FOOTER = ptr-HEADER +真正的全部len。在这个偏移处,你应该找到deadbeef,如果你没找到,那么你知道,它已经被破坏了。