不能在 VirtualQuery 返回的空闲区域上使用 VirtualAlloc

Can't VirtualAlloc on free region returned by VirtualQuery

我正在尝试在 Windows 应用程序中加载的 DLL 中的某个内存范围内分配一定数量的内存。

我这样做的方法是使用 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() returns ERROR_INVALID_ADDRESS (487).

我解决它的方法是,如果它足够大,使用页面大小步长扫描 mbi.RegionSize 以找到一个地址,让我可以分配我需要的内存。

为什么根据 VirtualQuery 整个区域应该是空闲的,我应该能够在我想要的任何地址内分配,但通常当第一个 VirtualAlloc 失败时我必须循环几个步骤,直到最后成功。

当您向 VirtualAlloc 提供地址并使用 MEM_RESERVE 标志时,地址会向下舍入到分配粒度 (64K) 的最接近倍数。您可能会发现空闲页面区域位于分配的 64K 块中,这些块并未完全保留。在释放已分配块中的所有页面之前,无法分配(或保留)这些块中的未保留页面。

来自 VirtualAlloc 的 MSDN 文档:

lpAddress [in, optional]

The starting address of the region to allocate. If the memory is being reserved, the specified address is rounded down to the nearest multiple of the allocation granularity. [...] To determine the size of a page and the allocation granularity on the host computer, use the GetSystemInfo function.

我找到了适合我的解决方案。在我之前的例子中,我试图同时分配和保留;并且我使用的地址与分配粒度不一致。所以我不得不四舍五入到适合该区域的最接近的分配粒度倍数。

类似这样的方法有效(注意,我还没有测试过这段代码)。

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;
}