elventear
elventear

Reputation: 341

Can't VirtualAlloc on free region returned by VirtualQuery

I am trying to allocate a certain amount of memory within some memory range in a DLL that gets loaded within a Windows Application.

The way I am doing it, is using VirtualQuery() to search for a region of memory that is marked as free and is within the boundaries where I require to do the allocation. What I am seeing is that even though the region is marked as MEM_FREE VirtualAlloc() fails sometimes to allocate the memory.

The code is very close to the following:

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

When VirtualAlloc() fails, GetLastError() returns ERROR_INVALID_ADDRESS (487).

The way I have worked around it is, if it is big enough, scan through mbi.RegionSize using page size steps to find an address that will allow me to allocate the memory that I need.

Why is it that according to VirtualQuery the entire region should be free and I should be able to allocate inside any address I want, but usually when the first VirtualAlloc fails I have to loop for several steps until it is finally successful.

Upvotes: 0

Views: 1751

Answers (2)

elventear
elventear

Reputation: 341

I found a solution that worked for me. I my previous example I was trying to allocate and reserve at the same time; and the addresses I was using were not aligned with the allocation granularity. So I had to round up to the nearest multiple of allocation granularity that fits inside the region.

Something like this worked (note, I have not tested this code).

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

Upvotes: 0

Ross Ridge
Ross Ridge

Reputation: 39651

When you provide an address to VirtualAlloc and use the MEM_RESERVE flag the address is rounded down to the nearest multiple of the allocation granulatiry (64K). You're probably finding regions of free pages are that are in allocated 64K blocks that aren't entirely reserved. The unreserved pages these in these blocks can't be allocated (or reserved) until all the pages in the allocated block are freed.

From MSDN documentation for VirtualAlloc:

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.

Upvotes: 5

Related Questions