Refactor memory management into wibo::heap

- Removes blockUpper2GB hack; we now start early in the process
  and reserve all (available) space in the lower 2GB address
  space, leaving the upper 2GB untouched for host code
- All virtual memory operations flow through wibo::heap for
  bookkeeping
- All guest code uses a guest mimalloc area + thread-local heaps
  reserved in the guest address space
This commit is contained in:
2025-11-02 20:18:23 -07:00
parent 52cdd7c811
commit 4dc599381f
23 changed files with 1558 additions and 753 deletions

View File

@@ -4,8 +4,8 @@
#include "context.h"
#include "errors.h"
#include "handles.h"
#include "heap.h"
#include "internal.h"
#include "modules.h"
#include "strutil.h"
#include <cerrno>
@@ -18,7 +18,6 @@
#include <sys/mman.h>
#include <unistd.h>
#include <utility>
#include <vector>
namespace {
@@ -55,32 +54,12 @@ struct ViewInfo {
DWORD protect = PAGE_NOACCESS;
DWORD allocationProtect = PAGE_NOACCESS;
DWORD type = MEM_PRIVATE;
bool managed = false;
};
std::map<uintptr_t, ViewInfo> g_viewInfo;
std::mutex g_viewInfoMutex;
struct VirtualAllocation {
uintptr_t base = 0;
size_t size = 0;
DWORD allocationProtect = 0;
std::vector<DWORD> pageProtect;
};
std::map<uintptr_t, VirtualAllocation> g_virtualAllocations;
std::mutex g_virtualAllocMutex;
size_t systemPageSize() {
static size_t cached = []() {
long detected = sysconf(_SC_PAGESIZE);
if (detected <= 0) {
return static_cast<size_t>(4096);
}
return static_cast<size_t>(detected);
}();
return cached;
}
uintptr_t alignDown(uintptr_t value, size_t alignment) {
const uintptr_t mask = static_cast<uintptr_t>(alignment) - 1;
return value & ~mask;
@@ -97,66 +76,6 @@ uintptr_t alignUp(uintptr_t value, size_t alignment) {
return (value + mask) & ~mask;
}
bool addOverflows(uintptr_t base, size_t amount) {
return base > std::numeric_limits<uintptr_t>::max() - static_cast<uintptr_t>(amount);
}
uintptr_t regionEnd(const VirtualAllocation &region) { return region.base + region.size; }
bool rangeOverlapsLocked(uintptr_t base, size_t length) {
if (length == 0) {
return false;
}
if (addOverflows(base, length - 1)) {
return true;
}
uintptr_t end = base + length;
auto next = g_virtualAllocations.lower_bound(base);
if (next != g_virtualAllocations.begin()) {
auto prev = std::prev(next);
if (regionEnd(prev->second) > base) {
return true;
}
}
if (next != g_virtualAllocations.end() && next->second.base < end) {
return true;
}
return false;
}
std::map<uintptr_t, VirtualAllocation>::iterator findRegionIterator(uintptr_t address) {
auto it = g_virtualAllocations.upper_bound(address);
if (it == g_virtualAllocations.begin()) {
return g_virtualAllocations.end();
}
--it;
if (address >= regionEnd(it->second)) {
return g_virtualAllocations.end();
}
return it;
}
VirtualAllocation *lookupRegion(uintptr_t address) {
auto it = findRegionIterator(address);
if (it == g_virtualAllocations.end()) {
return nullptr;
}
return &it->second;
}
bool rangeWithinRegion(const VirtualAllocation &region, uintptr_t start, size_t length) {
if (length == 0) {
return start >= region.base && start <= regionEnd(region);
}
if (start < region.base) {
return false;
}
if (addOverflows(start, length)) {
return false;
}
return (start + length) <= regionEnd(region);
}
DWORD desiredAccessToProtect(DWORD desiredAccess, DWORD mappingProtect) {
DWORD access = desiredAccess;
if ((access & FILE_MAP_ALL_ACCESS) == FILE_MAP_ALL_ACCESS) {
@@ -219,93 +138,12 @@ DWORD desiredAccessToProtect(DWORD desiredAccess, DWORD mappingProtect) {
return protect;
}
void markCommitted(VirtualAllocation &region, uintptr_t start, size_t length, DWORD protect) {
if (length == 0) {
return;
}
const size_t pageSize = systemPageSize();
const size_t firstPage = (start - region.base) / pageSize;
const size_t pageCount = length / pageSize;
for (size_t i = 0; i < pageCount; ++i) {
region.pageProtect[firstPage + i] = protect;
}
}
void markDecommitted(VirtualAllocation &region, uintptr_t start, size_t length) {
if (length == 0) {
return;
}
const size_t pageSize = systemPageSize();
const size_t firstPage = (start - region.base) / pageSize;
const size_t pageCount = length / pageSize;
for (size_t i = 0; i < pageCount; ++i) {
region.pageProtect[firstPage + i] = 0;
}
}
bool moduleRegionForAddress(uintptr_t pageBase, MEMORY_BASIC_INFORMATION &info) {
if (pageBase == 0) {
return false;
}
wibo::ModuleInfo *module = wibo::moduleInfoFromAddress(reinterpret_cast<void *>(pageBase));
if (!module || !module->executable) {
return false;
}
const auto &sections = module->executable->sections;
if (sections.empty()) {
return false;
}
size_t matchIndex = sections.size();
for (size_t i = 0; i < sections.size(); ++i) {
const auto &section = sections[i];
if (pageBase >= section.base && pageBase < section.base + section.size) {
matchIndex = i;
break;
}
}
if (matchIndex == sections.size()) {
return false;
}
uintptr_t blockStart = sections[matchIndex].base;
uintptr_t blockEnd = sections[matchIndex].base + sections[matchIndex].size;
DWORD blockProtect = sections[matchIndex].protect;
for (size_t prev = matchIndex; prev > 0;) {
--prev;
const auto &section = sections[prev];
if (section.base + section.size != blockStart) {
break;
}
if (section.protect != blockProtect) {
break;
}
blockStart = section.base;
}
for (size_t next = matchIndex + 1; next < sections.size(); ++next) {
const auto &section = sections[next];
if (section.base != blockEnd) {
break;
}
if (section.protect != blockProtect) {
break;
}
blockEnd = section.base + section.size;
}
info.BaseAddress = reinterpret_cast<void *>(blockStart);
info.AllocationBase = module->executable->imageBase;
info.AllocationProtect = blockProtect;
info.RegionSize = blockEnd > blockStart ? blockEnd - blockStart : 0;
info.State = MEM_COMMIT;
info.Protect = blockProtect;
info.Type = MEM_IMAGE;
return true;
}
bool mappedViewRegionForAddress(uintptr_t request, uintptr_t pageBase, MEMORY_BASIC_INFORMATION &info) {
std::lock_guard guard(g_viewInfoMutex);
if (g_viewInfo.empty()) {
return false;
}
const size_t pageSize = systemPageSize();
const size_t pageSize = wibo::heap::systemPageSize();
for (const auto &entry : g_viewInfo) {
const ViewInfo &view = entry.second;
if (view.viewLength == 0) {
@@ -335,136 +173,12 @@ bool mappedViewRegionForAddress(uintptr_t request, uintptr_t pageBase, MEMORY_BA
return false;
}
bool virtualAllocationRegionForAddress(uintptr_t pageBase, MEMORY_BASIC_INFORMATION &info) {
const size_t pageSize = systemPageSize();
std::unique_lock lk(g_virtualAllocMutex);
VirtualAllocation *region = lookupRegion(pageBase);
if (!region) {
uintptr_t regionStart = pageBase;
uintptr_t regionEnd = regionStart;
auto next = g_virtualAllocations.lower_bound(pageBase);
if (next != g_virtualAllocations.end()) {
regionEnd = next->second.base;
} else {
regionEnd = kProcessAddressLimit;
}
if (regionEnd <= regionStart) {
regionEnd = regionStart + pageSize;
}
lk.unlock();
info.BaseAddress = reinterpret_cast<void *>(regionStart);
info.AllocationBase = nullptr;
info.AllocationProtect = 0;
info.RegionSize = regionEnd - regionStart;
info.State = MEM_FREE;
info.Protect = PAGE_NOACCESS;
info.Type = 0;
return true;
}
const uintptr_t regionLimit = region->base + region->size;
const size_t pageIndex = (pageBase - region->base) / pageSize;
if (pageIndex >= region->pageProtect.size()) {
return false;
}
const DWORD pageProtect = region->pageProtect[pageIndex];
const bool committed = pageProtect != 0;
uintptr_t blockStart = pageBase;
uintptr_t blockEnd = pageBase + pageSize;
while (blockStart > region->base) {
size_t idx = (blockStart - region->base) / pageSize - 1;
DWORD protect = region->pageProtect[idx];
bool pageCommitted = protect != 0;
if (pageCommitted != committed) {
break;
}
if (committed && protect != pageProtect) {
break;
}
blockStart -= pageSize;
}
while (blockEnd < regionLimit) {
size_t idx = (blockEnd - region->base) / pageSize;
if (idx >= region->pageProtect.size()) {
break;
}
DWORD protect = region->pageProtect[idx];
bool pageCommitted = protect != 0;
if (pageCommitted != committed) {
break;
}
if (committed && protect != pageProtect) {
break;
}
blockEnd += pageSize;
}
uintptr_t allocationBase = region->base;
DWORD allocationProtect = region->allocationProtect != 0 ? region->allocationProtect : PAGE_NOACCESS;
DWORD finalProtect = committed ? pageProtect : PAGE_NOACCESS;
lk.unlock();
info.BaseAddress = reinterpret_cast<void *>(blockStart);
info.AllocationBase = reinterpret_cast<void *>(allocationBase);
info.AllocationProtect = allocationProtect;
info.RegionSize = blockEnd - blockStart;
info.State = committed ? MEM_COMMIT : MEM_RESERVE;
info.Protect = finalProtect;
info.Type = MEM_PRIVATE;
return true;
}
void *alignedReserve(size_t length, int prot, int flags) {
const size_t granularity = kVirtualAllocationGranularity;
const size_t request = length + granularity;
void *raw = mmap(nullptr, request, prot, flags, -1, 0);
if (raw == MAP_FAILED) {
return MAP_FAILED;
}
uintptr_t rawAddr = reinterpret_cast<uintptr_t>(raw);
uintptr_t aligned = alignUp(rawAddr, granularity);
size_t front = aligned - rawAddr;
size_t back = (rawAddr + request) - (aligned + length);
if (front != 0) {
if (munmap(raw, front) != 0) {
munmap(raw, request);
return MAP_FAILED;
}
}
if (back != 0) {
if (munmap(reinterpret_cast<void *>(aligned + length), back) != 0) {
munmap(reinterpret_cast<void *>(aligned), length);
return MAP_FAILED;
}
}
return reinterpret_cast<void *>(aligned);
}
int translateProtect(DWORD flProtect) {
switch (flProtect) {
case PAGE_NOACCESS:
return PROT_NONE;
case PAGE_READONLY:
return PROT_READ;
case PAGE_READWRITE:
case PAGE_WRITECOPY:
return PROT_READ | PROT_WRITE;
case PAGE_EXECUTE:
return PROT_EXEC;
case PAGE_EXECUTE_READ:
return PROT_READ | PROT_EXEC;
case PAGE_EXECUTE_READWRITE:
case PAGE_EXECUTE_WRITECOPY:
return PROT_READ | PROT_WRITE | PROT_EXEC;
default:
DEBUG_LOG("Unhandled flProtect: %u, defaulting to RW\n", flProtect);
return PROT_READ | PROT_WRITE;
}
}
} // namespace
namespace kernel32 {
HANDLE WINAPI CreateFileMappingA(HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMappingAttributes, DWORD flProtect,
DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCSTR lpName) {
DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCSTR lpName) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("CreateFileMappingA(%p, %p, %u, %u, %u, %s)\n", hFile, lpFileMappingAttributes, flProtect,
dwMaximumSizeHigh, dwMaximumSizeLow, lpName ? lpName : "(null)");
@@ -515,7 +229,7 @@ HANDLE WINAPI CreateFileMappingA(HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMappi
}
HANDLE WINAPI CreateFileMappingW(HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMappingAttributes, DWORD flProtect,
DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCWSTR lpName) {
DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCWSTR lpName) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("CreateFileMappingW -> ");
std::string name = wideStringToString(lpName);
@@ -581,7 +295,7 @@ static LPVOID mapViewOfFileInternal(Pin<MappingObject> mapping, DWORD dwDesiredA
}
int flags = (mapping->anonymous ? MAP_ANONYMOUS : 0) | (wantCopy ? MAP_PRIVATE : MAP_SHARED);
const size_t pageSize = systemPageSize();
const size_t pageSize = wibo::heap::systemPageSize();
off_t alignedOffset = mapping->anonymous ? 0 : static_cast<off_t>(offset & ~static_cast<uint64_t>(pageSize - 1));
size_t offsetDelta = static_cast<size_t>(offset - static_cast<uint64_t>(alignedOffset));
uint64_t requestedLength = length + offsetDelta;
@@ -598,6 +312,7 @@ static LPVOID mapViewOfFileInternal(Pin<MappingObject> mapping, DWORD dwDesiredA
int mmapFd = mapping->anonymous ? -1 : mapping->fd;
void *requestedBase = nullptr;
int mapFlags = flags;
bool reservedMapping = false;
if (baseAddress) {
uintptr_t baseAddr = reinterpret_cast<uintptr_t>(baseAddress);
if (baseAddr == 0 || (baseAddr % kVirtualAllocationGranularity) != 0) {
@@ -619,6 +334,16 @@ static LPVOID mapViewOfFileInternal(Pin<MappingObject> mapping, DWORD dwDesiredA
#else
mapFlags |= MAP_FIXED;
#endif
} else {
void *candidate = nullptr;
wibo::heap::VmStatus reserveStatus = wibo::heap::reserveViewRange(mapLength, 0, 0, &candidate);
if (reserveStatus != wibo::heap::VmStatus::Success) {
setLastError(wibo::heap::win32ErrorFromVmStatus(reserveStatus));
return nullptr;
}
reservedMapping = true;
requestedBase = candidate;
mapFlags |= MAP_FIXED;
}
errno = 0;
@@ -630,12 +355,18 @@ static LPVOID mapViewOfFileInternal(Pin<MappingObject> mapping, DWORD dwDesiredA
} else {
setLastError(wibo::winErrorFromErrno(err));
}
if (reservedMapping) {
wibo::heap::releaseViewRange(requestedBase);
}
return nullptr;
}
void *viewPtr = static_cast<uint8_t *>(mapBase) + offsetDelta;
if (baseAddress && viewPtr != baseAddress) {
munmap(mapBase, mapLength);
setLastError(ERROR_INVALID_ADDRESS);
if (reservedMapping) {
wibo::heap::releaseViewRange(requestedBase);
}
return nullptr;
}
uintptr_t viewLength = static_cast<uintptr_t>(length);
@@ -653,6 +384,10 @@ static LPVOID mapViewOfFileInternal(Pin<MappingObject> mapping, DWORD dwDesiredA
view.protect = desiredAccessToProtect(dwDesiredAccess, protect);
view.allocationProtect = protect;
view.type = MEM_MAPPED;
view.managed = reservedMapping;
if (reservedMapping) {
wibo::heap::registerViewRange(mapBase, mapLength, protect, view.protect);
}
{
std::lock_guard guard(g_viewInfoMutex);
g_viewInfo.emplace(view.viewBase, std::move(view));
@@ -661,7 +396,7 @@ static LPVOID mapViewOfFileInternal(Pin<MappingObject> mapping, DWORD dwDesiredA
}
LPVOID WINAPI MapViewOfFile(HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap) {
DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("MapViewOfFile(%p, 0x%x, %u, %u, %zu)\n", hFileMappingObject, dwDesiredAccess, dwFileOffsetHigh,
dwFileOffsetLow, dwNumberOfBytesToMap);
@@ -676,7 +411,7 @@ LPVOID WINAPI MapViewOfFile(HANDLE hFileMappingObject, DWORD dwDesiredAccess, DW
}
LPVOID WINAPI MapViewOfFileEx(HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap, LPVOID lpBaseAddress) {
DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap, LPVOID lpBaseAddress) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("MapViewOfFileEx(%p, 0x%x, %u, %u, %zu, %p)\n", hFileMappingObject, dwDesiredAccess, dwFileOffsetHigh,
dwFileOffsetLow, dwNumberOfBytesToMap, lpBaseAddress);
@@ -701,11 +436,15 @@ BOOL WINAPI UnmapViewOfFile(LPCVOID lpBaseAddress) {
}
void *base = reinterpret_cast<void *>(it->second.allocationBase);
size_t length = it->second.allocationLength;
bool managed = it->second.managed;
g_viewInfo.erase(it);
lk.unlock();
if (length != 0) {
munmap(base, length);
}
if (managed) {
wibo::heap::releaseViewRange(base);
}
return TRUE;
}
@@ -755,7 +494,7 @@ BOOL WINAPI FlushViewOfFile(LPCVOID lpBaseAddress, SIZE_T dwNumberOfBytesToFlush
uintptr_t flushStart = address;
uintptr_t flushEnd = flushStart + bytesToFlush;
const size_t pageSize = systemPageSize();
const size_t pageSize = wibo::heap::systemPageSize();
uintptr_t alignedStart = alignDown(flushStart, pageSize);
uintptr_t alignedEnd = alignUp(flushEnd, pageSize);
if (alignedEnd == std::numeric_limits<uintptr_t>::max()) {
@@ -788,336 +527,42 @@ LPVOID WINAPI VirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationTy
HOST_CONTEXT_GUARD();
DEBUG_LOG("VirtualAlloc(%p, %zu, %u, %u)\n", lpAddress, dwSize, flAllocationType, flProtect);
if (dwSize == 0) {
setLastError(ERROR_INVALID_PARAMETER);
void *base = lpAddress;
std::size_t size = static_cast<std::size_t>(dwSize);
wibo::heap::VmStatus status = wibo::heap::virtualAlloc(&base, &size, flAllocationType, flProtect);
if (status != wibo::heap::VmStatus::Success) {
DWORD err = wibo::heap::win32ErrorFromVmStatus(status);
DEBUG_LOG("-> failed (status=%u, err=%u)\n", static_cast<unsigned>(status), err);
setLastError(err);
return nullptr;
}
DWORD unsupportedFlags = flAllocationType & (MEM_WRITE_WATCH | MEM_PHYSICAL | MEM_LARGE_PAGES | MEM_RESET_UNDO);
if (unsupportedFlags != 0) {
DEBUG_LOG("VirtualAlloc unsupported flags: 0x%x\n", unsupportedFlags);
setLastError(ERROR_NOT_SUPPORTED);
return nullptr;
}
bool reserve = (flAllocationType & MEM_RESERVE) != 0;
bool commit = (flAllocationType & MEM_COMMIT) != 0;
bool reset = (flAllocationType & MEM_RESET) != 0;
if (!reserve && commit && lpAddress == nullptr) {
reserve = true;
}
if (reset) {
if (reserve || commit) {
setLastError(ERROR_INVALID_PARAMETER);
return nullptr;
}
if (!lpAddress) {
setLastError(ERROR_INVALID_ADDRESS);
return nullptr;
}
const size_t pageSize = systemPageSize();
uintptr_t request = reinterpret_cast<uintptr_t>(lpAddress);
if (addOverflows(request, static_cast<size_t>(dwSize))) {
setLastError(ERROR_INVALID_PARAMETER);
return nullptr;
}
uintptr_t start = alignDown(request, pageSize);
uintptr_t end = alignUp(request + static_cast<uintptr_t>(dwSize), pageSize);
size_t length = static_cast<size_t>(end - start);
std::unique_lock lk(g_virtualAllocMutex);
VirtualAllocation *region = lookupRegion(start);
if (!region || !rangeWithinRegion(*region, start, length)) {
setLastError(ERROR_INVALID_ADDRESS);
return nullptr;
}
#ifdef MADV_FREE
int advice = MADV_FREE;
#else
int advice = MADV_DONTNEED;
#endif
if (madvise(reinterpret_cast<void *>(start), length, advice) != 0) {
setLastErrorFromErrno();
return nullptr;
}
return reinterpret_cast<LPVOID>(start);
}
if (!reserve && !commit) {
setLastError(ERROR_INVALID_PARAMETER);
return nullptr;
}
const size_t pageSize = systemPageSize();
std::unique_lock lk(g_virtualAllocMutex);
if (reserve) {
uintptr_t base = 0;
size_t length = 0;
if (lpAddress) {
uintptr_t request = reinterpret_cast<uintptr_t>(lpAddress);
base = alignDown(request, kVirtualAllocationGranularity);
size_t offset = static_cast<size_t>(request - base);
if (addOverflows(offset, static_cast<size_t>(dwSize))) {
setLastError(ERROR_INVALID_PARAMETER);
return nullptr;
}
size_t span = static_cast<size_t>(dwSize) + offset;
uintptr_t alignedSpan = alignUp(span, pageSize);
if (alignedSpan == std::numeric_limits<uintptr_t>::max()) {
setLastError(ERROR_INVALID_PARAMETER);
return nullptr;
}
length = static_cast<size_t>(alignedSpan);
if (length == 0 || rangeOverlapsLocked(base, length)) {
setLastError(ERROR_INVALID_ADDRESS);
return nullptr;
}
} else {
uintptr_t aligned = alignUp(static_cast<uintptr_t>(dwSize), pageSize);
if (aligned == std::numeric_limits<uintptr_t>::max() || aligned == 0) {
setLastError(ERROR_INVALID_PARAMETER);
return nullptr;
}
length = static_cast<size_t>(aligned);
}
const int prot = commit ? translateProtect(flProtect) : PROT_NONE;
int flags = MAP_PRIVATE | MAP_ANONYMOUS;
if (!commit) {
flags |= MAP_NORESERVE;
}
void *result = MAP_FAILED;
if (lpAddress) {
#ifdef MAP_FIXED_NOREPLACE
flags |= MAP_FIXED_NOREPLACE;
#else
flags |= MAP_FIXED;
#endif
result = mmap(reinterpret_cast<void *>(base), length, prot, flags, -1, 0);
} else {
result = alignedReserve(length, prot, flags);
}
if (result == MAP_FAILED) {
setLastErrorFromErrno();
return nullptr;
}
if (reinterpret_cast<uintptr_t>(result) >= 0x80000000) {
munmap(result, length);
setLastError(ERROR_NOT_ENOUGH_MEMORY);
return nullptr;
}
uintptr_t actualBase = reinterpret_cast<uintptr_t>(result);
VirtualAllocation allocation{};
allocation.base = actualBase;
allocation.size = length;
allocation.allocationProtect = flProtect;
allocation.pageProtect.assign(length / pageSize, commit ? flProtect : 0);
g_virtualAllocations[actualBase] = std::move(allocation);
return result;
}
uintptr_t request = reinterpret_cast<uintptr_t>(lpAddress);
if (addOverflows(request, static_cast<size_t>(dwSize))) {
setLastError(ERROR_INVALID_PARAMETER);
return nullptr;
}
uintptr_t start = alignDown(request, pageSize);
uintptr_t end = alignUp(request + static_cast<uintptr_t>(dwSize), pageSize);
size_t length = static_cast<size_t>(end - start);
if (length == 0) {
setLastError(ERROR_INVALID_PARAMETER);
return nullptr;
}
VirtualAllocation *region = lookupRegion(start);
if (!region || !rangeWithinRegion(*region, start, length)) {
setLastError(ERROR_INVALID_ADDRESS);
return nullptr;
}
const size_t pageCount = length / pageSize;
std::vector<std::pair<uintptr_t, size_t>> committedRuns;
committedRuns.reserve(pageCount);
for (size_t i = 0; i < pageCount; ++i) {
size_t pageIndex = ((start - region->base) / pageSize) + i;
if (pageIndex >= region->pageProtect.size()) {
setLastError(ERROR_INVALID_ADDRESS);
return nullptr;
}
if (region->pageProtect[pageIndex] != 0) {
continue;
}
uintptr_t runBase = start + i * pageSize;
size_t runLength = pageSize;
while (i + 1 < pageCount) {
size_t nextIndex = ((start - region->base) / pageSize) + i + 1;
if (region->pageProtect[nextIndex] != 0) {
break;
}
++i;
runLength += pageSize;
}
committedRuns.emplace_back(runBase, runLength);
}
for (const auto &run : committedRuns) {
void *result = mmap(reinterpret_cast<void *>(run.first), run.second, translateProtect(flProtect),
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
if (result == MAP_FAILED) {
setLastErrorFromErrno();
return nullptr;
}
markCommitted(*region, run.first, run.second, flProtect);
}
DEBUG_LOG("VirtualAlloc commit success -> %p\n", reinterpret_cast<void *>(start));
return reinterpret_cast<LPVOID>(start);
return base;
}
BOOL WINAPI VirtualFree(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("VirtualFree(%p, %zu, %u)\n", lpAddress, dwSize, dwFreeType);
if (!lpAddress) {
setLastError(ERROR_INVALID_ADDRESS);
wibo::heap::VmStatus status = wibo::heap::virtualFree(lpAddress, static_cast<std::size_t>(dwSize), dwFreeType);
if (status != wibo::heap::VmStatus::Success) {
DWORD err = wibo::heap::win32ErrorFromVmStatus(status);
DEBUG_LOG("-> failed (status=%u, err=%u)\n", static_cast<unsigned>(status), err);
setLastError(err);
return FALSE;
}
if ((dwFreeType & (MEM_COALESCE_PLACEHOLDERS | MEM_PRESERVE_PLACEHOLDER)) != 0) {
setLastError(ERROR_NOT_SUPPORTED);
return FALSE;
}
const bool release = (dwFreeType & MEM_RELEASE) != 0;
const bool decommit = (dwFreeType & MEM_DECOMMIT) != 0;
if (release == decommit) {
setLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
const size_t pageSize = systemPageSize();
std::unique_lock lk(g_virtualAllocMutex);
if (release) {
uintptr_t base = reinterpret_cast<uintptr_t>(lpAddress);
auto exact = g_virtualAllocations.find(base);
if (exact == g_virtualAllocations.end()) {
auto containing = findRegionIterator(base);
if (dwSize != 0 && containing != g_virtualAllocations.end()) {
setLastError(ERROR_INVALID_PARAMETER);
} else {
setLastError(ERROR_INVALID_ADDRESS);
}
return FALSE;
}
if (dwSize != 0) {
setLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
size_t length = exact->second.size;
g_virtualAllocations.erase(exact);
lk.unlock();
if (munmap(lpAddress, length) != 0) {
setLastErrorFromErrno();
return FALSE;
}
return TRUE;
}
uintptr_t request = reinterpret_cast<uintptr_t>(lpAddress);
auto regionIt = findRegionIterator(request);
if (regionIt == g_virtualAllocations.end()) {
setLastError(ERROR_INVALID_ADDRESS);
return FALSE;
}
VirtualAllocation &region = regionIt->second;
uintptr_t start = alignDown(request, pageSize);
uintptr_t end = 0;
if (dwSize == 0) {
if (request != region.base) {
setLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
start = region.base;
end = region.base + region.size;
} else {
if (addOverflows(request, static_cast<size_t>(dwSize))) {
setLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
end = alignUp(request + static_cast<uintptr_t>(dwSize), pageSize);
}
if (end <= start) {
setLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
size_t length = static_cast<size_t>(end - start);
if (!rangeWithinRegion(region, start, length)) {
setLastError(ERROR_INVALID_ADDRESS);
return FALSE;
}
void *result = mmap(reinterpret_cast<void *>(start), length, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | MAP_NORESERVE, -1, 0);
if (result == MAP_FAILED) {
setLastErrorFromErrno();
return FALSE;
}
markDecommitted(region, start, length);
return TRUE;
}
BOOL WINAPI VirtualProtect(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("VirtualProtect(%p, %zu, %u)\n", lpAddress, dwSize, flNewProtect);
if (!lpAddress || dwSize == 0) {
setLastError(ERROR_INVALID_PARAMETER);
wibo::heap::VmStatus status =
wibo::heap::virtualProtect(lpAddress, static_cast<std::size_t>(dwSize), flNewProtect, lpflOldProtect);
if (status != wibo::heap::VmStatus::Success) {
DWORD err = wibo::heap::win32ErrorFromVmStatus(status);
DEBUG_LOG("-> failed (status=%u, err=%u)\n", static_cast<unsigned>(status), err);
setLastError(err);
return FALSE;
}
const size_t pageSize = systemPageSize();
uintptr_t request = reinterpret_cast<uintptr_t>(lpAddress);
uintptr_t start = alignDown(request, pageSize);
uintptr_t end = alignUp(request + static_cast<uintptr_t>(dwSize), pageSize);
if (end <= start) {
setLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
std::unique_lock lk(g_virtualAllocMutex);
VirtualAllocation *region = lookupRegion(start);
if (!region || !rangeWithinRegion(*region, start, static_cast<size_t>(end - start))) {
setLastError(ERROR_INVALID_ADDRESS);
return FALSE;
}
const size_t firstPage = (start - region->base) / pageSize;
const size_t pageCount = (end - start) / pageSize;
if (pageCount == 0) {
setLastError(ERROR_INVALID_PARAMETER);
return FALSE;
}
DWORD previousProtect = region->pageProtect[firstPage];
if (previousProtect == 0) {
setLastError(ERROR_NOACCESS);
return FALSE;
}
for (size_t i = 0; i < pageCount; ++i) {
if (region->pageProtect[firstPage + i] == 0) {
setLastError(ERROR_NOACCESS);
return FALSE;
}
}
int prot = translateProtect(flNewProtect);
if (mprotect(reinterpret_cast<void *>(start), end - start, prot) != 0) {
setLastErrorFromErrno();
return FALSE;
}
for (size_t i = 0; i < pageCount; ++i) {
region->pageProtect[firstPage + i] = flNewProtect;
}
lk.unlock();
if (lpflOldProtect) {
*lpflOldProtect = previousProtect;
}
return TRUE;
}
@@ -1131,7 +576,7 @@ SIZE_T WINAPI VirtualQuery(LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer
}
std::memset(lpBuffer, 0, sizeof(MEMORY_BASIC_INFORMATION));
const size_t pageSize = systemPageSize();
const size_t pageSize = wibo::heap::systemPageSize();
uintptr_t request = lpAddress ? reinterpret_cast<uintptr_t>(lpAddress) : 0;
uintptr_t pageBase = alignDown(request, pageSize);
if (pageBase >= kProcessAddressLimit) {
@@ -1141,26 +586,25 @@ SIZE_T WINAPI VirtualQuery(LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION lpBuffer
}
MEMORY_BASIC_INFORMATION info{};
if (moduleRegionForAddress(pageBase, info)) {
*lpBuffer = info;
return sizeof(MEMORY_BASIC_INFORMATION);
}
if (mappedViewRegionForAddress(request, pageBase, info)) {
*lpBuffer = info;
return sizeof(MEMORY_BASIC_INFORMATION);
}
if (virtualAllocationRegionForAddress(pageBase, info)) {
wibo::heap::VmStatus status = wibo::heap::virtualQuery(lpAddress, &info);
if (status == wibo::heap::VmStatus::Success) {
*lpBuffer = info;
return sizeof(MEMORY_BASIC_INFORMATION);
}
setLastError(ERROR_INVALID_ADDRESS);
DEBUG_LOG("-> ERROR_INVALID_ADDRESS\n");
DEBUG_LOG("VirtualQuery fallback failed status=%u\n", static_cast<unsigned>(status));
setLastError(wibo::heap::win32ErrorFromVmStatus(status));
DEBUG_LOG("-> VirtualQuery failed (status=%u)\n", static_cast<unsigned>(status));
return 0;
}
BOOL WINAPI GetProcessWorkingSetSize(HANDLE hProcess, PSIZE_T lpMinimumWorkingSetSize,
PSIZE_T lpMaximumWorkingSetSize) {
PSIZE_T lpMaximumWorkingSetSize) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("GetProcessWorkingSetSize(%p, %p, %p)\n", hProcess, lpMinimumWorkingSetSize, lpMaximumWorkingSetSize);
(void)hProcess;
@@ -1173,8 +617,7 @@ BOOL WINAPI GetProcessWorkingSetSize(HANDLE hProcess, PSIZE_T lpMinimumWorkingSe
return TRUE;
}
BOOL WINAPI SetProcessWorkingSetSize(HANDLE hProcess, SIZE_T dwMinimumWorkingSetSize,
SIZE_T dwMaximumWorkingSetSize) {
BOOL WINAPI SetProcessWorkingSetSize(HANDLE hProcess, SIZE_T dwMinimumWorkingSetSize, SIZE_T dwMaximumWorkingSetSize) {
HOST_CONTEXT_GUARD();
DEBUG_LOG("SetProcessWorkingSetSize(%p, %zu, %zu)\n", hProcess, dwMinimumWorkingSetSize, dwMaximumWorkingSetSize);
(void)hProcess;