Невозможно VirtualAlloc для свободного региона, возвращенного VirtualQuery
Я пытаюсь выделить определенный объем памяти в некотором диапазоне памяти в DLL, которая загружается в приложении Windows.
То, как я это делаю, использует VirtualQuery()
искать область памяти, которая помечена как свободная и находится в границах, где мне требуется сделать распределение. Что я вижу, так это то, что регион помечен как MEM_FREE
VirtualAlloc()
иногда не удается выделить память.
Код очень близок к следующему:
LPVOID address = NULL, mem = NULL;
for (address = LOWER_RANGE; address < UPPER_RANGE;) {
MEMORY_BASIC_INFORMATION mbi = {0};
if (VirtualQuery(address, &mbi, sizeof(mbi))) {
if (mbi.State == MEM_FREE && mbi.RegionSize >= ALLOC_SIZE) {
mem = VirtualAlloc(address, ALLOC_SIZE,
MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READ);
if (mem) {
break;
}
}
}
address = mbi.BaseAddress + mbi.RegionSize;
}
когда VirtualAlloc()
выходит из строя, GetLastError()
возвращается ERROR_INVALID_ADDRESS
(487).
То, как я обошел это, - если оно достаточно большое, просканируй mbi.RegionSize
используя шаги размера страницы, чтобы найти адрес, который позволит мне выделить память, которая мне нужна.
Почему это согласно VirtualQuery
весь регион должен быть свободным, и я должен быть в состоянии выделить внутри любой адрес, который я хочу, но обычно, когда первый VirtualAlloc
не удается сделать несколько шагов, пока он, наконец, не будет успешным.
2 ответа
Я нашел решение, которое сработало для меня. В моем предыдущем примере я пытался выделить и зарезервировать одновременно; и адреса, которые я использовал, не были согласованы с гранулярностью распределения. Поэтому мне пришлось округлить до ближайшего кратного распределения гранулярности, который подходит внутри региона.
Как-то так сработало (заметьте, я не проверял этот код).
PVOID address = NULL, mem = NULL;
for (address = LOWER_RANGE; address < UPPER_RANGE;) {
MEMORY_BASIC_INFORMATION mbi = {0};
if (VirtualQuery(address, &mbi, sizeof(mbi))) {
PVOID reserveAddr = mbi.BaseAddress + (mbi.BaseAddress % alloc_gran);
PVOID end_addr = mbi.BaseAddress + mbi.RegionSize;
if (mbi.State == MEM_FREE &&
mbi.RegionSize >= ALLOC_SIZE &&
reserveAddr + ALLOC_SIZE <= end_addr) {
mem = VirtualAlloc(reserveAddr, ALLOC_SIZE,
MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READ);
if (mem) {
break;
}
}
}
address = mbi.BaseAddress + mbi.RegionSize;
}
Когда вы предоставляете адрес VirtualAlloc и используете флаг MEM_RESERVE, адрес округляется до ближайшего кратного гранулирования распределения (64 КБ). Вероятно, вы обнаружите, что области свободных страниц находятся в выделенных блоках по 64 КБ, которые не полностью зарезервированы. Незарезервированные страницы в этих блоках не могут быть распределены (или зарезервированы), пока не будут освобождены все страницы в выделенном блоке.
Из документации MSDN для VirtualAlloc:
lpAddress [in, необязательно]
Начальный адрес региона для выделения. Если память зарезервирована, указанный адрес округляется до ближайшего кратного гранулярности выделения. [...] Чтобы определить размер страницы и степень детализации размещения на главном компьютере, используйте функцию GetSystemInfo.