C++ 在Windows上立即检测堆损坏错误。怎么用?
我睡不着!:) 我在Windows上有一个相当大的项目,遇到了一些堆损坏问题。我已经读了这么多书,包括这个不错的主题:,但是没有什么适合帮助我摆脱束缚C++ 在Windows上立即检测堆损坏错误。怎么用?,c++,c,heap-memory,heap-corruption,virtualalloc,C++,C,Heap Memory,Heap Corruption,Virtualalloc,我睡不着!:) 我在Windows上有一个相当大的项目,遇到了一些堆损坏问题。我已经读了这么多书,包括这个不错的主题:,但是没有什么适合帮助我摆脱束缚Debug CRT和BoundsChecker检测到堆损坏,但地址总是不同,检测点总是远离实际的内存覆盖。我一直到半夜才睡觉,并制作了以下黑客程序: DWORD PageSize = 0; inline void SetPageSize() { if ( !PageSize ) { SYSTEM_INFO sysI
Debug CRT
和BoundsChecker
检测到堆损坏,但地址总是不同,检测点总是远离实际的内存覆盖。我一直到半夜才睡觉,并制作了以下黑客程序:
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);
}
一些堆损坏错误变得很明显,我能够修复它们。退出时不再出现调试CRT警告。但是,我有一些关于这个黑客的问题:
1.它会产生任何误报吗
2.它会错过一些堆损坏吗?(即使我们更换malloc/realloc/free?)
3.它无法在内存不足的32位上运行,只能在64位上运行。我说的对吗?我们只是耗尽了32位上的虚拟地址空间?这不会捕获:
- 使用未初始化内存(一旦分配了指针,您可以随意从中读取垃圾)
- 缓冲区溢出(除非超出页面大小边界)
operator delete
可以检查它们是否被覆盖(指示缓冲区运行过度或不足)
目前,在您的方案中,这将被静默地允许,而切换回malloc
等将允许它静默地损坏堆,并在稍后显示为错误(例如,在过度运行后释放块时)
但是,您无法捕获所有内容:例如,请注意,如果潜在问题是某个地方的(有效)指针被垃圾覆盖,则在损坏的指针被取消引用之前,您无法检测到这一点
它能产生假阳性吗
因此,这将只捕获类“use after free()”的bug。为此,我认为,这是相当好的
如果你试图删除一些不是新的,那是另一种类型的bug。在delete
中,应首先检查内存是否已分配。你不应该盲目地释放内存并将其标记为无法访问。当有人试图删除某个不应该删除的内容时,我会尽量避免这种情况,并报告(比如,执行调试中断),因为它从来都不是新的
它会错过一些堆损坏吗?(即使我们更换malloc/realloc/free?)
显然,这不会捕获new
和相应的delete
之间堆数据的所有损坏。它将只捕获在删除之后尝试的内容
例如:
它无法在32位目标上运行,没有内存不足错误,仅在64位上运行。32位上的虚拟地址空间用完了,我说的对吗
通常,在32位Windows上,大约有2GB的虚拟地址空间可用。这对于提供的代码中最多~524288new
,都是好的。但对于大于4KB的对象,您将能够成功地分配更少的实例。然后地址空间碎片将进一步减少这个数量
如果您在程序的生命周期中创建了许多对象实例,这将是一个理想的结果。是的,您当前的答案可能会忽略-下的缓冲区的堆损坏和溢出。
您的delete()函数非常好!
我以类似的方式实现了一个新的()函数,该函数为不足和溢出添加了保护页
从GFlags文档中,我得出结论,它仅能防止溢出
请注意,当仅返回运行不足的保护页旁边的指针时,溢出的保护页很可能位于远离分配对象的位置,并且在分配对象未受保护后紧邻。
为了补偿这一点,需要返回这样一个指针,即对象位于溢出保护页之前(在这种情况下,再次检测到欠运行的可能性较小)。
下面的代码为new()的每次调用交替执行一个或另一个操作。或者您可能希望将其修改为使用threadsafe random generator,以防止对调用new()的代码的任何干扰。
考虑到所有这一切,我们应该意识到,检测以下代码的不足和溢出在某种程度上仍然是概率性的,这在某些对象在整个程序期间仅分配一次的情况下尤其重要
NB因为new()返回一个修改过的aaAddress,所以delete()函数也必须稍微调整一下,所以现在它对VirtualFree()和VirtualProtect()使用mbi.AllocationBase而不是ptr
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);
}
以延迟方式捕获缓冲区溢出没有问题-调试CRT可以做到这一点。你知道如何改进这种方法以立即捕获缓冲区溢出吗?真的,我认为你要么需要访问硬件监视点,要么需要一些聪明的内存保护技巧(理想情况下是处理陷阱的能力)。另一方面,Valgrind基本上是通过解释程序来实现的,因此它可以拦截每一次读写。“缓冲区溢出”-偶尔我会使用自定义分配器来解决这个问题,该分配器在特定的位置分配负载的最后一个字节正是页面上的最后一个字节,下一页是一个防护页面,它遇到了访问冲突异常,这在超限情况下非常有效-。你是在试图重新发明GFLAGs吗?见@Remus Rusanu:geat链接!我很想在24小时前拥有它:)下次你会知道:)虽然它会捕获免费错误后的重用,但请注意
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);
}