diff --git a/.gitignore b/.gitignore index 5376a77..4678653 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ venv/ *.pyc *.pyo *.pyd +.ruff_cache/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 79755fb..2f8a446 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,6 +174,7 @@ add_executable(wibo src/files.cpp src/handles.cpp src/loader.cpp + src/heap.cpp src/main.cpp src/modules.cpp src/processes.cpp @@ -184,9 +185,10 @@ add_executable(wibo target_compile_definitions(wibo PRIVATE _GNU_SOURCE _FILE_OFFSET_BITS=64 _TIME_BITS=64) target_compile_features(wibo PRIVATE cxx_std_20) target_compile_options(wibo PRIVATE -Wall -Wextra) +target_link_options(wibo PRIVATE -no-pie -Wl,--image-base=0x90000000) if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") target_compile_options(wibo PRIVATE -fno-pie -maccumulate-outgoing-args) - target_link_options(wibo PRIVATE -no-pie -maccumulate-outgoing-args) + target_link_options(wibo PRIVATE -maccumulate-outgoing-args) endif() target_include_directories(wibo PRIVATE dll src ${WIBO_GENERATED_HEADER_DIR}) target_link_libraries(wibo PRIVATE mimalloc-obj atomic) diff --git a/dll/crt.cpp b/dll/crt.cpp index 2f30444..9715876 100644 --- a/dll/crt.cpp +++ b/dll/crt.cpp @@ -3,6 +3,7 @@ #include "common.h" #include "context.h" #include "crt_trampolines.h" +#include "heap.h" #include "kernel32/internal.h" #include "modules.h" @@ -180,25 +181,25 @@ const char *CDECL strrchr(const char *str, int ch) { void *CDECL malloc(SIZE_T size) { HOST_CONTEXT_GUARD(); VERBOSE_LOG("malloc(%zu)\n", size); - return ::malloc(size); + return wibo::heap::guestMalloc(size); } void *CDECL calloc(SIZE_T count, SIZE_T size) { HOST_CONTEXT_GUARD(); VERBOSE_LOG("calloc(%zu, %zu)\n", count, size); - return ::calloc(count, size); + return wibo::heap::guestCalloc(count, size); } void *CDECL realloc(void *ptr, SIZE_T newSize) { HOST_CONTEXT_GUARD(); VERBOSE_LOG("realloc(%p, %zu)\n", ptr, newSize); - return ::realloc(ptr, newSize); + return wibo::heap::guestRealloc(ptr, newSize); } void CDECL free(void *ptr) { HOST_CONTEXT_GUARD(); VERBOSE_LOG("free(%p)\n", ptr); - ::free(ptr); + wibo::heap::guestFree(ptr); } void *CDECL memcpy(void *dest, const void *src, SIZE_T count) { diff --git a/dll/kernel32/heapapi.cpp b/dll/kernel32/heapapi.cpp index 18e3c76..ac36aba 100644 --- a/dll/kernel32/heapapi.cpp +++ b/dll/kernel32/heapapi.cpp @@ -1,4 +1,5 @@ #include "heapapi.h" +#include "heap.h" #include "common.h" #include "context.h" @@ -22,8 +23,7 @@ HeapObject *g_processHeapRecord = nullptr; void ensureProcessHeapInitialized() { std::call_once(g_processHeapInitFlag, []() { - mi_heap_t *heap = mi_heap_get_default(); - auto record = make_pin(heap); + auto record = make_pin(nullptr); if (!record) { return; } @@ -38,9 +38,13 @@ bool isExecutableHeap(const HeapObject *record) { } LPVOID heapAllocFromRecord(HeapObject *record, DWORD dwFlags, SIZE_T dwBytes) { - if (!record || !record->heap) { + if (!record) { return nullptr; } + auto *heap = record->heap; + if (!heap && record->isProcessHeap) { + heap = wibo::heap::getGuestHeap(); + } if ((record->createFlags | dwFlags) & HEAP_GENERATE_EXCEPTIONS) { DEBUG_LOG("HeapAlloc: HEAP_GENERATE_EXCEPTIONS not supported\n"); kernel32::setLastError(ERROR_INVALID_PARAMETER); @@ -48,7 +52,7 @@ LPVOID heapAllocFromRecord(HeapObject *record, DWORD dwFlags, SIZE_T dwBytes) { } const bool zeroMemory = (dwFlags & HEAP_ZERO_MEMORY) != 0; const SIZE_T requestSize = std::max(1, dwBytes); - void *mem = zeroMemory ? mi_heap_zalloc(record->heap, requestSize) : mi_heap_malloc(record->heap, requestSize); + void *mem = zeroMemory ? mi_heap_zalloc(heap, requestSize) : mi_heap_malloc(heap, requestSize); if (!mem) { kernel32::setLastError(ERROR_NOT_ENOUGH_MEMORY); return nullptr; @@ -63,9 +67,7 @@ LPVOID heapAllocFromRecord(HeapObject *record, DWORD dwFlags, SIZE_T dwBytes) { HeapObject::~HeapObject() { if (heap) { - if (!isProcessHeap) { - mi_heap_destroy(heap); - } + mi_heap_destroy(heap); heap = nullptr; } if (isProcessHeap) { @@ -84,7 +86,7 @@ HANDLE WINAPI HeapCreate(DWORD flOptions, SIZE_T dwInitialSize, SIZE_T dwMaximum return nullptr; } - mi_heap_t *heap = mi_heap_new(); + mi_heap_t *heap = wibo::heap::createGuestHeap(); if (!heap) { setLastError(ERROR_NOT_ENOUGH_MEMORY); return nullptr; @@ -211,7 +213,11 @@ LPVOID WINAPI HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBy return lpMem; } - void *ret = mi_heap_realloc(record->heap, lpMem, requestSize); + auto *heap = record->heap; + if (!heap && record->isProcessHeap) { + heap = wibo::heap::getGuestHeap(); + } + void *ret = mi_heap_realloc(heap, lpMem, requestSize); if (!ret) { setLastError(ERROR_NOT_ENOUGH_MEMORY); return nullptr; diff --git a/dll/kernel32/internal.h b/dll/kernel32/internal.h index 5940f71..66fd410 100644 --- a/dll/kernel32/internal.h +++ b/dll/kernel32/internal.h @@ -157,7 +157,7 @@ struct HeapObject : public ObjectBase { ~HeapObject() override; [[nodiscard]] inline bool isOwner() const { return pthread_equal(owner, pthread_self()); } - [[nodiscard]] inline bool canAccess() const { return (isProcessHeap || isOwner()) && heap != nullptr; } + [[nodiscard]] inline bool canAccess() const { return isProcessHeap || (isOwner() && heap != nullptr); } }; inline constexpr uintptr_t kPseudoCurrentProcessHandleValue = static_cast(-1); diff --git a/dll/kernel32/memoryapi.cpp b/dll/kernel32/memoryapi.cpp index 9ed77d6..e048666 100644 --- a/dll/kernel32/memoryapi.cpp +++ b/dll/kernel32/memoryapi.cpp @@ -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 @@ -18,7 +18,6 @@ #include #include #include -#include namespace { @@ -55,32 +54,12 @@ struct ViewInfo { DWORD protect = PAGE_NOACCESS; DWORD allocationProtect = PAGE_NOACCESS; DWORD type = MEM_PRIVATE; + bool managed = false; }; std::map g_viewInfo; std::mutex g_viewInfoMutex; -struct VirtualAllocation { - uintptr_t base = 0; - size_t size = 0; - DWORD allocationProtect = 0; - std::vector pageProtect; -}; - -std::map g_virtualAllocations; -std::mutex g_virtualAllocMutex; - -size_t systemPageSize() { - static size_t cached = []() { - long detected = sysconf(_SC_PAGESIZE); - if (detected <= 0) { - return static_cast(4096); - } - return static_cast(detected); - }(); - return cached; -} - uintptr_t alignDown(uintptr_t value, size_t alignment) { const uintptr_t mask = static_cast(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::max() - static_cast(amount); -} - -uintptr_t regionEnd(const VirtualAllocation ®ion) { 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::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 ®ion, 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 ®ion, 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 ®ion, 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(pageBase)); - if (!module || !module->executable) { - return false; - } - const auto §ions = module->executable->sections; - if (sections.empty()) { - return false; - } - size_t matchIndex = sections.size(); - for (size_t i = 0; i < sections.size(); ++i) { - const auto §ion = 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 §ion = 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 §ion = sections[next]; - if (section.base != blockEnd) { - break; - } - if (section.protect != blockProtect) { - break; - } - blockEnd = section.base + section.size; - } - info.BaseAddress = reinterpret_cast(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(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(blockStart); - info.AllocationBase = reinterpret_cast(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(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(aligned + length), back) != 0) { - munmap(reinterpret_cast(aligned), length); - return MAP_FAILED; - } - } - return reinterpret_cast(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 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(offset & ~static_cast(pageSize - 1)); size_t offsetDelta = static_cast(offset - static_cast(alignedOffset)); uint64_t requestedLength = length + offsetDelta; @@ -598,6 +312,7 @@ static LPVOID mapViewOfFileInternal(Pin 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(baseAddress); if (baseAddr == 0 || (baseAddr % kVirtualAllocationGranularity) != 0) { @@ -619,6 +334,16 @@ static LPVOID mapViewOfFileInternal(Pin 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 mapping, DWORD dwDesiredA } else { setLastError(wibo::winErrorFromErrno(err)); } + if (reservedMapping) { + wibo::heap::releaseViewRange(requestedBase); + } return nullptr; } void *viewPtr = static_cast(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(length); @@ -653,6 +384,10 @@ static LPVOID mapViewOfFileInternal(Pin 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 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(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::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(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(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(lpAddress); - if (addOverflows(request, static_cast(dwSize))) { - setLastError(ERROR_INVALID_PARAMETER); - return nullptr; - } - uintptr_t start = alignDown(request, pageSize); - uintptr_t end = alignUp(request + static_cast(dwSize), pageSize); - size_t length = static_cast(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(start), length, advice) != 0) { - setLastErrorFromErrno(); - return nullptr; - } - return reinterpret_cast(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(lpAddress); - base = alignDown(request, kVirtualAllocationGranularity); - size_t offset = static_cast(request - base); - if (addOverflows(offset, static_cast(dwSize))) { - setLastError(ERROR_INVALID_PARAMETER); - return nullptr; - } - size_t span = static_cast(dwSize) + offset; - uintptr_t alignedSpan = alignUp(span, pageSize); - if (alignedSpan == std::numeric_limits::max()) { - setLastError(ERROR_INVALID_PARAMETER); - return nullptr; - } - length = static_cast(alignedSpan); - if (length == 0 || rangeOverlapsLocked(base, length)) { - setLastError(ERROR_INVALID_ADDRESS); - return nullptr; - } - } else { - uintptr_t aligned = alignUp(static_cast(dwSize), pageSize); - if (aligned == std::numeric_limits::max() || aligned == 0) { - setLastError(ERROR_INVALID_PARAMETER); - return nullptr; - } - length = static_cast(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(base), length, prot, flags, -1, 0); - } else { - result = alignedReserve(length, prot, flags); - } - if (result == MAP_FAILED) { - setLastErrorFromErrno(); - return nullptr; - } - if (reinterpret_cast(result) >= 0x80000000) { - munmap(result, length); - setLastError(ERROR_NOT_ENOUGH_MEMORY); - return nullptr; - } - uintptr_t actualBase = reinterpret_cast(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(lpAddress); - if (addOverflows(request, static_cast(dwSize))) { - setLastError(ERROR_INVALID_PARAMETER); - return nullptr; - } - uintptr_t start = alignDown(request, pageSize); - uintptr_t end = alignUp(request + static_cast(dwSize), pageSize); - size_t length = static_cast(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> 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(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(start)); - return reinterpret_cast(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(dwSize), dwFreeType); + if (status != wibo::heap::VmStatus::Success) { + DWORD err = wibo::heap::win32ErrorFromVmStatus(status); + DEBUG_LOG("-> failed (status=%u, err=%u)\n", static_cast(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(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(lpAddress); - auto regionIt = findRegionIterator(request); - if (regionIt == g_virtualAllocations.end()) { - setLastError(ERROR_INVALID_ADDRESS); - return FALSE; - } - VirtualAllocation ®ion = 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(dwSize))) { - setLastError(ERROR_INVALID_PARAMETER); - return FALSE; - } - end = alignUp(request + static_cast(dwSize), pageSize); - } - if (end <= start) { - setLastError(ERROR_INVALID_PARAMETER); - return FALSE; - } - size_t length = static_cast(end - start); - if (!rangeWithinRegion(region, start, length)) { - setLastError(ERROR_INVALID_ADDRESS); - return FALSE; - } - void *result = mmap(reinterpret_cast(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(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(status), err); + setLastError(err); return FALSE; } - - const size_t pageSize = systemPageSize(); - uintptr_t request = reinterpret_cast(lpAddress); - uintptr_t start = alignDown(request, pageSize); - uintptr_t end = alignUp(request + static_cast(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(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(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(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(status)); + setLastError(wibo::heap::win32ErrorFromVmStatus(status)); + DEBUG_LOG("-> VirtualQuery failed (status=%u)\n", static_cast(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; diff --git a/dll/kernel32/memoryapi.h b/dll/kernel32/memoryapi.h index c9a99c0..6faf134 100644 --- a/dll/kernel32/memoryapi.h +++ b/dll/kernel32/memoryapi.h @@ -3,18 +3,6 @@ #include "types.h" #include "minwinbase.h" -struct MEMORY_BASIC_INFORMATION { - PVOID BaseAddress; - PVOID AllocationBase; - DWORD AllocationProtect; - SIZE_T RegionSize; - DWORD State; - DWORD Protect; - DWORD Type; -}; - -using PMEMORY_BASIC_INFORMATION = MEMORY_BASIC_INFORMATION *; - namespace kernel32 { HANDLE WINAPI CreateFileMappingA(HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMappingAttributes, DWORD flProtect, diff --git a/dll/kernel32/processenv.cpp b/dll/kernel32/processenv.cpp index 557ae00..4c74fe8 100644 --- a/dll/kernel32/processenv.cpp +++ b/dll/kernel32/processenv.cpp @@ -3,6 +3,7 @@ #include "context.h" #include "errors.h" #include "files.h" +#include "heap.h" #include "internal.h" #include "strutil.h" @@ -78,7 +79,7 @@ LPCH WINAPI GetEnvironmentStrings() { } bufSize++; - char *buffer = static_cast(mi_malloc(bufSize)); + char *buffer = static_cast(wibo::heap::guestMalloc(bufSize)); if (!buffer) { setLastError(ERROR_NOT_ENOUGH_MEMORY); return nullptr; @@ -111,7 +112,7 @@ LPWCH WINAPI GetEnvironmentStringsW() { } bufSizeW++; - uint16_t *buffer = static_cast(mi_malloc(bufSizeW * sizeof(uint16_t))); + uint16_t *buffer = static_cast(wibo::heap::guestMalloc(bufSizeW * sizeof(uint16_t))); if (!buffer) { setLastError(ERROR_NOT_ENOUGH_MEMORY); return nullptr; diff --git a/dll/kernel32/processthreadsapi.cpp b/dll/kernel32/processthreadsapi.cpp index 1cef501..b8a5a40 100644 --- a/dll/kernel32/processthreadsapi.cpp +++ b/dll/kernel32/processthreadsapi.cpp @@ -148,7 +148,6 @@ void *threadTrampoline(void *param) { DEBUG_LOG("Calling thread entry %p with userData %p\n", data.entry, data.userData); DWORD result = 0; if (data.entry) { - GUEST_CONTEXT_GUARD(threadTib); result = call_LPTHREAD_START_ROUTINE(data.entry, data.userData); } DEBUG_LOG("Thread exiting with code %u\n", result); diff --git a/dll/kernel32/winbase.cpp b/dll/kernel32/winbase.cpp index de1d04e..9130aff 100644 --- a/dll/kernel32/winbase.cpp +++ b/dll/kernel32/winbase.cpp @@ -4,6 +4,7 @@ #include "context.h" #include "errors.h" #include "files.h" +#include "heap.h" #include "internal.h" #include "modules.h" #include "strutil.h" @@ -169,7 +170,7 @@ void *doAlloc(UINT dwBytes, bool zero) { if (dwBytes == 0) { dwBytes = 1; } - void *ret = mi_malloc_aligned(dwBytes, 8); + void *ret = mi_heap_malloc_aligned(wibo::heap::getGuestHeap(), dwBytes, 8); if (ret && zero) { std::memset(ret, 0, mi_usable_size(ret)); } @@ -181,7 +182,7 @@ void *doRealloc(void *mem, UINT dwBytes, bool zero) { dwBytes = 1; } size_t oldSize = mi_usable_size(mem); - void *ret = mi_realloc_aligned(mem, dwBytes, 8); + void *ret = mi_heap_realloc_aligned(wibo::heap::getGuestHeap(), mem, dwBytes, 8); size_t newSize = mi_usable_size(ret); if (ret && zero && newSize > oldSize) { std::memset(static_cast(ret) + oldSize, 0, newSize - oldSize); diff --git a/dll/msvcrt.cpp b/dll/msvcrt.cpp index 9b584f8..040a989 100644 --- a/dll/msvcrt.cpp +++ b/dll/msvcrt.cpp @@ -3,6 +3,7 @@ #include "common.h" #include "context.h" #include "files.h" +#include "heap.h" #include "kernel32/internal.h" #include "modules.h" #include "msvcrt_trampolines.h" @@ -1382,7 +1383,7 @@ namespace msvcrt { std::strcpy(absPath, winPath.c_str()); return absPath; } - char *result = static_cast(std::malloc(winPath.size() + 1)); + char *result = static_cast(wibo::heap::guestMalloc(winPath.size() + 1)); if (!result) { errno = ENOMEM; return nullptr; @@ -1512,7 +1513,7 @@ namespace msvcrt { } SIZE_T value_len = match->length; - auto *copy = static_cast(malloc((value_len + 1) * sizeof(uint16_t))); + auto *copy = static_cast(wibo::heap::guestMalloc((value_len + 1) * sizeof(uint16_t))); if (!copy) { DEBUG_LOG("_wdupenv_s: allocation failed\n"); errno = ENOMEM; @@ -1686,7 +1687,7 @@ namespace msvcrt { } SIZE_T length = ::strlen(strSource); - auto *copy = static_cast(std::malloc(length + 1)); + auto *copy = static_cast(wibo::heap::guestMalloc(length + 1)); if (!copy) { return nullptr; } @@ -1704,25 +1705,25 @@ namespace msvcrt { void* CDECL malloc(SIZE_T size){ HOST_CONTEXT_GUARD(); VERBOSE_LOG("malloc(%zu)\n", size); - return std::malloc(size); + return wibo::heap::guestMalloc(size); } void* CDECL calloc(SIZE_T count, SIZE_T size){ HOST_CONTEXT_GUARD(); VERBOSE_LOG("calloc(%zu, %zu)\n", count, size); - return std::calloc(count, size); + return wibo::heap::guestCalloc(count, size); } void* CDECL realloc(void *ptr, SIZE_T size) { HOST_CONTEXT_GUARD(); VERBOSE_LOG("realloc(%p, %zu)\n", ptr, size); - return std::realloc(ptr, size); + return wibo::heap::guestRealloc(ptr, size); } void* CDECL _malloc_crt(SIZE_T size) { HOST_CONTEXT_GUARD(); VERBOSE_LOG("_malloc_crt(%zu)\n", size); - return std::malloc(size); + return wibo::heap::guestMalloc(size); } void CDECL _lock(int locknum) { @@ -2479,7 +2480,7 @@ namespace msvcrt { if(!strSource) return nullptr; SIZE_T strLen = wstrlen(strSource); - auto *dup = static_cast(malloc((strLen + 1) * sizeof(uint16_t))); + auto *dup = static_cast(wibo::heap::guestMalloc((strLen + 1) * sizeof(uint16_t))); if(!dup) return nullptr; for(SIZE_T i = 0; i <= strLen; i++){ @@ -3000,7 +3001,7 @@ namespace msvcrt { return absPath; } else { // Windows behavior: if absPath == NULL, allocate new - auto *newBuf = new uint16_t[wResolved.size() + 1]; + auto *newBuf = static_cast(wibo::heap::guestMalloc((wResolved.size() + 1) * sizeof(uint16_t))); std::copy(wResolved.begin(), wResolved.end(), newBuf); newBuf[wResolved.size()] = 0; diff --git a/dll/ntdll.cpp b/dll/ntdll.cpp index 233e7af..775061e 100644 --- a/dll/ntdll.cpp +++ b/dll/ntdll.cpp @@ -5,6 +5,7 @@ #include "errors.h" #include "files.h" #include "handles.h" +#include "heap.h" #include "kernel32/internal.h" #include "kernel32/processthreadsapi.h" #include "modules.h" @@ -14,7 +15,6 @@ #include #include -#include #include #include @@ -267,35 +267,23 @@ NTSTATUS WINAPI NtAllocateVirtualMemory(HANDLE ProcessHandle, PVOID *BaseAddress HOST_CONTEXT_GUARD(); DEBUG_LOG("NtAllocateVirtualMemory(%p, %p, %lu, %p, %lu, %lu) ", ProcessHandle, BaseAddress, ZeroBits, RegionSize, AllocationType, Protect); - assert(ProcessHandle == (HANDLE)-1); - assert(ZeroBits == 0); - - int prot = 0; - if (Protect & PAGE_NOACCESS) - prot |= PROT_NONE; - if (Protect & PAGE_READONLY) - prot |= PROT_READ; - if (Protect & PAGE_READWRITE) - prot |= PROT_READ | PROT_WRITE; - if (Protect & PAGE_WRITECOPY) - prot |= PROT_READ | PROT_WRITE; - if (Protect & PAGE_EXECUTE) - prot |= PROT_EXEC; - if (Protect & PAGE_EXECUTE_READ) - prot |= PROT_EXEC | PROT_READ; - if (Protect & PAGE_EXECUTE_READWRITE) - prot |= PROT_EXEC | PROT_READ | PROT_WRITE; - assert(!(Protect & PAGE_EXECUTE_WRITECOPY)); - assert(!(Protect & PAGE_GUARD)); - assert(!(Protect & PAGE_NOCACHE)); - assert(!(Protect & PAGE_WRITECOMBINE)); - - void *addr = mmap(*BaseAddress, *RegionSize, prot, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (addr == MAP_FAILED) { - perror("mmap"); - return STATUS_NOT_SUPPORTED; + if (ProcessHandle != (HANDLE)-1) { + DEBUG_LOG("-> 0x%x\n", STATUS_INVALID_HANDLE); + return STATUS_INVALID_HANDLE; + } + if (ZeroBits != 0 || BaseAddress == nullptr || RegionSize == nullptr) { + DEBUG_LOG("-> 0x%x\n", STATUS_INVALID_PARAMETER); + return STATUS_INVALID_PARAMETER; + } + + wibo::heap::VmStatus vmStatus = + wibo::heap::virtualAlloc(reinterpret_cast(BaseAddress), reinterpret_cast(RegionSize), + static_cast(AllocationType), static_cast(Protect)); + if (vmStatus != wibo::heap::VmStatus::Success) { + NTSTATUS status = wibo::heap::ntStatusFromVmStatus(vmStatus); + DEBUG_LOG("-> 0x%x\n", status); + return status; } - *BaseAddress = addr; DEBUG_LOG("-> 0x%x\n", STATUS_SUCCESS); return STATUS_SUCCESS; @@ -306,38 +294,25 @@ NTSTATUS WINAPI NtProtectVirtualMemory(HANDLE ProcessHandle, PVOID *BaseAddress, HOST_CONTEXT_GUARD(); DEBUG_LOG("NtProtectVirtualMemory(%p, %p, %p, %lu, %p) ", ProcessHandle, BaseAddress, NumberOfBytesToProtect, NewAccessProtection, OldAccessProtection); - assert(ProcessHandle == (HANDLE)-1); - assert(NumberOfBytesToProtect != nullptr); - - int prot = 0; - if (NewAccessProtection & PAGE_NOACCESS) - prot |= PROT_NONE; - if (NewAccessProtection & PAGE_READONLY) - prot |= PROT_READ; - if (NewAccessProtection & PAGE_READWRITE) - prot |= PROT_READ | PROT_WRITE; - if (NewAccessProtection & PAGE_WRITECOPY) - prot |= PROT_READ | PROT_WRITE; - if (NewAccessProtection & PAGE_EXECUTE) - prot |= PROT_EXEC; - if (NewAccessProtection & PAGE_EXECUTE_READ) - prot |= PROT_EXEC | PROT_READ; - if (NewAccessProtection & PAGE_EXECUTE_READWRITE) - prot |= PROT_EXEC | PROT_READ | PROT_WRITE; - assert(!(NewAccessProtection & PAGE_EXECUTE_WRITECOPY)); - assert(!(NewAccessProtection & PAGE_GUARD)); - assert(!(NewAccessProtection & PAGE_NOCACHE)); - assert(!(NewAccessProtection & PAGE_WRITECOMBINE)); - - int ret = mprotect(*BaseAddress, *NumberOfBytesToProtect, prot); - if (ret != 0) { - perror("mprotect"); - return STATUS_NOT_SUPPORTED; + if (ProcessHandle != (HANDLE)-1) { + DEBUG_LOG("-> 0x%x\n", STATUS_INVALID_HANDLE); + return STATUS_INVALID_HANDLE; + } + if (BaseAddress == nullptr || NumberOfBytesToProtect == nullptr) { + DEBUG_LOG("-> 0x%x\n", STATUS_INVALID_PARAMETER); + return STATUS_INVALID_PARAMETER; } - if (OldAccessProtection) { - *OldAccessProtection = 0; // stub + void *base = *BaseAddress; + std::size_t length = static_cast(*NumberOfBytesToProtect); + wibo::heap::VmStatus vmStatus = + wibo::heap::virtualProtect(base, length, static_cast(NewAccessProtection), OldAccessProtection); + if (vmStatus != wibo::heap::VmStatus::Success) { + NTSTATUS status = wibo::heap::ntStatusFromVmStatus(vmStatus); + DEBUG_LOG("-> 0x%x\n", status); + return status; } + DEBUG_LOG("-> 0x%x\n", STATUS_SUCCESS); return STATUS_SUCCESS; } diff --git a/dll/rpcrt4.cpp b/dll/rpcrt4.cpp index 648a98e..0ea1ee6 100644 --- a/dll/rpcrt4.cpp +++ b/dll/rpcrt4.cpp @@ -2,6 +2,7 @@ #include "common.h" #include "context.h" +#include "heap.h" #include "modules.h" #include @@ -125,7 +126,7 @@ RPC_STATUS WINAPI RpcStringBindingComposeW(RPC_WSTR objUuid, RPC_WSTR protSeq, R if (stringBinding) { size_t length = encoded.size(); - auto *buffer = static_cast(std::malloc((length + 1) * sizeof(char16_t))); + auto *buffer = static_cast(wibo::heap::guestMalloc((length + 1) * sizeof(char16_t))); if (!buffer) { return RPC_S_OUT_OF_MEMORY; } diff --git a/src/context.h b/src/context.h index 246d7cd..feaecba 100644 --- a/src/context.h +++ b/src/context.h @@ -1,4 +1,3 @@ #pragma once #define HOST_CONTEXT_GUARD() -#define GUEST_CONTEXT_GUARD(tibPtr) diff --git a/src/entry.h b/src/entry.h index 191b0d2..fc3e08b 100644 --- a/src/entry.h +++ b/src/entry.h @@ -5,3 +5,9 @@ typedef VOID(_CC_CDECL *EntryProc)(); typedef BOOL(_CC_STDCALL *DllEntryProc)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved); typedef VOID(_CC_STDCALL *PIMAGE_TLS_CALLBACK)(PVOID DllHandle, DWORD Reason, PVOID Reserved); + +namespace entry { + +void CDECL stubBase(SIZE_T index); + +} // namespace entry diff --git a/src/heap.cpp b/src/heap.cpp new file mode 100644 index 0000000..23e7cd5 --- /dev/null +++ b/src/heap.cpp @@ -0,0 +1,1229 @@ +#include "heap.h" +#include "common.h" +#include "errors.h" +#include "types.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +// Pre-initialization logging macros +#define LOG_OUT(msg) write(STDOUT_FILENO, msg, strlen(msg)) +#define LOG_ERR(msg) write(STDERR_FILENO, msg, strlen(msg)) + +namespace { + +constexpr uintptr_t kLowMemoryStart = 0x00110000UL; // 1 MiB + 64 KiB +constexpr uintptr_t kTopDownStart = 0x7F000000UL; // Just below 2GB +constexpr uintptr_t kTwoGB = 0x80000000UL; +constexpr std::size_t kGuestArenaSize = 512ULL * 1024ULL * 1024ULL; // 512 MiB +constexpr std::size_t kVirtualAllocationGranularity = 64ULL * 1024ULL; + +struct ArenaRange { + void *start = nullptr; + std::size_t size = 0; +}; + +// Guest arena (<2GB) +ArenaRange g_guest; +mi_arena_id_t g_guestArenaId = nullptr; +thread_local mi_heap_t *g_guestHeap = nullptr; + +bool g_initialized = false; +std::once_flag g_initOnce; + +std::mutex g_mappingsMutex; +std::map *g_mappings = nullptr; + +std::mutex g_virtualAllocMutex; + +struct VirtualAllocation { + uintptr_t base = 0; + std::size_t size = 0; + DWORD allocationProtect = 0; + DWORD type = MEM_PRIVATE; + std::vector pageProtect; +}; + +std::map g_virtualAllocations; + +const uintptr_t kDefaultMmapMinAddr = 0x10000u; + +uintptr_t readMmapMinAddr() { + char buf[64]; + int fd = open("/proc/sys/vm/mmap_min_addr", O_RDONLY | O_CLOEXEC, 0); + if (fd < 0) { + return kDefaultMmapMinAddr; + } + ssize_t rd = read(fd, buf, sizeof(buf) - 1); + close(fd); + if (rd <= 0) { + return kDefaultMmapMinAddr; + } + uintptr_t value = 0; + auto result = std::from_chars(buf, buf + rd, value); + if (result.ec != std::errc()) { + LOG_ERR("heap: failed to parse mmap_min_addr\n"); + return kDefaultMmapMinAddr; + } + if (value < kDefaultMmapMinAddr) { + value = kDefaultMmapMinAddr; + } + return value; +} + +uintptr_t mmapMinAddr() { + static uintptr_t minAddr = readMmapMinAddr(); + return minAddr; +} + +uintptr_t alignDown(uintptr_t value, std::size_t alignment) { + const uintptr_t mask = static_cast(alignment) - 1; + return value & ~mask; +} + +uintptr_t alignUp(uintptr_t value, std::size_t alignment) { + const uintptr_t mask = static_cast(alignment) - 1; + if (mask == std::numeric_limits::max()) { + return value; + } + if (value > std::numeric_limits::max() - mask) { + return std::numeric_limits::max(); + } + return (value + mask) & ~mask; +} + +bool addOverflows(uintptr_t base, std::size_t amount) { + return base > std::numeric_limits::max() - static_cast(amount); +} + +uintptr_t regionEnd(const VirtualAllocation ®ion) { return region.base + region.size; } + +std::map::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 ®ion, uintptr_t start, std::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); +} + +void markCommitted(VirtualAllocation ®ion, uintptr_t start, std::size_t length, DWORD protect) { + if (length == 0) { + return; + } + const std::size_t pageSize = wibo::heap::systemPageSize(); + const std::size_t firstPage = (start - region.base) / pageSize; + const std::size_t pageCount = length / pageSize; + for (std::size_t i = 0; i < pageCount; ++i) { + region.pageProtect[firstPage + i] = protect; + } +} + +void markDecommitted(VirtualAllocation ®ion, uintptr_t start, std::size_t length) { + if (length == 0) { + return; + } + const std::size_t pageSize = wibo::heap::systemPageSize(); + const std::size_t firstPage = (start - region.base) / pageSize; + const std::size_t pageCount = length / pageSize; + for (std::size_t i = 0; i < pageCount; ++i) { + region.pageProtect[firstPage + i] = 0; + } +} + +bool overlapsExistingMapping(uintptr_t base, std::size_t length) { + if (g_mappings == nullptr || length == 0) { + return false; + } + if (addOverflows(base, length - 1)) { + return true; + } + uintptr_t end = base + length; + std::lock_guard guard(g_mappingsMutex); + auto it = g_mappings->upper_bound(base); + if (it != g_mappings->begin()) { + --it; + } + for (; it != g_mappings->end(); ++it) { + const auto &info = it->second; + if (info.RegionSize == 0) { + continue; + } + uintptr_t mapStart = reinterpret_cast(info.BaseAddress); + uintptr_t mapEnd = mapStart + static_cast(info.RegionSize); + if (mapEnd <= base) { + continue; + } + if (mapStart >= end) { + break; + } + return true; + } + return false; +} + +void recordGuestMapping(uintptr_t base, std::size_t size, DWORD allocationProtect, DWORD state, DWORD protect, + DWORD type) { + if (g_mappings == nullptr) { + return; + } + MEMORY_BASIC_INFORMATION info{}; + info.BaseAddress = reinterpret_cast(base); + info.AllocationBase = reinterpret_cast(base); + info.AllocationProtect = allocationProtect; + info.RegionSize = size; + info.State = state; + info.Protect = protect; + info.Type = type; + std::lock_guard guard(g_mappingsMutex); + (*g_mappings)[base] = info; +} + +void eraseGuestMapping(uintptr_t base) { + if (g_mappings == nullptr) { + return; + } + std::lock_guard guard(g_mappingsMutex); + g_mappings->erase(base); +} + +int posixProtectFromWin32(DWORD flProtect) { + switch (flProtect & 0xFF) { + 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("heap: unhandled flProtect %u, defaulting to RW\n", flProtect); + return PROT_READ | PROT_WRITE; + } +} + +wibo::heap::VmStatus vmStatusFromErrno(int err) { + switch (err) { + case ENOMEM: + return wibo::heap::VmStatus::NoMemory; + case EACCES: + case EPERM: + return wibo::heap::VmStatus::NoAccess; + case EINVAL: + return wibo::heap::VmStatus::InvalidParameter; + case EBUSY: + return wibo::heap::VmStatus::Rejected; + default: + return wibo::heap::VmStatus::UnknownError; + } +} + +void refreshGuestMapping(const VirtualAllocation ®ion) { + if (g_mappings == nullptr) { + return; + } + bool allCommitted = true; + bool anyCommitted = false; + DWORD firstProtect = 0; + bool uniformProtect = true; + for (DWORD pageProtect : region.pageProtect) { + if (pageProtect == 0) { + allCommitted = false; + continue; + } + anyCommitted = true; + if (firstProtect == 0) { + firstProtect = pageProtect; + } else if (firstProtect != pageProtect) { + uniformProtect = false; + } + } + DWORD state = allCommitted && anyCommitted ? MEM_COMMIT : MEM_RESERVE; + DWORD protect = PAGE_NOACCESS; + if (state == MEM_COMMIT) { + if (uniformProtect && firstProtect != 0) { + protect = firstProtect; + } else { + protect = PAGE_NOACCESS; + } + } + DWORD allocationProtect = region.allocationProtect != 0 ? region.allocationProtect : PAGE_NOACCESS; + recordGuestMapping(region.base, region.size, allocationProtect, state, protect, region.type); +} + +bool mapAtAddr(uintptr_t addr, std::size_t size, const char *name, void **outPtr) { + void *p = mmap(reinterpret_cast(addr), size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); + if (p == MAP_FAILED) { + return false; + } + if (name) { + prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, addr, size, name); + } + recordGuestMapping(addr, size, PAGE_READWRITE, MEM_RESERVE, PAGE_READWRITE, MEM_PRIVATE); + if (outPtr) { + *outPtr = p; + } + return true; +} + +bool findFreeMapping(std::size_t size, uintptr_t minAddr, uintptr_t maxAddr, bool preferTop, uintptr_t *outAddr) { + if (outAddr == nullptr || size == 0 || g_mappings == nullptr) { + return false; + } + + const uintptr_t pageSize = wibo::heap::systemPageSize(); + const uintptr_t alignedSize = alignUp(static_cast(size), pageSize); + const uintptr_t granularity = kVirtualAllocationGranularity; + uintptr_t searchMin = static_cast(minAddr); + uintptr_t searchMax = static_cast(maxAddr); + + if (searchMax <= searchMin || alignedSize > (searchMax - searchMin)) { + return false; + } + + std::lock_guard guard(g_mappingsMutex); + + auto tryGap = [&](uintptr_t gapStart, uintptr_t gapEnd, uintptr_t &result) -> bool { + if (gapEnd <= gapStart) { + return false; + } + + uintptr_t lower = alignUp(gapStart, granularity); + if (lower >= gapEnd) { + return false; + } + + if (!preferTop) { + if (lower + alignedSize <= gapEnd) { + result = lower; + return true; + } + return false; + } + + if (gapEnd < alignedSize) { + return false; + } + + uintptr_t upper = gapEnd - alignedSize; + uintptr_t chosen = alignDown(upper, granularity); + if (chosen < lower) { + return false; + } + if (chosen + alignedSize > gapEnd) { + return false; + } + result = chosen; + return true; + }; + + uintptr_t cursor = alignUp(searchMin, granularity); + for (auto &g_mapping : *g_mappings) { + uintptr_t mapStart = g_mapping.first; + uintptr_t mapEnd = mapStart + static_cast(g_mapping.second.RegionSize); + + if (mapEnd <= searchMin) { + continue; + } + if (mapStart >= searchMax) { + if (tryGap(cursor, searchMax, *outAddr)) { + return true; + } + break; + } + + if (mapStart > cursor) { + uintptr_t gapEnd = std::min(mapStart, searchMax); + if (tryGap(cursor, gapEnd, *outAddr)) { + return true; + } + } + + if (mapEnd > cursor) { + cursor = alignUp(mapEnd, pageSize); + } + + if (cursor >= searchMax) { + break; + } + } + + if (cursor < searchMax) { + if (tryGap(cursor, searchMax, *outAddr)) { + return true; + } + } + + return false; +} + +bool mapArena(std::size_t size, uintptr_t minAddr, uintptr_t maxAddr, bool preferTop, const char *name, + ArenaRange &out) { + const std::size_t ps = wibo::heap::systemPageSize(); + size = (size + ps - 1) & ~(ps - 1); + uintptr_t cand = 0; + void *p = nullptr; + if (findFreeMapping(size, minAddr, maxAddr, preferTop, &cand)) { + DEBUG_LOG("heap: found free mapping at %lx\n", cand); + if (mapAtAddr(cand, size, name, &p)) { + out.start = p; + out.size = size; + return true; + } + } + return false; +} + +void initializeImpl() { + if (g_initialized) { + return; + } + + // Map and register guest arena (below 2GB, exclusive) + ArenaRange guest; + if (mapArena(kGuestArenaSize, kLowMemoryStart, kTopDownStart, false, "wibo guest arena", guest)) { + bool ok = mi_manage_os_memory_ex(guest.start, guest.size, + /*is_committed*/ false, + /*is_pinned*/ false, + /*is_zero*/ true, + /*numa_node*/ -1, + /*exclusive*/ true, &g_guestArenaId); + if (ok) { + g_guest = guest; + } else { + LOG_ERR("heap: failed to register guest arena with mimalloc\n"); + } + } + if (g_guest.size) { + DEBUG_LOG("heap: initialized guest arena %p..%p (%zu MiB) id=%p\n", g_guest.start, + static_cast(static_cast(g_guest.start) + g_guest.size), g_guest.size >> 20, + g_guestArenaId); + } else { + DEBUG_LOG("heap: guest arena initialization incomplete\n"); + } + + g_initialized = true; +} + +} // anonymous namespace + +namespace wibo::heap { + +bool initialize() { + std::call_once(g_initOnce, initializeImpl); + return g_initialized; +} + +uintptr_t systemPageSize() { + static uintptr_t cached = []() { + long detected = sysconf(_SC_PAGESIZE); + if (detected <= 0) { + return static_cast(4096); + } + return static_cast(detected); + }(); + return cached; +} + +mi_heap_t *getGuestHeap() { + initialize(); + if (g_guestHeap == nullptr) { + g_guestHeap = createGuestHeap(); + } + return g_guestHeap; +} + +mi_heap_t *createGuestHeap() { + initialize(); + if (g_guestArenaId != nullptr) { + if (mi_heap_t *h = mi_heap_new_ex(0, true, g_guestArenaId)) { + DEBUG_LOG("heap: created guest heap in arena %p\n", g_guestArenaId); + return h; + } + } + DEBUG_LOG("heap: created guest heap without arena\n"); + return mi_heap_new(); +} + +void *guestMalloc(std::size_t size) { return mi_heap_malloc(getGuestHeap(), size); } + +void *guestCalloc(std::size_t count, std::size_t size) { return mi_heap_calloc(getGuestHeap(), count, size); } + +void *guestRealloc(void *ptr, std::size_t newSize) { return mi_heap_realloc(getGuestHeap(), ptr, newSize); } + +void guestFree(void *ptr) { mi_free(ptr); } + +uintptr_t allocationGranularity() { return kVirtualAllocationGranularity; } + +DWORD win32ErrorFromVmStatus(VmStatus status) { + switch (status) { + case VmStatus::Success: + return ERROR_SUCCESS; + case VmStatus::InvalidParameter: + return ERROR_INVALID_PARAMETER; + case VmStatus::InvalidAddress: + case VmStatus::Rejected: + return ERROR_INVALID_ADDRESS; + case VmStatus::NoAccess: + return ERROR_NOACCESS; + case VmStatus::NotSupported: + return ERROR_NOT_SUPPORTED; + case VmStatus::NoMemory: + return ERROR_NOT_ENOUGH_MEMORY; + case VmStatus::UnknownError: + default: + return ERROR_INVALID_PARAMETER; + } +} + +NTSTATUS ntStatusFromVmStatus(VmStatus status) { return wibo::statusFromWinError(win32ErrorFromVmStatus(status)); } + +VmStatus virtualReset(void *baseAddress, std::size_t regionSize) { + if (!baseAddress) { + return VmStatus::InvalidAddress; + } + if (regionSize == 0) { + return VmStatus::InvalidParameter; + } + uintptr_t request = reinterpret_cast(baseAddress); + if (addOverflows(request, regionSize)) { + return VmStatus::InvalidParameter; + } + const uintptr_t pageSize = wibo::heap::systemPageSize(); + uintptr_t start = alignDown(request, pageSize); + uintptr_t end = alignUp(request + static_cast(regionSize), pageSize); + std::size_t length = static_cast(end - start); + if (length == 0) { + return VmStatus::InvalidParameter; + } + std::unique_lock allocLock(g_virtualAllocMutex); + VirtualAllocation *region = lookupRegion(start); + if (!region || !rangeWithinRegion(*region, start, length)) { + return VmStatus::InvalidAddress; + } + allocLock.unlock(); +#ifdef MADV_FREE + int advice = MADV_FREE; +#else + int advice = MADV_DONTNEED; +#endif + if (madvise(reinterpret_cast(start), length, advice) != 0) { + return vmStatusFromErrno(errno); + } + return VmStatus::Success; +} + +VmStatus virtualAlloc(void **baseAddress, std::size_t *regionSize, DWORD allocationType, DWORD protect, DWORD type) { + if (!regionSize) { + return VmStatus::InvalidParameter; + } + std::size_t requestedSize = *regionSize; + if (requestedSize == 0) { + return VmStatus::InvalidParameter; + } + void *requestedAddress = baseAddress ? *baseAddress : nullptr; + + DWORD unsupportedFlags = allocationType & (MEM_WRITE_WATCH | MEM_PHYSICAL | MEM_LARGE_PAGES | MEM_RESET_UNDO); + if (unsupportedFlags != 0) { + return VmStatus::NotSupported; + } + + bool reserve = (allocationType & MEM_RESERVE) != 0; + bool commit = (allocationType & MEM_COMMIT) != 0; + bool reset = (allocationType & MEM_RESET) != 0; + bool topDown = (allocationType & MEM_TOP_DOWN) != 0; + + if (!reserve && commit && requestedAddress == nullptr) { + reserve = true; + } + + const uintptr_t pageSize = wibo::heap::systemPageSize(); + if (reset) { + if (reserve || commit) { + return VmStatus::InvalidParameter; + } + if (requestedAddress == nullptr) { + return VmStatus::InvalidAddress; + } + uintptr_t requestVal = reinterpret_cast(requestedAddress); + uintptr_t start = alignDown(requestVal, pageSize); + uintptr_t end = alignUp(requestVal + static_cast(requestedSize), pageSize); + std::size_t length = static_cast(end - start); + VmStatus status = virtualReset(requestedAddress, requestedSize); + if (status == VmStatus::Success) { + if (baseAddress) { + *baseAddress = reinterpret_cast(start); + } + *regionSize = length; + } + return status; + } + + if (!reserve && !commit) { + return VmStatus::InvalidParameter; + } + + std::unique_lock allocLock(g_virtualAllocMutex); + + if (reserve) { + uintptr_t base = 0; + std::size_t length = 0; + if (requestedAddress != nullptr) { + uintptr_t request = reinterpret_cast(requestedAddress); + base = alignDown(request, kVirtualAllocationGranularity); + std::size_t offset = static_cast(request - base); + if (addOverflows(offset, requestedSize)) { + return VmStatus::InvalidParameter; + } + std::size_t span = requestedSize + offset; + uintptr_t alignedSpan = alignUp(static_cast(span), pageSize); + if (alignedSpan == 0) { + return VmStatus::InvalidParameter; + } + length = static_cast(alignedSpan); + if (length == 0) { + return VmStatus::InvalidParameter; + } + if (base >= kTwoGB || (base + length) > kTwoGB) { + return VmStatus::InvalidAddress; + } + if (overlapsExistingMapping(base, length)) { + return VmStatus::InvalidAddress; + } + } else { + uintptr_t aligned = alignUp(static_cast(requestedSize), pageSize); + if (aligned == 0) { + return VmStatus::InvalidParameter; + } + length = static_cast(aligned); + if (!findFreeMapping(length, kLowMemoryStart, kTopDownStart, topDown, &base)) { + return VmStatus::NoMemory; + } + if (base >= kTwoGB || (base + length) > kTwoGB) { + return VmStatus::NoMemory; + } + } + + int prot = commit ? posixProtectFromWin32(protect) : PROT_NONE; + int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED; + if (!commit) { + flags |= MAP_NORESERVE; + } + void *mapped = mmap(reinterpret_cast(base), length, prot, flags, -1, 0); + if (mapped == MAP_FAILED) { + return vmStatusFromErrno(errno); + } + if (type == MEM_IMAGE) { + prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, base, length, "wibo guest image"); + } else { + prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, base, length, "wibo guest allocated"); + } + uintptr_t actualBase = reinterpret_cast(mapped); + VirtualAllocation allocation{}; + allocation.base = actualBase; + allocation.size = length; + allocation.allocationProtect = protect; + allocation.type = type; + allocation.pageProtect.assign(length / pageSize, commit ? protect : 0); + g_virtualAllocations[actualBase] = std::move(allocation); + refreshGuestMapping(g_virtualAllocations[actualBase]); + + if (baseAddress) { + *baseAddress = reinterpret_cast(actualBase); + } + *regionSize = length; + return VmStatus::Success; + } + + if (requestedAddress == nullptr) { + return VmStatus::InvalidAddress; + } + uintptr_t request = reinterpret_cast(requestedAddress); + if (addOverflows(request, requestedSize)) { + return VmStatus::InvalidParameter; + } + uintptr_t start = alignDown(request, pageSize); + uintptr_t end = alignUp(request + static_cast(requestedSize), pageSize); + std::size_t length = static_cast(end - start); + if (length == 0) { + return VmStatus::InvalidParameter; + } + + VirtualAllocation *region = lookupRegion(start); + if (!region || !rangeWithinRegion(*region, start, length)) { + return VmStatus::InvalidAddress; + } + + const std::size_t pageCount = length / pageSize; + std::vector> runs; + runs.reserve(pageCount); + for (std::size_t i = 0; i < pageCount; ++i) { + std::size_t pageIndex = ((start - region->base) / pageSize) + i; + if (pageIndex >= region->pageProtect.size()) { + return VmStatus::InvalidAddress; + } + if (region->pageProtect[pageIndex] != 0) { + continue; + } + uintptr_t runBase = start + i * pageSize; + std::size_t runLength = pageSize; + while (i + 1 < pageCount) { + std::size_t nextIndex = ((start - region->base) / pageSize) + i + 1; + if (region->pageProtect[nextIndex] != 0) { + break; + } + ++i; + runLength += pageSize; + } + runs.emplace_back(runBase, runLength); + } + + for (const auto &run : runs) { + void *res = mmap(reinterpret_cast(run.first), run.second, posixProtectFromWin32(protect), + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); + if (res == MAP_FAILED) { + return vmStatusFromErrno(errno); + } + prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, run.first, run.second, "wibo guest committed"); + markCommitted(*region, run.first, run.second, protect); + } + + refreshGuestMapping(*region); + + if (baseAddress) { + *baseAddress = reinterpret_cast(start); + } + *regionSize = length; + return VmStatus::Success; +} + +VmStatus virtualFree(void *baseAddress, std::size_t regionSize, DWORD freeType) { + if (!baseAddress) { + return VmStatus::InvalidAddress; + } + if ((freeType & (MEM_COALESCE_PLACEHOLDERS | MEM_PRESERVE_PLACEHOLDER)) != 0) { + return VmStatus::NotSupported; + } + + const bool release = (freeType & MEM_RELEASE) != 0; + const bool decommit = (freeType & MEM_DECOMMIT) != 0; + if (release == decommit) { + return VmStatus::InvalidParameter; + } + + const uintptr_t pageSize = wibo::heap::systemPageSize(); + std::unique_lock allocLock(g_virtualAllocMutex); + + if (release) { + uintptr_t base = reinterpret_cast(baseAddress); + auto it = g_virtualAllocations.find(base); + if (it == g_virtualAllocations.end()) { + auto containing = findRegionIterator(base); + if (regionSize != 0 && containing != g_virtualAllocations.end()) { + return VmStatus::InvalidParameter; + } + return VmStatus::InvalidAddress; + } + if (regionSize != 0) { + return VmStatus::InvalidParameter; + } + std::size_t length = it->second.size; + g_virtualAllocations.erase(it); + allocLock.unlock(); + // Replace with PROT_NONE + MAP_NORESERVE to release physical memory + void *res = mmap(reinterpret_cast(base), length, PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | MAP_NORESERVE, -1, 0); + if (res == MAP_FAILED) { + return vmStatusFromErrno(errno); + } + prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, base, length, "wibo reserved"); + eraseGuestMapping(base); + return VmStatus::Success; + } + + uintptr_t request = reinterpret_cast(baseAddress); + auto regionIt = findRegionIterator(request); + if (regionIt == g_virtualAllocations.end()) { + return VmStatus::InvalidAddress; + } + VirtualAllocation ®ion = regionIt->second; + uintptr_t start = alignDown(request, pageSize); + uintptr_t end = 0; + if (regionSize == 0) { + if (request != region.base) { + return VmStatus::InvalidParameter; + } + start = region.base; + end = region.base + region.size; + } else { + if (addOverflows(request, regionSize)) { + return VmStatus::InvalidParameter; + } + end = alignUp(request + static_cast(regionSize), pageSize); + } + if (end <= start) { + return VmStatus::InvalidParameter; + } + std::size_t length = static_cast(end - start); + if (!rangeWithinRegion(region, start, length)) { + return VmStatus::InvalidAddress; + } + void *res = mmap(reinterpret_cast(start), length, PROT_NONE, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | MAP_NORESERVE, -1, 0); + if (res == MAP_FAILED) { + return vmStatusFromErrno(errno); + } + prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, res, length, "wibo reserved"); + markDecommitted(region, start, length); + refreshGuestMapping(region); + return VmStatus::Success; +} + +VmStatus virtualProtect(void *baseAddress, std::size_t regionSize, DWORD newProtect, DWORD *oldProtect) { + if (!baseAddress || regionSize == 0) { + return VmStatus::InvalidParameter; + } + + const std::size_t pageSize = systemPageSize(); + uintptr_t request = reinterpret_cast(baseAddress); + uintptr_t start = alignDown(request, pageSize); + uintptr_t end = alignUp(request + static_cast(regionSize), pageSize); + if (end <= start) { + return VmStatus::InvalidParameter; + } + + std::unique_lock allocLock(g_virtualAllocMutex); + VirtualAllocation *region = lookupRegion(start); + if (!region || !rangeWithinRegion(*region, start, static_cast(end - start))) { + return VmStatus::InvalidAddress; + } + + const std::size_t firstPage = (start - region->base) / pageSize; + const std::size_t pageCount = (end - start) / pageSize; + if (pageCount == 0) { + return VmStatus::InvalidParameter; + } + + DWORD previousProtect = region->pageProtect[firstPage]; + if (previousProtect == 0) { + return VmStatus::NoAccess; + } + for (std::size_t i = 0; i < pageCount; ++i) { + if (region->pageProtect[firstPage + i] == 0) { + return VmStatus::NoAccess; + } + } + + int prot = posixProtectFromWin32(newProtect); + if (mprotect(reinterpret_cast(start), end - start, prot) != 0) { + return vmStatusFromErrno(errno); + } + for (std::size_t i = 0; i < pageCount; ++i) { + region->pageProtect[firstPage + i] = newProtect; + } + refreshGuestMapping(*region); + + if (oldProtect) { + *oldProtect = previousProtect; + } + return VmStatus::Success; +} + +VmStatus virtualQuery(const void *address, MEMORY_BASIC_INFORMATION *outInfo) { + if (!outInfo) { + return VmStatus::InvalidParameter; + } + + const std::size_t pageSize = systemPageSize(); + uintptr_t request = address ? reinterpret_cast(address) : 0; + if (request >= kTwoGB) { + return VmStatus::InvalidParameter; + } + uintptr_t pageBase = alignDown(request, pageSize); + + std::unique_lock allocLock(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 = kTwoGB; + } + if (regionEnd <= regionStart) { + regionEnd = regionStart + pageSize; + } + allocLock.unlock(); + outInfo->BaseAddress = reinterpret_cast(regionStart); + outInfo->AllocationBase = nullptr; + outInfo->AllocationProtect = 0; + outInfo->RegionSize = regionEnd - regionStart; + outInfo->State = MEM_FREE; + outInfo->Protect = PAGE_NOACCESS; + outInfo->Type = 0; + return VmStatus::Success; + } + + const uintptr_t regionLimit = region->base + region->size; + const std::size_t pageIndex = (pageBase - region->base) / pageSize; + if (pageIndex >= region->pageProtect.size()) { + allocLock.unlock(); + return VmStatus::InvalidAddress; + } + const DWORD pageProtect = region->pageProtect[pageIndex]; + const bool committed = pageProtect != 0; + uintptr_t blockStart = pageBase; + uintptr_t blockEnd = pageBase + pageSize; + while (blockStart > region->base) { + std::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) { + std::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; + } + DWORD allocationProtect = region->allocationProtect != 0 ? region->allocationProtect : PAGE_NOACCESS; + DWORD finalProtect = committed ? pageProtect : PAGE_NOACCESS; + allocLock.unlock(); + + outInfo->BaseAddress = reinterpret_cast(blockStart); + outInfo->AllocationBase = reinterpret_cast(region->base); + outInfo->AllocationProtect = allocationProtect; + outInfo->RegionSize = blockEnd - blockStart; + outInfo->State = committed ? MEM_COMMIT : MEM_RESERVE; + outInfo->Protect = finalProtect; + outInfo->Type = region->type; + return VmStatus::Success; +} + +VmStatus reserveViewRange(std::size_t regionSize, uintptr_t minAddr, uintptr_t maxAddr, void **baseAddress) { + if (!baseAddress || regionSize == 0) { + return VmStatus::InvalidParameter; + } + const uintptr_t pageSize = wibo::heap::systemPageSize(); + std::size_t aligned = static_cast(alignUp(static_cast(regionSize), pageSize)); + if (aligned == 0) { + return VmStatus::InvalidParameter; + } + if (minAddr == 0) { + minAddr = kLowMemoryStart; + } + if (maxAddr == 0) { + maxAddr = kTopDownStart; + } + if (minAddr >= maxAddr || aligned > (maxAddr - minAddr)) { + return VmStatus::InvalidParameter; + } + uintptr_t candidate = 0; + if (!findFreeMapping(aligned, minAddr, maxAddr, false, &candidate)) { + return VmStatus::NoMemory; + } + recordGuestMapping(candidate, aligned, PAGE_NOACCESS, MEM_RESERVE, PAGE_NOACCESS, MEM_MAPPED); + *baseAddress = reinterpret_cast(candidate); + return VmStatus::Success; +} + +void registerViewRange(void *baseAddress, std::size_t regionSize, DWORD allocationProtect, DWORD protect) { + if (!baseAddress) { + return; + } + const uintptr_t pageSize = wibo::heap::systemPageSize(); + std::size_t aligned = static_cast(alignUp(static_cast(regionSize), pageSize)); + recordGuestMapping(reinterpret_cast(baseAddress), aligned, allocationProtect, MEM_COMMIT, protect, + MEM_MAPPED); +} + +void releaseViewRange(void *baseAddress) { + if (!baseAddress) { + return; + } + eraseGuestMapping(reinterpret_cast(baseAddress)); +} + +bool reserveGuestStack(std::size_t stackSizeBytes, void **outStackLimit, void **outStackBase) { + const std::size_t ps = systemPageSize(); + std::size_t total = ((stackSizeBytes + (ps * 2) - 1) & ~(ps - 1)); + + ArenaRange r; + if (!mapArena(total, kTopDownStart, kTwoGB, true, "wibo guest stack", r)) { + DEBUG_LOG("heap: reserveGuestStack: failed to map low region\n"); + return false; + } + + // Protect the guard page at the bottom of the mapped region + if (mprotect(r.start, ps, PROT_NONE) != 0) { + // Non-fatal; continue without guard + DEBUG_LOG("heap: reserveGuestStack: mprotect guard failed\n"); + } + + // Stack grows downwards; limit is after guard, base is top of mapping + void *limit = static_cast(r.start) + ps; + void *base = static_cast(r.start) + r.size; + *outStackLimit = limit; + *outStackBase = base; + DEBUG_LOG("heap: reserved guest stack limit=%p base=%p (total=%zu KiB)\n", limit, base, r.size >> 10); + return true; +} + +} // namespace wibo::heap + +static void debugPrintMaps() { + char buf[1024]; + int fd = open("/proc/self/maps", O_RDONLY); + if (fd == -1) { + LOG_ERR("heap: failed to open /proc/self/maps\n"); + return; + } + while (true) { + ssize_t r = read(fd, buf, sizeof(buf)); + if (r == 0) { + break; + } else if (r == -1) { + LOG_ERR("heap: failed to read /proc/self/maps\n"); + close(fd); + return; + } + write(STDERR_FILENO, buf, r); + } + close(fd); +} + +constexpr size_t MAPS_BUFFER_SIZE = 0x10000; +constexpr size_t MAX_NUM_MAPPINGS = 128; + +/** + * Read /proc/self/maps into a buffer. + * + * While reading /proc/self/maps, we need to be extremely careful not to allocate any memory, + * as that could cause libc to modify memory mappings while we're attempting to fill them. + * To accomplish this, we use Linux syscalls directly. + * + * @param buffer The buffer to read into. + * @return The number of bytes read. + */ +static size_t readMaps(char *buffer) { + int fd = open("/proc/self/maps", O_RDONLY); + if (fd == -1) { + perror("heap: failed to open /proc/self/maps"); + exit(1); + } + + char *cur = buffer; + char *bufferEnd = buffer + MAPS_BUFFER_SIZE; + while (cur < bufferEnd) { + int ret = read(fd, cur, static_cast(bufferEnd - cur)); + if (ret == -1) { + if (errno == EINTR) { + continue; + } + perror("heap: failed to read /proc/self/maps"); + exit(1); + } else if (ret == 0) { + break; + } + cur += ret; + } + close(fd); + + if (cur == bufferEnd) { + fprintf(stderr, "heap: buffer too small while reading /proc/self/maps\n"); + exit(1); + } + *cur = '\0'; + return static_cast(cur - buffer); +} + +/** + * Map the upper 2GB of memory to prevent libc from allocating there. + * + * This is necessary because 32-bit windows only reserves the lowest 2GB of memory for use by a process + * (https://www.tenouk.com/WinVirtualAddressSpace.html). Linux, on the other hand, will happily allow + * nearly the entire 4GB address space to be used. Some Windows programs rely on heap allocations to be + * in the lower 2GB of memory, otherwise they misbehave or crash. + * + * Between reading /proc/self/maps and mmap-ing the upper 2GB, we must be extremely careful not to allocate + * any memory, as that could cause libc to modify memory mappings while we're attempting to fill them. + */ +static size_t blockLower2GB(MEMORY_BASIC_INFORMATION mappings[MAX_NUM_MAPPINGS]) { + // Buffer lives on the stack to avoid heap allocation + char buffer[MAPS_BUFFER_SIZE]; + size_t len = readMaps(buffer); + std::string_view procLine(buffer, len); + uintptr_t lastMapEnd = mmapMinAddr(); + size_t numMappings = 0; + while (true) { + size_t newline = procLine.find('\n'); + if (newline == std::string::npos) { + break; + } + + uintptr_t mapStart = 0; + const char *lineEnd = procLine.data() + procLine.size(); + auto result = std::from_chars(procLine.data(), lineEnd, mapStart, 16); + if (result.ec != std::errc()) { + break; + } + if (result.ptr >= lineEnd || *result.ptr != '-') { + continue; + } + uintptr_t mapEnd = 0; + result = std::from_chars(result.ptr + 1, lineEnd, mapEnd, 16); + if (result.ec != std::errc()) { + break; + } + if (mapStart == mapEnd || mapStart > mapEnd) { + continue; + } + + if (numMappings < MAX_NUM_MAPPINGS) { + if (numMappings > 0) { + auto &prevMapping = mappings[numMappings - 1]; + uintptr_t prevMapStart = reinterpret_cast(prevMapping.BaseAddress); + uintptr_t prevMapEnd = prevMapStart + prevMapping.RegionSize; + if (mapStart <= prevMapEnd) { + // Extend the previous mapping + prevMapping.RegionSize = mapEnd - prevMapStart; + lastMapEnd = mapEnd; + procLine = procLine.substr(newline + 1); + continue; + } + } + mappings[numMappings++] = (MEMORY_BASIC_INFORMATION){ + .BaseAddress = reinterpret_cast(mapStart), + .AllocationBase = reinterpret_cast(mapStart), + .AllocationProtect = PAGE_NOACCESS, + .RegionSize = mapEnd - mapStart, + .State = MEM_RESERVE, + .Protect = PAGE_NOACCESS, + .Type = 0, // external + }; + } + + // The empty space we want to map out is now between lastMapEnd and mapStart + uintptr_t reserveStart = lastMapEnd; + uintptr_t reserveEnd = mapStart; + + if ((reserveEnd - reserveStart) != 0 && reserveStart < kTwoGB) { + reserveEnd = std::min(reserveEnd, kTwoGB); + + uintptr_t len = reserveEnd - reserveStart; + int flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE; +#ifdef MAP_FIXED_NOREPLACE + flags |= MAP_FIXED_NOREPLACE; +#else + flags |= MAP_FIXED; +#endif + void *ptr = mmap(reinterpret_cast(reserveStart), len, PROT_NONE, flags, -1, 0); + if (ptr == MAP_FAILED) { + perror("heap: failed reserve memory"); + exit(1); + } + prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, ptr, len, "wibo reserved"); + } + + lastMapEnd = mapEnd; + procLine = procLine.substr(newline + 1); + } + + return numMappings; +} + +#if defined(__clang__) +__attribute__((constructor(101))) +#else +__attribute__((constructor)) +#endif +__attribute__((used)) static void wibo_heap_constructor() { + MEMORY_BASIC_INFORMATION mappings[MAX_NUM_MAPPINGS]; + memset(mappings, 0, sizeof(mappings)); + bool debug = getenv("WIBO_DEBUG_HEAP") != nullptr; + if (debug) { + LOG_OUT("heap: initializing...\n"); + debugPrintMaps(); + } + size_t numMappings = blockLower2GB(mappings); + // Now we can allocate memory + if (debug) { + mi_option_enable(mi_option_show_stats); + mi_option_enable(mi_option_verbose); + } + g_mappings = new std::map; + for (size_t i = 0; i < numMappings; ++i) { + if (debug) { + fprintf(stderr, "Existing %zu: BaseAddress=%p, RegionSize=%lu\n", i, mappings[i].BaseAddress, + mappings[i].RegionSize); + } + g_mappings->emplace(reinterpret_cast(mappings[i].BaseAddress), mappings[i]); + } +} diff --git a/src/heap.h b/src/heap.h new file mode 100644 index 0000000..e80da91 --- /dev/null +++ b/src/heap.h @@ -0,0 +1,53 @@ +#pragma once + +#include "types.h" + +#include +#include +#include + +struct mi_heap_s; +typedef struct mi_heap_s mi_heap_t; + +namespace wibo::heap { + +bool initialize(); +uintptr_t systemPageSize(); +uintptr_t allocationGranularity(); +mi_heap_t *getGuestHeap(); +mi_heap_t *createGuestHeap(); + +enum class VmStatus : uint32_t { + Success = 0, + InvalidParameter, + InvalidAddress, + NoAccess, + NotSupported, + NoMemory, + Rejected, + UnknownError, +}; + +// Guest heap memory allocation helpers +void *guestMalloc(std::size_t size); +void *guestCalloc(std::size_t count, std::size_t size); +void *guestRealloc(void *ptr, std::size_t newSize); +void guestFree(void *ptr); + +VmStatus virtualAlloc(void **baseAddress, std::size_t *regionSize, DWORD allocationType, DWORD protect, + DWORD type = MEM_PRIVATE); +VmStatus virtualFree(void *baseAddress, std::size_t regionSize, DWORD freeType); +VmStatus virtualProtect(void *baseAddress, std::size_t regionSize, DWORD newProtect, DWORD *oldProtect); +VmStatus virtualQuery(const void *address, MEMORY_BASIC_INFORMATION *outInfo); +VmStatus virtualReset(void *baseAddress, std::size_t regionSize); + +VmStatus reserveViewRange(std::size_t regionSize, uintptr_t minAddr, uintptr_t maxAddr, void **baseAddress); +void registerViewRange(void *baseAddress, std::size_t regionSize, DWORD allocationProtect, DWORD protect); +void releaseViewRange(void *baseAddress); + +DWORD win32ErrorFromVmStatus(VmStatus status); +NTSTATUS ntStatusFromVmStatus(VmStatus status); + +bool reserveGuestStack(std::size_t stackSizeBytes, void **outStackLimit, void **outStackBase); + +} // namespace wibo::heap diff --git a/src/loader.cpp b/src/loader.cpp index 3f65d02..91277c5 100644 --- a/src/loader.cpp +++ b/src/loader.cpp @@ -1,12 +1,13 @@ #include "common.h" #include "errors.h" +#include "heap.h" #include "kernel32/internal.h" #include "modules.h" +#include "types.h" #include #include #include -#include #include #include @@ -183,7 +184,7 @@ uint32_t read32(FILE *file) { wibo::Executable::~Executable() { if (imageBase) { - munmap(imageBase, imageSize); + wibo::heap::virtualFree(imageBase, 0, MEM_RELEASE); imageBase = nullptr; } } @@ -239,20 +240,28 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { // Build buffer imageSize = header32.sizeOfImage; - int prot = PROT_READ | PROT_WRITE; - if (exec) - prot |= PROT_EXEC; - void *preferredBase = (void *)(uintptr_t)header32.imageBase; - imageBase = mmap(preferredBase, header32.sizeOfImage, prot, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - if (imageBase == MAP_FAILED) { - imageBase = mmap(nullptr, header32.sizeOfImage, prot, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + DWORD initialProtect = exec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE; + void *preferredBase = reinterpret_cast(static_cast(header32.imageBase)); + void *allocatedBase = preferredBase; + std::size_t allocationSize = static_cast(header32.sizeOfImage); + wibo::heap::VmStatus allocStatus = wibo::heap::virtualAlloc( + &allocatedBase, &allocationSize, MEM_RESERVE | MEM_COMMIT, initialProtect, MEM_IMAGE); + if (allocStatus != wibo::heap::VmStatus::Success) { + DEBUG_LOG("loadPE: preferred base allocation failed (status=%u), retrying anywhere\n", + static_cast(allocStatus)); + allocatedBase = nullptr; + allocationSize = static_cast(header32.sizeOfImage); + allocStatus = wibo::heap::virtualAlloc( + &allocatedBase, &allocationSize, MEM_RESERVE | MEM_COMMIT, initialProtect, MEM_IMAGE); } - if (imageBase == MAP_FAILED) { - perror("Image mapping failed!"); + if (allocStatus != wibo::heap::VmStatus::Success) { + DEBUG_LOG("Image mapping failed (status=%u)\n", static_cast(allocStatus)); imageBase = nullptr; return false; } - relocationDelta = (intptr_t)((uintptr_t)imageBase - (uintptr_t)header32.imageBase); + imageBase = allocatedBase; + relocationDelta = + static_cast(reinterpret_cast(imageBase) - static_cast(header32.imageBase)); memset(imageBase, 0, header32.sizeOfImage); sections.clear(); uintptr_t imageBaseAddr = reinterpret_cast(imageBase); @@ -315,7 +324,7 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { if (exec && relocationDelta != 0) { if (relocationDirectoryRVA == 0 || relocationDirectorySize == 0) { DEBUG_LOG("Relocation required but no relocation directory present\n"); - munmap(imageBase, imageSize); + wibo::heap::virtualFree(imageBase, 0, MEM_RELEASE); imageBase = nullptr; return false; } @@ -361,9 +370,34 @@ bool wibo::Executable::loadPE(FILE *file, bool exec) { } bool wibo::Executable::resolveImports() { + auto finalizeSections = [this]() -> bool { + if (!execMapped || sectionsProtected) { + return true; + } + for (const auto §ion : sections) { + if (section.size == 0) { + continue; + } + void *sectionAddress = reinterpret_cast(section.base); + wibo::heap::VmStatus status = + wibo::heap::virtualProtect(sectionAddress, section.size, section.protect, nullptr); + if (status != wibo::heap::VmStatus::Success) { + DEBUG_LOG("resolveImports: failed to set section protection at %p (size=%zu, protect=0x%x) status=%u\n", + sectionAddress, section.size, section.protect, static_cast(status)); + kernel32::setLastError(wibo::heap::win32ErrorFromVmStatus(status)); + return false; + } + } + sectionsProtected = true; + return true; + }; + if (importsResolved || !execMapped) { importsResolved = true; importsResolving = false; + if (!finalizeSections()) { + return false; + } return true; } if (importsResolving) { @@ -374,6 +408,9 @@ bool wibo::Executable::resolveImports() { if (!importDirectoryRVA) { importsResolved = true; importsResolving = false; + if (!finalizeSections()) { + return false; + } return true; } @@ -381,6 +418,9 @@ bool wibo::Executable::resolveImports() { if (!dir) { importsResolved = true; importsResolving = false; + if (!finalizeSections()) { + return false; + } return true; } @@ -464,5 +504,9 @@ bool wibo::Executable::resolveImports() { importsResolved = true; importsResolving = false; + if (!finalizeSections()) { + importsResolved = false; + return false; + } return true; } diff --git a/src/main.cpp b/src/main.cpp index f5fa939..877e019 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,6 +3,7 @@ #include "entry.h" #include "entry_trampolines.h" #include "files.h" +#include "heap.h" #include "modules.h" #include "processes.h" #include "strutil.h" @@ -501,10 +502,7 @@ int main(int argc, char **argv) { kernel32::setLastError(0); // Invoke the damn thing - { - GUEST_CONTEXT_GUARD(&tib); - call_EntryProc(entryPoint); - } + call_EntryProc(entryPoint); DEBUG_LOG("We came back\n"); wibo::shutdownModuleRegistry(); wibo::tls::cleanupTib(&tib); diff --git a/src/modules.cpp b/src/modules.cpp index 5c57f26..39b04bb 100644 --- a/src/modules.cpp +++ b/src/modules.cpp @@ -1,11 +1,11 @@ #include "modules.h" #include "common.h" -#include "context.h" #include "entry.h" #include "entry_trampolines.h" #include "errors.h" #include "files.h" +#include "heap.h" #include "kernel32/internal.h" #include "msvcrt.h" #include "msvcrt_trampolines.h" @@ -87,16 +87,11 @@ std::string makeStubKey(const char *dllName, const char *funcName) { return key; } -void stubBase(size_t index) { - const char *func = stubFuncNames[index].empty() ? "" : stubFuncNames[index].c_str(); - const char *dll = stubDlls[index].empty() ? "" : stubDlls[index].c_str(); - fprintf(stderr, "wibo: call reached missing import %s from %s\n", func, dll); - fflush(stderr); - abort(); +template void stubThunk() { + // Call the appropriate entry trampoline + thunk_entry_stubBase(Index); } -template void stubThunk() { stubBase(Index); } - template constexpr std::array makeStubTable(std::index_sequence) { return {{stubThunk...}}; @@ -293,7 +288,7 @@ bool allocateModuleTlsForThread(wibo::ModuleInfo &module, TEB *tib) { void *block = nullptr; const size_t allocationSize = info.allocationSize; if (allocationSize > 0) { - block = std::malloc(allocationSize); + block = wibo::heap::guestMalloc(allocationSize); if (!block) { DEBUG_LOG(" allocateModuleTlsForThread: failed to allocate %zu bytes for %s\n", allocationSize, module.originalName.c_str()); @@ -716,6 +711,19 @@ void ensureExportsInitialized(wibo::ModuleInfo &info) { } // namespace +namespace entry { + +// Trampoline target for missing imports +void stubBase(SIZE_T index) { + const char *func = stubFuncNames[index].empty() ? "" : stubFuncNames[index].c_str(); + const char *dll = stubDlls[index].empty() ? "" : stubDlls[index].c_str(); + fprintf(stderr, "wibo: call reached missing import %s from %s\n", func, dll); + fflush(stderr); + abort(); +} + +} // namespace entry + namespace wibo { void initializeModuleRegistry() { registry(); } diff --git a/src/modules.h b/src/modules.h index 41270d6..7653217 100644 --- a/src/modules.h +++ b/src/modules.h @@ -62,6 +62,7 @@ class Executable { bool execMapped = false; bool importsResolved = false; bool importsResolving = false; + bool sectionsProtected = false; std::vector sections; }; diff --git a/src/types.h b/src/types.h index 483a6d6..6a3c480 100644 --- a/src/types.h +++ b/src/types.h @@ -436,3 +436,13 @@ static_assert(offsetof(TEB, LastErrorValue) == 0x34, "LastErrorValue offset mism static_assert(offsetof(TEB, GdiTebBatch) == 0x1FC, "GdiTebBatch offset mismatch"); static_assert(offsetof(TEB, DeallocationStack) == 0xE0C, "DeallocationStack offset mismatch"); static_assert(offsetof(TEB, TlsSlots) == 0xE10, "TLS slots offset mismatch"); + +typedef struct _MEMORY_BASIC_INFORMATION { + PVOID BaseAddress; + PVOID AllocationBase; + DWORD AllocationProtect; + SIZE_T RegionSize; + DWORD State; + DWORD Protect; + DWORD Type; +} MEMORY_BASIC_INFORMATION, *PMEMORY_BASIC_INFORMATION; diff --git a/tools/gen_trampolines.py b/tools/gen_trampolines.py index 8f4a1c0..97bb92e 100644 --- a/tools/gen_trampolines.py +++ b/tools/gen_trampolines.py @@ -30,6 +30,7 @@ from clang.cindex import ( CursorKind, Index, TranslationUnit, + Type, TypeKind, conf, ) @@ -97,7 +98,7 @@ class ArgInfo: slot_size: int primitive: bool sign_extended: bool - type_str: str + type: Type @dataclass @@ -108,6 +109,7 @@ class FuncInfo: source_cc: CallingConv target_cc: CallingConv variadic: bool + return_type: Type args: List[ArgInfo] = field(default_factory=list) @@ -117,7 +119,7 @@ class TypedefInfo: source_cc: CallingConv target_cc: CallingConv variadic: bool - return_type: str + return_type: Type args: List[ArgInfo] = field(default_factory=list) @@ -233,7 +235,7 @@ def _collect_args(func_type: CXType) -> List[ArgInfo]: slot_size=slot_size, primitive=is_primitive, sign_extended=is_sign_extended, - type_str=_type_to_string(t), + type=t, ) ) return args @@ -258,10 +260,11 @@ def collect_functions(tu: TranslationUnit, ns_filter: Optional[str]) -> List[Fun qualified_ns="::".join(ns_parts), name=name, mangled=node.mangled_name or name, - args=_collect_args(node.type), source_cc=source_cc, target_cc=_get_function_calling_conv(node.type), variadic=node.type.is_function_variadic(), + return_type=node.type.get_result(), + args=_collect_args(node.type), ) # Recurse into children @@ -519,8 +522,42 @@ def emit_header_mapping( # Guest-to-host thunk functions for f in funcs: + # Generate best-effort function prototype so that simple thunks can be called directly + # in special cases (e.g. thunk_entry_stubBase) + def _is_opaque(t: Type) -> bool: + if ( + t.kind == TypeKind.RECORD + or t.kind == TypeKind.ENUM + or t.kind == TypeKind.FUNCTIONPROTO + or t.kind == TypeKind.FUNCTIONNOPROTO + ): + return True + return t.kind == TypeKind.POINTER and _is_opaque( + t.get_pointee().get_canonical() + ) + + def _canonical_type_str(t: Type) -> str: + c = t.get_canonical() + if _is_opaque(c): + return "void *" + return c.spelling + thunk = f"thunk_{dll}_{f.name}" - lines.append(f"void {thunk}(void);") + args = [] + for i, arg in enumerate(f.args): + type_str = _canonical_type_str(arg.type) + args.append(f"{type_str} arg{i}") + param_list = ", ".join(args) + return_type = _canonical_type_str(f.return_type) + if f.source_cc == CallingConv.X86_STDCALL: + cc_attr = "__attribute__((stdcall))" + elif f.source_cc == CallingConv.C: + cc_attr = "__attribute__((cdecl))" + else: + raise NotImplementedError( + f"Unsupported calling convention {f.source_cc} for function {f.name}" + ) + lines.append(f"{cc_attr} {return_type} {thunk}({param_list});") # Host-to-guest thunk functions for td in typedefs: @@ -530,7 +567,8 @@ def emit_header_mapping( params = [f"{td.name} fn"] for i, arg in enumerate(td.args): - params.append(f"{arg.type_str} arg{i}") + type_str = _type_to_string(arg.type) + params.append(f"{type_str} arg{i}") param_list = ", ".join(params) lines.append(f"{td.return_type} {thunk}({param_list});")