立即检测Windows上的堆损坏错误。 怎么样?

我无法入睡! :)

我在Windows上有一个相当大的项目,并遇到一些堆腐败问题。 我已经阅读了所有这些,包括这个不错的主题:如何调试堆损坏错误?,但没有什么适合帮助我开箱即用。 Debug CRTBoundsChecker检测到堆损坏,但地址总是不同,并且检测点始终远离实际的内存覆盖。 我一直睡到半夜才制作下面的黑客:

DWORD PageSize = 0;

inline void SetPageSize()
{
    if ( !PageSize )
    {
        SYSTEM_INFO sysInfo;
        GetSystemInfo(&sysInfo);
        PageSize = sysInfo.dwPageSize;
    }
}

void* operator new (size_t nSize)
{
    SetPageSize();
    size_t Extra = nSize % PageSize;
    nSize = nSize + ( PageSize - Extra );
    return Ptr = VirtualAlloc( 0, nSize, MEM_COMMIT, PAGE_READWRITE);
}

void operator delete (void* pPtr)
{
    MEMORY_BASIC_INFORMATION mbi;
    VirtualQuery(pPtr, &mbi, sizeof(mbi));
    // leave pages in reserved state, but free the physical memory
    VirtualFree(pPtr, 0, MEM_DECOMMIT);
    DWORD OldProtect;
    // protect the address space, so noone can access those pages
    VirtualProtect(pPtr, mbi.RegionSize, PAGE_NOACCESS, &OldProtect);
}

一些堆腐败错误变得明显,我能够解决它们。 退出时没有更多Debug CRT警告。 不过,我对这个黑客有一些疑问:

1.它能产生任何误报吗?

2.它能错过一些堆损坏吗? (即使我们替换malloc / realloc / free?)

3.无法使用OUT_OF_MEMORY在32位上OUT_OF_MEMORY ,仅在64位上运行。 我是对的,我们只是用完了32位的虚拟地址空间?


它能产生任何误报吗?

所以,这只会捕获类“在free()后使用”的错误。 为此,我认为,这是相当不错的。

如果您尝试delete不是new的东西,那是一种不同类型的错误。 在delete你应该首先检查内存是否确实分配。 你不应该盲目地释放内存并将其标记为不可访问。 我会尽量避免和报告(通过,说,做了调试中断)时,有一个尝试delete的东西,因为它是永远不应该被删除, new “版。

它可以错过一些堆腐败? (即使我们替换malloc / realloc / free?)

显然,这不会捕获new和相应的delete之间的堆数据的所有损坏。 它只会捕获delete后尝试的那些。

例如:

myObj* = new MyObj(1,2,3);
// corruption of *myObj happens here and may go unnoticed
delete myObj;

无法在32位目标上运行OUT_OF_MEMORY错误,仅在64位上运行。 我说对了,我们只是用完了32位的虚拟地址空间?

通常情况下,您在32位Windows上拥有约2GB的虚拟地址空间。 这在所提供的代码中最多可以达到524288个new 。 但对于大于4KB的对象,您将能够成功分配比此更少的实例。 然后地址空间碎片会进一步减少这个数字。

如果在程序的生命周期中创建许多对象实例,那么这是一个完美的预期结果。


这不会赶上:

  • 使用未初始化的内存(一旦你的指针被分配,你可以随意读取垃圾)
  • 缓冲区溢出(除非您超出PageSize边界)
  • 理想情况下,您应该在分配的块之前和之后编写一个众所周知的位模式,以便operator delete可以检查它们是否被覆盖(指定的缓冲区运行过度或运行不足)。

    目前,这可以在你的方案中默默的允许,并且切换回malloc等可以让它默默地损坏堆,并在稍后显示为错误(例如,在溢出之后释放块)。

    你不能抓住所有东西:例如注意,如果底层问题是(有效的)指针被垃圾覆盖,那么直到损坏的指针被取消引用后才能检测到它。


    是的,你目前的答案可能会错过缓冲 不足超支堆损坏。
    你的delete()函数非常好!
    我以类似的方式实现了一个new()函数,它为under-和overruns增加了guard页面。
    我从GFlags文件得出结论,它只保护超限。

    需要注意的是,当简单地返回一个位于欠载防护页面旁边的指针时,超出的防护页面可能位于远离分配对象和紧邻区域的位置 ,在分配的对象未被保护之后
    为了弥补这一点,需要返回一个指针,该对象位于超限防护页面之前(在这种情况下,再次出现欠载情况的可能性较小)。
    下面的代码为new()的每个调用交替地执行一个或另一个。 或者可能需要修改它以使用线程安全随机生成器来防止对调用new()的代码造成干扰。
    考虑到这一点,应该意识到,通过下面的代码检测欠量和超量在一定程度上仍然是概率性的 - 这在一些对象在整个程序期间仅被分配一次的情况下尤其相关。

    NB! 因为new()返回一个修改后的aadress,所以delete()函数也需要稍微调整一下,所以它现在使用mbi.AllocationBase而不是ptr来表示VirtualFree()和VirtualProtect()。

    PS。 驱动程序验证者的特殊池使用类似的技巧。

    volatile LONG priorityForUnderrun = rand(); //NB! init with rand so that the pattern is different across program runs and different checks are applied to global singleton objects
    
    void ProtectMemRegion(void* region_ptr, size_t sizeWithGuardPages)
    {
        size_t preRegionGuardPageAddress = (size_t)region_ptr;
        size_t postRegionGuardPageAddress = (size_t)(region_ptr) + sizeWithGuardPages - PageSize;   
    
        DWORD flOldProtect1;
        BOOL preRegionProtectSuccess = VirtualProtect(
            (void*)(preRegionGuardPageAddress),
            pageSize,
            PAGE_NOACCESS,
            &flOldProtect1  
        );
    
        DWORD flOldProtect2;
        BOOL postRegionProtectSuccess = VirtualProtect(
            (void*)(postRegionGuardPageAddress),
            PageSize,
            PAGE_NOACCESS,
            &flOldProtect2  
        );
    }   
    
    void* operator new (size_t size)
    {
        size_t sizeWithGuardPages = (size + PageSize - 1) / PageSize * PageSize + 2 * PageSize;
    
        void* ptr = VirtualAlloc
        (
            NULL,
            sizeWithGuardPages,
            MEM_COMMIT | MEM_RESERVE,
            PAGE_READWRITE
        );
    
        if (ptr == NULL)    //NB! check for allocation failures
        {
            return NULL;
        }
    
        ProtectMemRegion(ptr, sizeWithGuardPages);
    
        void* result;
        if (InterlockedIncrement(&priorityForUnderrun) % 2)
            result = (void*)((size_t)(ptr) + pageSize);
        else 
            result = (void*)(((size_t)(ptr) + sizeWithGuardPages - pageSize - size) / sizeof(size_t) * sizeof(size_t)); 
    
        return result;
    }   
    
    void operator delete (void* ptr) 
    {
        MEMORY_BASIC_INFORMATION mbi;
        DWORD OldProtect;
    
        VirtualQuery(ptr, &mbi, sizeof(mbi));
        // leave pages in reserved state, but free the physical memory
        VirtualFree(mbi.AllocationBase, 0, MEM_DECOMMIT);
        // protect the address space, so noone can access those pages
        VirtualProtect(mbi.AllocationBase, mbi.RegionSize, PAGE_NOACCESS, &OldProtect);
    }
    
    链接地址: http://www.djcxy.com/p/82303.html

    上一篇: Immediate detection of heap corruption errors on Windows. How?

    下一篇: what to do if debug runs fine, but release crashes