Я не могу спать! 🙂
У меня достаточно большой проект для Windows, и я столкнулся с некоторыми проблемами с кучей повреждений. Я прочитал все ТАК, включая эту приятную тему: Как отладить ошибки повреждения кучи?, однако ничто не было подходящим, чтобы помочь мне из коробки. 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-битной с OUT_OF_MEMORY
Только на 64-битных. Я прав, мы просто исчерпали виртуальное адресное пространство на 32-разрядных?
Может ли это привести к ложным срабатываниям?
Таким образом, это будет ловить только ошибки класса «use after 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-битной версии?
Обычно у вас есть около 2 ГБ виртуального адресного пространства в 32-битной Windows. Это хорошо для не более ~ 524288 new
Это как в приведенном коде. Но с объектами размером более 4 КБ вы сможете успешно выделить меньше экземпляров, чем это. И тогда фрагментация адресного пространства еще больше сократит это число.
Это вполне ожидаемый результат, если вы создадите много экземпляров объектов в течение жизненного цикла вашей программы.
Это не поймает:
В идеале вы должны написать хорошо известную битовую комбинацию до и после выделенных блоков, чтобы operator delete
может проверить, были ли они перезаписаны (указано, что буфер переполнен или недостаточно загружен).
В настоящее время это будет разрешено в вашей схеме, и переключиться обратно на malloc
и т. д. позволит молча повредить кучу, и позже появится как ошибка (например, при освобождении блока после переполнения).
Вы не можете поймать все, хотя: обратите внимание, например, что если основной проблемой является (действительный) указатель, который где-то перезаписывается мусором, вы не сможете обнаружить это, пока не будет удалена ссылка на поврежденный указатель.
Да, ваш текущий ответ может пропустить кучу повреждений буфер под- а также перерасход.
Ваш удалять() функция довольно хорошая!
Я реализовал новый () функционируют аналогичным образом, добавляя защитные страницы как для переполнения, так и для переполнения.
От GFlags Из документации я делаю вывод, что она защищает только от переполнения.
Обратите внимание, что при возврате простого указателя рядом со страницей защиты от опустошения страница защиты для переполнения, вероятно, будет расположена вдали от выделенного объекта и Непосредственная близость после того, как выделенный объект НЕ охраняется.
Чтобы компенсировать это, необходимо вернуть такой указатель, чтобы объект находился непосредственно перед защитной страницей переполнения (в этом случае повторное опустошение с меньшей вероятностью будет обнаружено).
Приведенный ниже код выполняет одно или другое попеременно для каждого вызова новый (). Или можно захотеть изменить его, чтобы вместо него использовать генератор случайных потоков, чтобы предотвратить любые помехи в коде, вызывающем новый ().
Принимая во внимание все это, следует помнить, что обнаружение недостаточного или переполнения с помощью приведенного ниже кода все еще является вероятностным в некоторой степени — это особенно актуально в случае, когда некоторые объекты выделяются только один раз на протяжении всей продолжительности программы.
NB! Так как новый () возвращает измененный адрес, удалять() Функция также должна быть немного скорректирована, так что теперь она использует mbi.AllocationBase вместо PTR за VirtualFree () а также VirtualProtect ().
PS. Проверка драйверов«s Специальный бассейн использует похожие трюки.
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);
}