Невозможно VirtualAlloc для свободного региона, возвращенного VirtualQuery

Я пытаюсь выделить определенный объем памяти в некотором диапазоне памяти в DLL, которая загружается в приложении Windows.

То, как я это делаю, использует VirtualQuery() искать область памяти, которая помечена как свободная и находится в границах, где мне требуется сделать распределение. Что я вижу, так это то, что регион помечен как MEM_FREEVirtualAlloc() иногда не удается выделить память.

Код очень близок к следующему:

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.

Другие вопросы по тегам