mirror of
https://github.com/decompals/wibo.git
synced 2025-12-11 14:41:58 +00:00
Refactor heap and add dynamic expansion (increments of 64 MiB)
This commit is contained in:
@@ -89,8 +89,8 @@ set(MI_BUILD_TESTS OFF CACHE BOOL "Build test executables" FORCE)
|
|||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
mimalloc
|
mimalloc
|
||||||
GIT_REPOSITORY https://github.com/encounter/mimalloc.git
|
GIT_REPOSITORY https://github.com/microsoft/mimalloc.git
|
||||||
GIT_TAG 18d8537659d36ddeb320dfacc3b88fcd22a2607f # v3.1.5 + patch
|
GIT_TAG 69c5c5c402bf0414ff4a366697ecbd5c7578dc02 # dev3 (2025-11-05)
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(mimalloc)
|
FetchContent_MakeAvailable(mimalloc)
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <mimalloc.h>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
|
||||||
using kernel32::HeapObject;
|
using kernel32::HeapObject;
|
||||||
@@ -23,7 +23,7 @@ HeapObject *g_processHeapRecord = nullptr;
|
|||||||
|
|
||||||
void ensureProcessHeapInitialized() {
|
void ensureProcessHeapInitialized() {
|
||||||
std::call_once(g_processHeapInitFlag, []() {
|
std::call_once(g_processHeapInitFlag, []() {
|
||||||
auto record = make_pin<HeapObject>(nullptr);
|
auto record = make_pin<HeapObject>(std::nullopt);
|
||||||
if (!record) {
|
if (!record) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -41,10 +41,6 @@ LPVOID heapAllocFromRecord(HeapObject *record, DWORD dwFlags, SIZE_T dwBytes) {
|
|||||||
if (!record) {
|
if (!record) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
auto *heap = record->heap;
|
|
||||||
if (!heap && record->isProcessHeap) {
|
|
||||||
heap = wibo::heap::getGuestHeap();
|
|
||||||
}
|
|
||||||
if ((record->createFlags | dwFlags) & HEAP_GENERATE_EXCEPTIONS) {
|
if ((record->createFlags | dwFlags) & HEAP_GENERATE_EXCEPTIONS) {
|
||||||
DEBUG_LOG("HeapAlloc: HEAP_GENERATE_EXCEPTIONS not supported\n");
|
DEBUG_LOG("HeapAlloc: HEAP_GENERATE_EXCEPTIONS not supported\n");
|
||||||
kernel32::setLastError(ERROR_INVALID_PARAMETER);
|
kernel32::setLastError(ERROR_INVALID_PARAMETER);
|
||||||
@@ -52,7 +48,8 @@ LPVOID heapAllocFromRecord(HeapObject *record, DWORD dwFlags, SIZE_T dwBytes) {
|
|||||||
}
|
}
|
||||||
const bool zeroMemory = (dwFlags & HEAP_ZERO_MEMORY) != 0;
|
const bool zeroMemory = (dwFlags & HEAP_ZERO_MEMORY) != 0;
|
||||||
const SIZE_T requestSize = std::max<SIZE_T>(1, dwBytes);
|
const SIZE_T requestSize = std::max<SIZE_T>(1, dwBytes);
|
||||||
void *mem = zeroMemory ? mi_heap_zalloc(heap, requestSize) : mi_heap_malloc(heap, requestSize);
|
void *mem =
|
||||||
|
record->heap ? record->heap->malloc(requestSize, zeroMemory) : wibo::heap::guestMalloc(requestSize, zeroMemory);
|
||||||
if (!mem) {
|
if (!mem) {
|
||||||
kernel32::setLastError(ERROR_NOT_ENOUGH_MEMORY);
|
kernel32::setLastError(ERROR_NOT_ENOUGH_MEMORY);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -66,10 +63,6 @@ LPVOID heapAllocFromRecord(HeapObject *record, DWORD dwFlags, SIZE_T dwBytes) {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
HeapObject::~HeapObject() {
|
HeapObject::~HeapObject() {
|
||||||
if (heap) {
|
|
||||||
mi_heap_destroy(heap);
|
|
||||||
heap = nullptr;
|
|
||||||
}
|
|
||||||
if (isProcessHeap) {
|
if (isProcessHeap) {
|
||||||
g_processHeapHandle = NO_HANDLE;
|
g_processHeapHandle = NO_HANDLE;
|
||||||
g_processHeapRecord = nullptr;
|
g_processHeapRecord = nullptr;
|
||||||
@@ -86,13 +79,7 @@ HANDLE WINAPI HeapCreate(DWORD flOptions, SIZE_T dwInitialSize, SIZE_T dwMaximum
|
|||||||
return NO_HANDLE;
|
return NO_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
mi_heap_t *heap = wibo::heap::createGuestHeap();
|
auto record = make_pin<HeapObject>(wibo::Heap());
|
||||||
if (!heap) {
|
|
||||||
setLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
||||||
return NO_HANDLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto record = make_pin<HeapObject>(heap);
|
|
||||||
record->createFlags = flOptions;
|
record->createFlags = flOptions;
|
||||||
record->initialSize = dwInitialSize;
|
record->initialSize = dwInitialSize;
|
||||||
record->maximumSize = dwMaximumSize;
|
record->maximumSize = dwMaximumSize;
|
||||||
@@ -107,8 +94,7 @@ BOOL WINAPI HeapDestroy(HANDLE hHeap) {
|
|||||||
setLastError(ERROR_INVALID_HANDLE);
|
setLastError(ERROR_INVALID_HANDLE);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
mi_heap_destroy(record->heap);
|
record->heap.reset();
|
||||||
record->heap = nullptr;
|
|
||||||
wibo::handles().release(hHeap);
|
wibo::handles().release(hHeap);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -178,11 +164,6 @@ LPVOID WINAPI HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBy
|
|||||||
VERBOSE_LOG("-> %p (alloc)\n", alloc);
|
VERBOSE_LOG("-> %p (alloc)\n", alloc);
|
||||||
return alloc;
|
return alloc;
|
||||||
}
|
}
|
||||||
if (!mi_is_in_heap_region(lpMem)) {
|
|
||||||
VERBOSE_LOG("-> NULL (not owned)\n");
|
|
||||||
setLastError(ERROR_INVALID_PARAMETER);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
if ((record->createFlags | dwFlags) & HEAP_GENERATE_EXCEPTIONS) {
|
if ((record->createFlags | dwFlags) & HEAP_GENERATE_EXCEPTIONS) {
|
||||||
VERBOSE_LOG("-> NULL (exceptions unsupported)\n");
|
VERBOSE_LOG("-> NULL (exceptions unsupported)\n");
|
||||||
setLastError(ERROR_NOT_SUPPORTED);
|
setLastError(ERROR_NOT_SUPPORTED);
|
||||||
@@ -192,7 +173,7 @@ LPVOID WINAPI HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBy
|
|||||||
const bool zeroMemory = (dwFlags & HEAP_ZERO_MEMORY) != 0;
|
const bool zeroMemory = (dwFlags & HEAP_ZERO_MEMORY) != 0;
|
||||||
if (dwBytes == 0) {
|
if (dwBytes == 0) {
|
||||||
if (!inplaceOnly) {
|
if (!inplaceOnly) {
|
||||||
mi_free(lpMem);
|
wibo::heap::guestFree(lpMem);
|
||||||
VERBOSE_LOG("-> NULL (freed)\n");
|
VERBOSE_LOG("-> NULL (freed)\n");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -202,7 +183,7 @@ LPVOID WINAPI HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBy
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SIZE_T requestSize = std::max<SIZE_T>(1, dwBytes);
|
const SIZE_T requestSize = std::max<SIZE_T>(1, dwBytes);
|
||||||
const SIZE_T oldSize = mi_usable_size(lpMem);
|
const SIZE_T oldSize = wibo::heap::guestSize(lpMem);
|
||||||
if (inplaceOnly || requestSize <= oldSize) {
|
if (inplaceOnly || requestSize <= oldSize) {
|
||||||
if (requestSize > oldSize) {
|
if (requestSize > oldSize) {
|
||||||
VERBOSE_LOG("-> NULL (cannot grow in place)\n");
|
VERBOSE_LOG("-> NULL (cannot grow in place)\n");
|
||||||
@@ -213,22 +194,8 @@ LPVOID WINAPI HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBy
|
|||||||
return lpMem;
|
return lpMem;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *heap = record->heap;
|
void *ret = record->heap ? record->heap->realloc(lpMem, requestSize, zeroMemory)
|
||||||
if (!heap && record->isProcessHeap) {
|
: wibo::heap::guestRealloc(lpMem, requestSize, zeroMemory);
|
||||||
heap = wibo::heap::getGuestHeap();
|
|
||||||
}
|
|
||||||
void *ret = mi_heap_realloc(heap, lpMem, requestSize);
|
|
||||||
if (!ret) {
|
|
||||||
setLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
if (zeroMemory && requestSize > oldSize) {
|
|
||||||
size_t newUsable = mi_usable_size(ret);
|
|
||||||
if (newUsable > oldSize) {
|
|
||||||
size_t zeroLen = std::min<SIZE_T>(newUsable, requestSize) - oldSize;
|
|
||||||
std::memset(static_cast<char *>(ret) + oldSize, 0, zeroLen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isExecutableHeap(record.get())) {
|
if (isExecutableHeap(record.get())) {
|
||||||
tryMarkExecutable(ret);
|
tryMarkExecutable(ret);
|
||||||
}
|
}
|
||||||
@@ -251,13 +218,7 @@ SIZE_T WINAPI HeapSize(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem) {
|
|||||||
setLastError(ERROR_INVALID_PARAMETER);
|
setLastError(ERROR_INVALID_PARAMETER);
|
||||||
return static_cast<SIZE_T>(-1);
|
return static_cast<SIZE_T>(-1);
|
||||||
}
|
}
|
||||||
if (!mi_is_in_heap_region(lpMem)) {
|
return static_cast<SIZE_T>(wibo::heap::guestSize(lpMem));
|
||||||
VERBOSE_LOG("-> ERROR_INVALID_PARAMETER (not owned)\n");
|
|
||||||
setLastError(ERROR_INVALID_PARAMETER);
|
|
||||||
return static_cast<SIZE_T>(-1);
|
|
||||||
}
|
|
||||||
size_t size = mi_usable_size(lpMem);
|
|
||||||
return static_cast<SIZE_T>(size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL WINAPI HeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem) {
|
BOOL WINAPI HeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem) {
|
||||||
@@ -273,12 +234,12 @@ BOOL WINAPI HeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem) {
|
|||||||
setLastError(ERROR_INVALID_HANDLE);
|
setLastError(ERROR_INVALID_HANDLE);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
if (!mi_is_in_heap_region(lpMem)) {
|
bool ret = record->heap ? record->heap->free(lpMem) : wibo::heap::guestFree(lpMem);
|
||||||
|
if (!ret) {
|
||||||
VERBOSE_LOG("-> ERROR_INVALID_PARAMETER (not owned)\n");
|
VERBOSE_LOG("-> ERROR_INVALID_PARAMETER (not owned)\n");
|
||||||
setLastError(ERROR_INVALID_PARAMETER);
|
setLastError(ERROR_INVALID_PARAMETER);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
mi_free(lpMem);
|
|
||||||
VERBOSE_LOG("-> SUCCESS\n");
|
VERBOSE_LOG("-> SUCCESS\n");
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ HANDLE WINAPI HeapCreate(DWORD flOptions, SIZE_T dwInitialSize, SIZE_T dwMaximum
|
|||||||
BOOL WINAPI HeapDestroy(HANDLE hHeap);
|
BOOL WINAPI HeapDestroy(HANDLE hHeap);
|
||||||
HANDLE WINAPI GetProcessHeap();
|
HANDLE WINAPI GetProcessHeap();
|
||||||
BOOL WINAPI HeapSetInformation(HANDLE HeapHandle, HEAP_INFORMATION_CLASS HeapInformationClass, PVOID HeapInformation,
|
BOOL WINAPI HeapSetInformation(HANDLE HeapHandle, HEAP_INFORMATION_CLASS HeapInformationClass, PVOID HeapInformation,
|
||||||
SIZE_T HeapInformationLength);
|
SIZE_T HeapInformationLength);
|
||||||
LPVOID WINAPI HeapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes);
|
LPVOID WINAPI HeapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes);
|
||||||
LPVOID WINAPI HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBytes);
|
LPVOID WINAPI HeapReAlloc(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBytes);
|
||||||
SIZE_T WINAPI HeapSize(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem);
|
SIZE_T WINAPI HeapSize(HANDLE hHeap, DWORD dwFlags, LPCVOID lpMem);
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "handles.h"
|
#include "handles.h"
|
||||||
#include "mimalloc.h"
|
#include "heap.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <optional>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
|
||||||
namespace kernel32 {
|
namespace kernel32 {
|
||||||
@@ -153,7 +154,7 @@ struct SemaphoreObject final : WaitableObject {
|
|||||||
struct HeapObject : public ObjectBase {
|
struct HeapObject : public ObjectBase {
|
||||||
static constexpr ObjectType kType = ObjectType::Heap;
|
static constexpr ObjectType kType = ObjectType::Heap;
|
||||||
|
|
||||||
mi_heap_t *heap;
|
std::optional<wibo::Heap> heap;
|
||||||
const pthread_t owner;
|
const pthread_t owner;
|
||||||
DWORD createFlags = 0;
|
DWORD createFlags = 0;
|
||||||
SIZE_T initialSize = 0;
|
SIZE_T initialSize = 0;
|
||||||
@@ -161,11 +162,12 @@ struct HeapObject : public ObjectBase {
|
|||||||
DWORD compatibility = 0;
|
DWORD compatibility = 0;
|
||||||
bool isProcessHeap = false;
|
bool isProcessHeap = false;
|
||||||
|
|
||||||
explicit HeapObject(mi_heap_t *heap) : ObjectBase(kType), heap(heap), owner(pthread_self()) {}
|
explicit HeapObject(std::optional<wibo::Heap> heap)
|
||||||
|
: ObjectBase(kType), heap(std::move(heap)), owner(pthread_self()) {}
|
||||||
~HeapObject() override;
|
~HeapObject() override;
|
||||||
|
|
||||||
[[nodiscard]] inline bool isOwner() const { return pthread_equal(owner, pthread_self()); }
|
[[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.has_value()); }
|
||||||
};
|
};
|
||||||
|
|
||||||
inline constexpr HANDLE kPseudoCurrentProcessHandleValue = static_cast<HANDLE>(-1);
|
inline constexpr HANDLE kPseudoCurrentProcessHandleValue = static_cast<HANDLE>(-1);
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ GUEST_PTR WINAPI GetCommandLineA() {
|
|||||||
HOST_CONTEXT_GUARD();
|
HOST_CONTEXT_GUARD();
|
||||||
DEBUG_LOG("GetCommandLineA() -> %s\n", wibo::commandLine.c_str());
|
DEBUG_LOG("GetCommandLineA() -> %s\n", wibo::commandLine.c_str());
|
||||||
if (g_commandLineA == GUEST_NULL) {
|
if (g_commandLineA == GUEST_NULL) {
|
||||||
void *tmp = wibo::heap::guestCalloc(1, wibo::commandLine.size() + 1);
|
void *tmp = wibo::heap::guestMalloc(wibo::commandLine.size() + 1, true);
|
||||||
memcpy(tmp, wibo::commandLine.c_str(), wibo::commandLine.size());
|
memcpy(tmp, wibo::commandLine.c_str(), wibo::commandLine.size());
|
||||||
g_commandLineA = toGuestPtr(tmp);
|
g_commandLineA = toGuestPtr(tmp);
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ GUEST_PTR WINAPI GetCommandLineW() {
|
|||||||
HOST_CONTEXT_GUARD();
|
HOST_CONTEXT_GUARD();
|
||||||
DEBUG_LOG("GetCommandLineW() -> %s\n", wideStringToString(wibo::commandLineW.data()).c_str());
|
DEBUG_LOG("GetCommandLineW() -> %s\n", wideStringToString(wibo::commandLineW.data()).c_str());
|
||||||
if (g_commandLineW == GUEST_NULL) {
|
if (g_commandLineW == GUEST_NULL) {
|
||||||
void *tmp = wibo::heap::guestCalloc(1, wibo::commandLineW.size() * sizeof(WCHAR) + sizeof(WCHAR));
|
void *tmp = wibo::heap::guestMalloc(wibo::commandLineW.size() * sizeof(WCHAR) + sizeof(WCHAR), true);
|
||||||
memcpy(tmp, wibo::commandLineW.data(), wibo::commandLineW.size() * sizeof(WCHAR));
|
memcpy(tmp, wibo::commandLineW.data(), wibo::commandLineW.size() * sizeof(WCHAR));
|
||||||
g_commandLineW = toGuestPtr(tmp);
|
g_commandLineW = toGuestPtr(tmp);
|
||||||
}
|
}
|
||||||
@@ -85,9 +85,7 @@ BOOL WINAPI SetStdHandle(DWORD nStdHandle, HANDLE hHandle) {
|
|||||||
return files::setStdHandle(nStdHandle, hHandle);
|
return files::setStdHandle(nStdHandle, hHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
GUEST_PTR WINAPI GetEnvironmentStrings() {
|
GUEST_PTR WINAPI GetEnvironmentStrings() { return GetEnvironmentStringsA(); }
|
||||||
return GetEnvironmentStringsA();
|
|
||||||
}
|
|
||||||
|
|
||||||
GUEST_PTR WINAPI GetEnvironmentStringsA() {
|
GUEST_PTR WINAPI GetEnvironmentStringsA() {
|
||||||
HOST_CONTEXT_GUARD();
|
HOST_CONTEXT_GUARD();
|
||||||
|
|||||||
@@ -994,7 +994,7 @@ BOOL WINAPI InitializeCriticalSectionEx(LPCRITICAL_SECTION lpCriticalSection, DW
|
|||||||
lpCriticalSection->DebugInfo = static_cast<GUEST_PTR>(-1);
|
lpCriticalSection->DebugInfo = static_cast<GUEST_PTR>(-1);
|
||||||
} else {
|
} else {
|
||||||
auto *debugInfo = reinterpret_cast<RTL_CRITICAL_SECTION_DEBUG *>(
|
auto *debugInfo = reinterpret_cast<RTL_CRITICAL_SECTION_DEBUG *>(
|
||||||
wibo::heap::guestCalloc(1, sizeof(RTL_CRITICAL_SECTION_DEBUG)));
|
wibo::heap::guestMalloc(sizeof(RTL_CRITICAL_SECTION_DEBUG), true));
|
||||||
debugInfo->CriticalSection = toGuestPtr(lpCriticalSection);
|
debugInfo->CriticalSection = toGuestPtr(lpCriticalSection);
|
||||||
debugInfo->ProcessLocksList.Blink = toGuestPtr(&debugInfo->ProcessLocksList);
|
debugInfo->ProcessLocksList.Blink = toGuestPtr(&debugInfo->ProcessLocksList);
|
||||||
debugInfo->ProcessLocksList.Flink = toGuestPtr(&debugInfo->ProcessLocksList);
|
debugInfo->ProcessLocksList.Flink = toGuestPtr(&debugInfo->ProcessLocksList);
|
||||||
|
|||||||
@@ -167,82 +167,6 @@ ATOM addAtomByString(const std::string &value) {
|
|||||||
return newAtom;
|
return newAtom;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *doAlloc(UINT dwBytes, bool zero) {
|
|
||||||
if (dwBytes == 0) {
|
|
||||||
dwBytes = 1;
|
|
||||||
}
|
|
||||||
void *ret;
|
|
||||||
size_t size = static_cast<size_t>(dwBytes);
|
|
||||||
if (dwBytes > MI_ARENA_MAX_OBJ_SIZE) {
|
|
||||||
// If the size is too large, allocate memory using virtualAlloc
|
|
||||||
DEBUG_LOG("doAlloc(%u, %d) -> virtualAlloc\n", dwBytes, zero);
|
|
||||||
void *addr = nullptr;
|
|
||||||
const auto result = wibo::heap::virtualAlloc(&addr, &size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
|
||||||
if (result != wibo::heap::VmStatus::Success) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
ret = addr;
|
|
||||||
} else {
|
|
||||||
ret = mi_heap_malloc_aligned(wibo::heap::getGuestHeap(), dwBytes, 8);
|
|
||||||
size = mi_usable_size(ret);
|
|
||||||
}
|
|
||||||
if (ret && zero) {
|
|
||||||
std::memset(ret, 0, size);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void *doRealloc(void *mem, UINT dwBytes, bool zero) {
|
|
||||||
if (dwBytes == 0) {
|
|
||||||
dwBytes = 1;
|
|
||||||
}
|
|
||||||
size_t oldSize;
|
|
||||||
void *ret;
|
|
||||||
size_t newSize = static_cast<size_t>(dwBytes);
|
|
||||||
mi_heap_t *heap = wibo::heap::getGuestHeap();
|
|
||||||
if ((mem == nullptr && dwBytes <= MI_ARENA_MAX_OBJ_SIZE) || mi_is_in_heap_region(mem)) {
|
|
||||||
oldSize = mi_usable_size(mem);
|
|
||||||
ret = mi_heap_realloc_aligned(heap, mem, dwBytes, 8);
|
|
||||||
newSize = mi_usable_size(ret);
|
|
||||||
} else {
|
|
||||||
DEBUG_LOG("doRealloc(%u, %d, %d) -> virtualAlloc\n", dwBytes, zero, mem);
|
|
||||||
MEMORY_BASIC_INFORMATION info;
|
|
||||||
auto result = wibo::heap::virtualQuery(mem, &info);
|
|
||||||
if (result != wibo::heap::VmStatus::Success) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
oldSize = info.RegionSize;
|
|
||||||
result = wibo::heap::virtualAlloc(&ret, &newSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
|
||||||
if (result != wibo::heap::VmStatus::Success) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
std::memcpy(ret, mem, oldSize);
|
|
||||||
wibo::heap::virtualFree(mem, oldSize, MEM_RELEASE);
|
|
||||||
}
|
|
||||||
if (ret && zero && newSize > oldSize) {
|
|
||||||
std::memset(static_cast<char *>(ret) + oldSize, 0, newSize - oldSize);
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool doFree(void *mem) {
|
|
||||||
if (mem == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (mi_is_in_heap_region(mem)) {
|
|
||||||
mi_free(mem);
|
|
||||||
} else {
|
|
||||||
DEBUG_LOG("doFree(%p) -> virtualFree\n", mem);
|
|
||||||
MEMORY_BASIC_INFORMATION info;
|
|
||||||
auto result = wibo::heap::virtualQuery(mem, &info);
|
|
||||||
if (result != wibo::heap::VmStatus::Success || fromGuestPtr(info.BaseAddress) != mem) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
wibo::heap::virtualFree(mem, info.RegionSize, MEM_RELEASE);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool tryGetCurrentDirectoryPath(std::string &outPath) {
|
bool tryGetCurrentDirectoryPath(std::string &outPath) {
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
std::filesystem::path cwd = std::filesystem::current_path(ec);
|
std::filesystem::path cwd = std::filesystem::current_path(ec);
|
||||||
@@ -811,17 +735,21 @@ HGLOBAL WINAPI GlobalAlloc(UINT uFlags, SIZE_T dwBytes) {
|
|||||||
return NO_HANDLE;
|
return NO_HANDLE;
|
||||||
}
|
}
|
||||||
bool zero = (uFlags & GMEM_ZEROINIT) != 0;
|
bool zero = (uFlags & GMEM_ZEROINIT) != 0;
|
||||||
void *ret = doAlloc(static_cast<UINT>(dwBytes), zero);
|
void *ret = wibo::heap::guestMalloc(static_cast<UINT>(dwBytes), zero);
|
||||||
VERBOSE_LOG("-> %p\n", ret);
|
VERBOSE_LOG("-> %p\n", ret);
|
||||||
|
if (!ret) {
|
||||||
|
setLastError(ERROR_NOT_ENOUGH_MEMORY);
|
||||||
|
return GUEST_NULL;
|
||||||
|
}
|
||||||
return toGuestPtr(ret);
|
return toGuestPtr(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
HGLOBAL WINAPI GlobalFree(HGLOBAL hMem) {
|
HGLOBAL WINAPI GlobalFree(HGLOBAL hMem) {
|
||||||
HOST_CONTEXT_GUARD();
|
HOST_CONTEXT_GUARD();
|
||||||
VERBOSE_LOG("GlobalFree(%p)\n", hMem);
|
VERBOSE_LOG("GlobalFree(%p)\n", hMem);
|
||||||
if (doFree(reinterpret_cast<void *>(hMem))) {
|
if (wibo::heap::guestFree(reinterpret_cast<void *>(hMem))) {
|
||||||
VERBOSE_LOG("-> success\n");
|
VERBOSE_LOG("-> success\n");
|
||||||
return NO_HANDLE;
|
return GUEST_NULL;
|
||||||
} else {
|
} else {
|
||||||
VERBOSE_LOG("-> failure\n");
|
VERBOSE_LOG("-> failure\n");
|
||||||
return hMem;
|
return hMem;
|
||||||
@@ -833,11 +761,15 @@ HGLOBAL WINAPI GlobalReAlloc(HGLOBAL hMem, SIZE_T dwBytes, UINT uFlags) {
|
|||||||
VERBOSE_LOG("GlobalReAlloc(%p, %zu, %x)\n", hMem, static_cast<size_t>(dwBytes), uFlags);
|
VERBOSE_LOG("GlobalReAlloc(%p, %zu, %x)\n", hMem, static_cast<size_t>(dwBytes), uFlags);
|
||||||
if (uFlags & GMEM_MODIFY) {
|
if (uFlags & GMEM_MODIFY) {
|
||||||
assert(0);
|
assert(0);
|
||||||
return NO_HANDLE;
|
return GUEST_NULL;
|
||||||
}
|
}
|
||||||
bool zero = (uFlags & GMEM_ZEROINIT) != 0;
|
bool zero = (uFlags & GMEM_ZEROINIT) != 0;
|
||||||
void *ret = doRealloc(reinterpret_cast<void *>(hMem), static_cast<UINT>(dwBytes), zero);
|
void *ret = wibo::heap::guestRealloc(reinterpret_cast<void *>(hMem), static_cast<UINT>(dwBytes), zero);
|
||||||
VERBOSE_LOG("-> %p\n", ret);
|
VERBOSE_LOG("-> %p\n", ret);
|
||||||
|
if (!ret) {
|
||||||
|
setLastError(ERROR_NOT_ENOUGH_MEMORY);
|
||||||
|
return GUEST_NULL;
|
||||||
|
}
|
||||||
return toGuestPtr(ret);
|
return toGuestPtr(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -855,10 +787,10 @@ HLOCAL WINAPI LocalAlloc(UINT uFlags, SIZE_T uBytes) {
|
|||||||
if ((uFlags & LMEM_MOVEABLE) != 0) {
|
if ((uFlags & LMEM_MOVEABLE) != 0) {
|
||||||
VERBOSE_LOG(" ignoring LMEM_MOVEABLE\n");
|
VERBOSE_LOG(" ignoring LMEM_MOVEABLE\n");
|
||||||
}
|
}
|
||||||
void *result = doAlloc(static_cast<UINT>(uBytes), zero);
|
void *result = wibo::heap::guestMalloc(static_cast<UINT>(uBytes), zero);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
setLastError(ERROR_NOT_SUPPORTED);
|
setLastError(ERROR_NOT_ENOUGH_MEMORY);
|
||||||
return NO_HANDLE;
|
return GUEST_NULL;
|
||||||
}
|
}
|
||||||
// Legacy Windows applications (pre-NX and DEP) may expect executable memory from LocalAlloc.
|
// Legacy Windows applications (pre-NX and DEP) may expect executable memory from LocalAlloc.
|
||||||
tryMarkExecutable(result);
|
tryMarkExecutable(result);
|
||||||
@@ -869,11 +801,12 @@ HLOCAL WINAPI LocalAlloc(UINT uFlags, SIZE_T uBytes) {
|
|||||||
HLOCAL WINAPI LocalFree(HLOCAL hMem) {
|
HLOCAL WINAPI LocalFree(HLOCAL hMem) {
|
||||||
HOST_CONTEXT_GUARD();
|
HOST_CONTEXT_GUARD();
|
||||||
VERBOSE_LOG("LocalFree(%p)\n", hMem);
|
VERBOSE_LOG("LocalFree(%p)\n", hMem);
|
||||||
if (doFree(reinterpret_cast<void *>(hMem))) {
|
if (wibo::heap::guestFree(reinterpret_cast<void *>(hMem))) {
|
||||||
VERBOSE_LOG("-> success\n");
|
VERBOSE_LOG("-> success\n");
|
||||||
return NO_HANDLE;
|
return GUEST_NULL;
|
||||||
} else {
|
} else {
|
||||||
VERBOSE_LOG("-> failure\n");
|
VERBOSE_LOG("-> failure\n");
|
||||||
|
setLastError(ERROR_INVALID_HANDLE);
|
||||||
return hMem;
|
return hMem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -885,10 +818,10 @@ HLOCAL WINAPI LocalReAlloc(HLOCAL hMem, SIZE_T uBytes, UINT uFlags) {
|
|||||||
if ((uFlags & LMEM_MOVEABLE) != 0) {
|
if ((uFlags & LMEM_MOVEABLE) != 0) {
|
||||||
VERBOSE_LOG(" ignoring LMEM_MOVEABLE\n");
|
VERBOSE_LOG(" ignoring LMEM_MOVEABLE\n");
|
||||||
}
|
}
|
||||||
void *result = doRealloc(reinterpret_cast<void *>(hMem), static_cast<UINT>(uBytes), zero);
|
void *result = wibo::heap::guestRealloc(reinterpret_cast<void *>(hMem), static_cast<UINT>(uBytes), zero);
|
||||||
if (!result && uBytes != 0) {
|
if (!result && uBytes != 0) {
|
||||||
setLastError(ERROR_NOT_SUPPORTED);
|
setLastError(ERROR_NOT_ENOUGH_MEMORY);
|
||||||
return NO_HANDLE;
|
return GUEST_NULL;
|
||||||
}
|
}
|
||||||
// Legacy Windows applications (pre-NX and DEP) may expect executable memory from LocalReAlloc.
|
// Legacy Windows applications (pre-NX and DEP) may expect executable memory from LocalReAlloc.
|
||||||
tryMarkExecutable(result);
|
tryMarkExecutable(result);
|
||||||
|
|||||||
328
src/heap.cpp
328
src/heap.cpp
@@ -1,9 +1,11 @@
|
|||||||
#include "heap.h"
|
#include "heap.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "errors.h"
|
#include "errors.h"
|
||||||
|
#include "processes.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -26,7 +28,6 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <mimalloc.h>
|
#include <mimalloc.h>
|
||||||
#include <mimalloc/internal.h>
|
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr uintptr_t kLowMemoryStart = 0x00110000UL; // 1 MiB + 64 KiB
|
constexpr uintptr_t kLowMemoryStart = 0x00110000UL; // 1 MiB + 64 KiB
|
||||||
constexpr uintptr_t kHeapMax = 0x60000000UL; // 1 GiB
|
constexpr uintptr_t kHeapMax = 0x70000000UL;
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
// On macOS, our program is mapped at 0x7E001000
|
// On macOS, our program is mapped at 0x7E001000
|
||||||
constexpr uintptr_t kTopDownStart = 0x7D000000UL;
|
constexpr uintptr_t kTopDownStart = 0x7D000000UL;
|
||||||
@@ -46,21 +47,22 @@ constexpr uintptr_t kTwoGB = 0x7E000000UL;
|
|||||||
constexpr uintptr_t kTopDownStart = 0x7F000000UL; // Just below 2GB
|
constexpr uintptr_t kTopDownStart = 0x7F000000UL; // Just below 2GB
|
||||||
constexpr uintptr_t kTwoGB = 0x80000000UL;
|
constexpr uintptr_t kTwoGB = 0x80000000UL;
|
||||||
#endif
|
#endif
|
||||||
constexpr std::size_t kGuestArenaSize = 512ULL * 1024ULL * 1024ULL; // 512 MiB
|
constexpr std::size_t kGuestArenaSize = 64ULL * 1024ULL * 1024ULL; // 64 MiB
|
||||||
|
constexpr std::size_t kArenaMaxObjSize = 8ULL * 1024ULL * 1024ULL; // 8 MiB
|
||||||
constexpr std::size_t kVirtualAllocationGranularity = 64ULL * 1024ULL;
|
constexpr std::size_t kVirtualAllocationGranularity = 64ULL * 1024ULL;
|
||||||
|
|
||||||
struct ArenaRange {
|
struct Arena {
|
||||||
|
mi_arena_id_t arenaId = nullptr;
|
||||||
void *start = nullptr;
|
void *start = nullptr;
|
||||||
std::size_t size = 0;
|
size_t size = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Guest arena (<2GB)
|
std::recursive_mutex g_arenasMutex;
|
||||||
ArenaRange g_guest;
|
std::vector<Arena> g_arenas;
|
||||||
mi_arena_id_t g_guestArenaId = nullptr;
|
std::atomic_uint32_t g_heapTag(1);
|
||||||
thread_local mi_heap_t *g_guestHeap = nullptr;
|
|
||||||
|
|
||||||
bool g_initialized = false;
|
// Each thread gets its own set of mi_heap objects corresponding to each allocated arena
|
||||||
std::once_flag g_initOnce;
|
thread_local wibo::detail::HeapInternal g_guestHeap(0);
|
||||||
|
|
||||||
std::mutex g_mappingsMutex;
|
std::mutex g_mappingsMutex;
|
||||||
std::map<uintptr_t, MEMORY_BASIC_INFORMATION> *g_mappings = nullptr;
|
std::map<uintptr_t, MEMORY_BASIC_INFORMATION> *g_mappings = nullptr;
|
||||||
@@ -386,6 +388,24 @@ bool findFreeMappingLocked(std::size_t size, uintptr_t minAddr, uintptr_t maxAdd
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool foundTopCandidate = false;
|
||||||
|
uintptr_t bestCandidate = 0;
|
||||||
|
auto considerGap = [&](uintptr_t gapStart, uintptr_t gapEnd) -> bool {
|
||||||
|
uintptr_t candidate = 0;
|
||||||
|
if (!tryGap(gapStart, gapEnd, candidate)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!preferTop) {
|
||||||
|
*outAddr = candidate;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!foundTopCandidate || candidate > bestCandidate) {
|
||||||
|
bestCandidate = candidate;
|
||||||
|
foundTopCandidate = true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
uintptr_t cursor = alignUp(searchMin, granularity);
|
uintptr_t cursor = alignUp(searchMin, granularity);
|
||||||
for (auto &g_mapping : *g_mappings) {
|
for (auto &g_mapping : *g_mappings) {
|
||||||
uintptr_t mapStart = g_mapping.first;
|
uintptr_t mapStart = g_mapping.first;
|
||||||
@@ -395,7 +415,7 @@ bool findFreeMappingLocked(std::size_t size, uintptr_t minAddr, uintptr_t maxAdd
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (mapStart >= searchMax) {
|
if (mapStart >= searchMax) {
|
||||||
if (tryGap(cursor, searchMax, *outAddr)) {
|
if (considerGap(cursor, searchMax)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -403,7 +423,7 @@ bool findFreeMappingLocked(std::size_t size, uintptr_t minAddr, uintptr_t maxAdd
|
|||||||
|
|
||||||
if (mapStart > cursor) {
|
if (mapStart > cursor) {
|
||||||
uintptr_t gapEnd = std::min(mapStart, searchMax);
|
uintptr_t gapEnd = std::min(mapStart, searchMax);
|
||||||
if (tryGap(cursor, gapEnd, *outAddr)) {
|
if (considerGap(cursor, gapEnd)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -418,17 +438,20 @@ bool findFreeMappingLocked(std::size_t size, uintptr_t minAddr, uintptr_t maxAdd
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cursor < searchMax) {
|
if (cursor < searchMax) {
|
||||||
if (tryGap(cursor, searchMax, *outAddr)) {
|
if (considerGap(cursor, searchMax)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (foundTopCandidate) {
|
||||||
|
*outAddr = bestCandidate;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mapArena(std::size_t size, uintptr_t minAddr, uintptr_t maxAddr, bool preferTop, const char *name,
|
bool mapArena(std::size_t size, uintptr_t minAddr, uintptr_t maxAddr, bool preferTop, const char *name, Arena &out) {
|
||||||
ArenaRange &out) {
|
std::lock_guard lk(g_mappingsMutex);
|
||||||
std::lock_guard<std::mutex> guard(g_mappingsMutex);
|
|
||||||
const std::size_t ps = wibo::heap::systemPageSize();
|
const std::size_t ps = wibo::heap::systemPageSize();
|
||||||
size = (size + ps - 1) & ~(ps - 1);
|
size = (size + ps - 1) & ~(ps - 1);
|
||||||
uintptr_t cand = 0;
|
uintptr_t cand = 0;
|
||||||
@@ -444,46 +467,217 @@ bool mapArena(std::size_t size, uintptr_t minAddr, uintptr_t maxAddr, bool prefe
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void initializeImpl() {
|
bool createArenaLocked(size_t size) {
|
||||||
if (g_initialized) {
|
Arena arena;
|
||||||
return;
|
if (!mapArena(size, kLowMemoryStart, kHeapMax, true, "wibo heap arena", arena)) {
|
||||||
|
DEBUG_LOG("heap: failed to find free mapping for arena\n");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!mi_manage_os_memory_ex(arena.start, arena.size,
|
||||||
|
/*is_committed*/ false,
|
||||||
|
/*is_pinned*/ false,
|
||||||
|
/*is_zero*/ true,
|
||||||
|
/*numa_node*/ -1,
|
||||||
|
/*exclusive*/ true, &arena.arenaId)) {
|
||||||
|
DEBUG_LOG("heap: failed to create mi_arena\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DEBUG_LOG("heap: created arena %d at %p..%p (%zu MiB)\n", arena.arenaId, arena.start,
|
||||||
|
reinterpret_cast<uint8_t *>(arena.start) + arena.size, arena.size >> 20);
|
||||||
|
g_arenas.push_back(arena);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Map and register guest arena (below 2GB, exclusive)
|
mi_heap_t *heapForArena(std::vector<mi_heap_t *> &heaps, uint32_t arenaIdx, uint32_t heapTag) {
|
||||||
ArenaRange guest;
|
if (heaps.size() <= arenaIdx) {
|
||||||
if (mapArena(kGuestArenaSize, kLowMemoryStart, kHeapMax, true, "wibo guest arena", guest)) {
|
heaps.resize(arenaIdx + 1, nullptr);
|
||||||
bool ok = mi_manage_os_memory_ex(guest.start, guest.size,
|
}
|
||||||
/*is_committed*/ false,
|
if (heaps[arenaIdx] == nullptr) {
|
||||||
/*is_pinned*/ false,
|
mi_arena_id_t arenaId;
|
||||||
/*is_zero*/ true,
|
{
|
||||||
/*numa_node*/ -1,
|
std::lock_guard lk(g_arenasMutex);
|
||||||
/*exclusive*/ true, &g_guestArenaId);
|
if (arenaIdx >= g_arenas.size()) {
|
||||||
if (ok) {
|
return nullptr;
|
||||||
g_guest = guest;
|
}
|
||||||
} else {
|
arenaId = g_arenas[arenaIdx].arenaId;
|
||||||
LOG_ERR("heap: failed to register guest arena with mimalloc\n");
|
}
|
||||||
|
mi_heap_t *h = mi_heap_new_ex(static_cast<int>(heapTag), heapTag != 0, arenaId);
|
||||||
|
if (h == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
heaps[arenaIdx] = h;
|
||||||
|
}
|
||||||
|
return heaps[arenaIdx];
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename CallbackFn>
|
||||||
|
inline auto tryWithArena(wibo::detail::HeapInternal &internal, uint32_t arenaIdx, CallbackFn &&cb)
|
||||||
|
-> std::invoke_result_t<CallbackFn, mi_heap_t *, uint32_t> {
|
||||||
|
mi_heap_t *heap = heapForArena(internal.heaps, arenaIdx, internal.heapTag);
|
||||||
|
if (!heap) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return std::forward<CallbackFn>(cb)(heap, arenaIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename CallbackFn>
|
||||||
|
inline auto tryWithAnyArena(wibo::detail::HeapInternal &internal, CallbackFn &&cb)
|
||||||
|
-> std::invoke_result_t<CallbackFn, mi_heap_t *, uint32_t> {
|
||||||
|
using R = std::invoke_result_t<CallbackFn, mi_heap_t *, uint32_t>;
|
||||||
|
R ret = tryWithArena(internal, internal.arenaHint, cb);
|
||||||
|
if (ret) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
// Loop without locking (arenas won't be removed)
|
||||||
|
uint32_t numArenas = static_cast<uint32_t>(g_arenas.size());
|
||||||
|
for (uint32_t i = 0; i < numArenas; ++i) {
|
||||||
|
if (i == internal.arenaHint) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ret = tryWithArena(internal, i, cb);
|
||||||
|
if (ret) {
|
||||||
|
internal.arenaHint = i;
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (g_guest.size) {
|
std::lock_guard lk(g_arenasMutex);
|
||||||
DEBUG_LOG("heap: initialized guest arena %p..%p (%zu MiB) id=%p\n", g_guest.start,
|
// Was a new arena created while we were looping?
|
||||||
static_cast<void *>(static_cast<char *>(g_guest.start) + g_guest.size), g_guest.size >> 20,
|
for (uint32_t i = numArenas; i < g_arenas.size(); ++i) {
|
||||||
g_guestArenaId);
|
ret = tryWithArena(internal, i, cb);
|
||||||
} else {
|
if (ret) {
|
||||||
DEBUG_LOG("heap: guest arena initialization incomplete\n");
|
internal.arenaHint = i;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
DEBUG_LOG("heap: no arena available, creating new arena\n");
|
||||||
|
if (createArenaLocked(kGuestArenaSize)) {
|
||||||
|
uint32_t newArenaIdx = static_cast<uint32_t>(g_arenas.size() - 1);
|
||||||
|
ret = tryWithArena(internal, newArenaIdx, cb);
|
||||||
|
if (ret) {
|
||||||
|
internal.arenaHint = newArenaIdx;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
g_initialized = true;
|
void *doAlloc(wibo::detail::HeapInternal &internal, size_t size, bool zero) {
|
||||||
|
if (size >= kArenaMaxObjSize) {
|
||||||
|
DEBUG_LOG("heap: large malloc %zu bytes, using virtualAlloc\n", size);
|
||||||
|
void *addr = nullptr;
|
||||||
|
const auto result = wibo::heap::virtualAlloc(&addr, &size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||||
|
if (result != wibo::heap::VmStatus::Success) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
return tryWithAnyArena(internal, [size, zero](mi_heap_t *heap, uint32_t) {
|
||||||
|
return (zero ? mi_heap_zalloc_aligned : mi_heap_malloc_aligned)(heap, size, 8);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void *doRealloc(wibo::detail::HeapInternal &internal, void *ptr, size_t newSize, bool zero) {
|
||||||
|
bool isInHeap = mi_is_in_heap_region(ptr);
|
||||||
|
if (newSize >= kArenaMaxObjSize || !isInHeap) {
|
||||||
|
DEBUG_LOG("heap: large realloc %zu bytes, using virtualAlloc\n", newSize);
|
||||||
|
size_t oldSize;
|
||||||
|
if (isInHeap) {
|
||||||
|
oldSize = mi_usable_size(ptr);
|
||||||
|
} else {
|
||||||
|
// Get size from virtualQuery
|
||||||
|
MEMORY_BASIC_INFORMATION info;
|
||||||
|
auto result = wibo::heap::virtualQuery(ptr, &info);
|
||||||
|
if (result != wibo::heap::VmStatus::Success) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
oldSize = info.RegionSize;
|
||||||
|
}
|
||||||
|
void *ret = nullptr;
|
||||||
|
if (newSize >= kArenaMaxObjSize) {
|
||||||
|
auto result = wibo::heap::virtualAlloc(&ret, &newSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||||
|
if (result != wibo::heap::VmStatus::Success) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret = doAlloc(internal, newSize, zero);
|
||||||
|
}
|
||||||
|
if (!ret) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
std::memcpy(ret, ptr, std::min(oldSize, newSize));
|
||||||
|
if (isInHeap) {
|
||||||
|
mi_free(ptr);
|
||||||
|
} else {
|
||||||
|
auto result = wibo::heap::virtualFree(ptr, 0, MEM_RELEASE);
|
||||||
|
if (result != wibo::heap::VmStatus::Success) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
return tryWithAnyArena(internal, [ptr, newSize, zero](mi_heap_t *heap, uint32_t) {
|
||||||
|
return (zero ? mi_heap_rezalloc_aligned : mi_heap_realloc_aligned)(heap, ptr, newSize, 8);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool doFree(void *ptr) {
|
||||||
|
if (ptr == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (mi_is_in_heap_region(ptr)) {
|
||||||
|
mi_free(ptr);
|
||||||
|
} else {
|
||||||
|
DEBUG_LOG("heap: free(%p) -> virtualFree\n", ptr);
|
||||||
|
auto result = wibo::heap::virtualFree(ptr, 0, MEM_RELEASE);
|
||||||
|
if (result != wibo::heap::VmStatus::Success) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
namespace wibo::heap {
|
namespace wibo {
|
||||||
|
|
||||||
bool initialize() {
|
Heap::Heap() : threadId(getThreadId()), internal(g_heapTag++) {}
|
||||||
std::call_once(g_initOnce, initializeImpl);
|
|
||||||
return g_initialized;
|
Heap::~Heap() {
|
||||||
|
if (getThreadId() != threadId) {
|
||||||
|
DEBUG_LOG("heap: ~Heap() failed; heap owned by another thread\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (mi_heap_t *h : internal.heaps) {
|
||||||
|
if (h) {
|
||||||
|
mi_heap_destroy(h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
internal.heaps.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void *Heap::malloc(size_t size, bool zero) {
|
||||||
|
if (getThreadId() != threadId) {
|
||||||
|
DEBUG_LOG("heap: malloc(%zu) failed; heap owned by another thread\n", size);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return doAlloc(internal, size, zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *Heap::realloc(void *ptr, size_t newSize, bool zero) {
|
||||||
|
if (getThreadId() != threadId) {
|
||||||
|
DEBUG_LOG("heap: realloc(%p, %zu) failed; heap owned by another thread\n", ptr, newSize);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return doRealloc(internal, ptr, newSize, zero);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
|
||||||
|
bool Heap::free(void *ptr) { return doFree(ptr); }
|
||||||
|
|
||||||
|
}; // namespace wibo
|
||||||
|
|
||||||
|
namespace wibo::heap {
|
||||||
|
|
||||||
uintptr_t systemPageSize() {
|
uintptr_t systemPageSize() {
|
||||||
static uintptr_t cached = []() {
|
static uintptr_t cached = []() {
|
||||||
long detected = sysconf(_SC_PAGESIZE);
|
long detected = sysconf(_SC_PAGESIZE);
|
||||||
@@ -495,34 +689,25 @@ uintptr_t systemPageSize() {
|
|||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
mi_heap_t *getGuestHeap() {
|
void *guestMalloc(std::size_t size, bool zero) { return doAlloc(g_guestHeap, size, zero); }
|
||||||
initialize();
|
|
||||||
if (g_guestHeap == nullptr) {
|
|
||||||
g_guestHeap = createGuestHeap();
|
|
||||||
}
|
|
||||||
return g_guestHeap;
|
|
||||||
}
|
|
||||||
|
|
||||||
mi_heap_t *createGuestHeap() {
|
void *guestRealloc(void *ptr, std::size_t newSize, bool zero) { return doRealloc(g_guestHeap, ptr, newSize, zero); }
|
||||||
initialize();
|
|
||||||
if (g_guestArenaId != nullptr) {
|
bool guestFree(void *ptr) { return doFree(ptr); }
|
||||||
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);
|
size_t guestSize(const void *ptr) {
|
||||||
return h;
|
if (mi_is_in_heap_region(ptr)) {
|
||||||
|
return mi_usable_size(ptr);
|
||||||
|
} else {
|
||||||
|
MEMORY_BASIC_INFORMATION info;
|
||||||
|
auto result = wibo::heap::virtualQuery(ptr, &info);
|
||||||
|
if (result != wibo::heap::VmStatus::Success) {
|
||||||
|
return SIZE_MAX;
|
||||||
}
|
}
|
||||||
|
return info.RegionSize;
|
||||||
}
|
}
|
||||||
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; }
|
uintptr_t allocationGranularity() { return kVirtualAllocationGranularity; }
|
||||||
|
|
||||||
DWORD win32ErrorFromVmStatus(VmStatus status) {
|
DWORD win32ErrorFromVmStatus(VmStatus status) {
|
||||||
@@ -1041,8 +1226,8 @@ bool reserveGuestStack(std::size_t stackSizeBytes, void **outStackLimit, void **
|
|||||||
const std::size_t ps = systemPageSize();
|
const std::size_t ps = systemPageSize();
|
||||||
std::size_t total = ((stackSizeBytes + (ps * 2) - 1) & ~(ps - 1));
|
std::size_t total = ((stackSizeBytes + (ps * 2) - 1) & ~(ps - 1));
|
||||||
|
|
||||||
ArenaRange r;
|
Arena r;
|
||||||
if (!mapArena(total, kTopDownStart, kTwoGB, true, "wibo guest stack", r)) {
|
if (!mapArena(total, kLowMemoryStart, kTwoGB, true, "wibo guest stack", r)) {
|
||||||
DEBUG_LOG("heap: reserveGuestStack: failed to map region\n");
|
DEBUG_LOG("heap: reserveGuestStack: failed to map region\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1174,8 +1359,8 @@ static size_t blockLower2GB(MEMORY_BASIC_INFORMATION mappings[MAX_NUM_MAPPINGS])
|
|||||||
if (mapStart >= kTwoGB) {
|
if (mapStart >= kTwoGB) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (mapStart + mapEnd > kTwoGB) {
|
if (mapEnd > kTwoGB) {
|
||||||
mapEnd = kTwoGB - mapStart;
|
mapEnd = kTwoGB;
|
||||||
}
|
}
|
||||||
if (mapStart == mapEnd || mapStart > mapEnd) {
|
if (mapStart == mapEnd || mapStart > mapEnd) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1190,7 +1375,6 @@ static size_t blockLower2GB(MEMORY_BASIC_INFORMATION mappings[MAX_NUM_MAPPINGS])
|
|||||||
// Extend the previous mapping
|
// Extend the previous mapping
|
||||||
prevMapping.RegionSize = mapEnd - prevMapStart;
|
prevMapping.RegionSize = mapEnd - prevMapStart;
|
||||||
lastMapEnd = mapEnd;
|
lastMapEnd = mapEnd;
|
||||||
procLine = procLine.substr(newline + 1);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
50
src/heap.h
50
src/heap.h
@@ -7,17 +7,53 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
struct mi_heap_s;
|
struct mi_heap_s;
|
||||||
typedef struct mi_heap_s mi_heap_t;
|
typedef struct mi_heap_s mi_heap_t;
|
||||||
|
|
||||||
|
namespace wibo {
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
struct HeapInternal {
|
||||||
|
uint32_t heapTag;
|
||||||
|
// Previously used arena
|
||||||
|
uint32_t arenaHint = 0;
|
||||||
|
// Thread-local mi_heap objects per arena
|
||||||
|
std::vector<mi_heap_t *> heaps;
|
||||||
|
|
||||||
|
explicit HeapInternal(uint32_t heapTag) : heapTag(heapTag) {}
|
||||||
|
~HeapInternal() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
}; // namespace detail
|
||||||
|
|
||||||
|
class Heap {
|
||||||
|
public:
|
||||||
|
Heap();
|
||||||
|
~Heap();
|
||||||
|
|
||||||
|
Heap(const Heap &) = delete;
|
||||||
|
Heap &operator=(const Heap &) = delete;
|
||||||
|
Heap(Heap &&) noexcept = default;
|
||||||
|
Heap &operator=(Heap &&) noexcept = default;
|
||||||
|
|
||||||
|
void *malloc(size_t size, bool zero = false);
|
||||||
|
void *realloc(void *ptr, size_t newSize, bool zero = false);
|
||||||
|
bool free(void *ptr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t threadId;
|
||||||
|
detail::HeapInternal internal;
|
||||||
|
};
|
||||||
|
|
||||||
|
}; // namespace wibo
|
||||||
|
|
||||||
namespace wibo::heap {
|
namespace wibo::heap {
|
||||||
|
|
||||||
bool initialize();
|
|
||||||
uintptr_t systemPageSize();
|
uintptr_t systemPageSize();
|
||||||
uintptr_t allocationGranularity();
|
uintptr_t allocationGranularity();
|
||||||
mi_heap_t *getGuestHeap();
|
|
||||||
mi_heap_t *createGuestHeap();
|
|
||||||
|
|
||||||
enum class VmStatus : uint32_t {
|
enum class VmStatus : uint32_t {
|
||||||
Success = 0,
|
Success = 0,
|
||||||
@@ -31,10 +67,10 @@ enum class VmStatus : uint32_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Guest heap memory allocation helpers
|
// Guest heap memory allocation helpers
|
||||||
void *guestMalloc(std::size_t size);
|
void *guestMalloc(std::size_t size, bool zero = false);
|
||||||
void *guestCalloc(std::size_t count, std::size_t size);
|
void *guestRealloc(void *ptr, std::size_t newSize, bool zero = false);
|
||||||
void *guestRealloc(void *ptr, std::size_t newSize);
|
bool guestFree(void *ptr);
|
||||||
void guestFree(void *ptr);
|
size_t guestSize(const void *ptr);
|
||||||
|
|
||||||
VmStatus virtualAlloc(void **baseAddress, std::size_t *regionSize, DWORD allocationType, DWORD protect,
|
VmStatus virtualAlloc(void **baseAddress, std::size_t *regionSize, DWORD allocationType, DWORD protect,
|
||||||
DWORD type = MEM_PRIVATE);
|
DWORD type = MEM_PRIVATE);
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ void wibo::debug_log(const char *fmt, ...) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEB *wibo::allocateTib() {
|
TEB *wibo::allocateTib() {
|
||||||
auto *newTib = static_cast<TEB *>(wibo::heap::guestCalloc(1, sizeof(TEB)));
|
auto *newTib = static_cast<TEB *>(wibo::heap::guestMalloc(sizeof(TEB), true));
|
||||||
if (!newTib) {
|
if (!newTib) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -317,11 +317,11 @@ int main(int argc, char **argv) {
|
|||||||
files::init();
|
files::init();
|
||||||
|
|
||||||
// Create PEB
|
// Create PEB
|
||||||
PEB *peb = reinterpret_cast<PEB *>(wibo::heap::guestCalloc(1, sizeof(PEB)));
|
PEB *peb = reinterpret_cast<PEB *>(wibo::heap::guestMalloc(sizeof(PEB), true));
|
||||||
peb->ProcessParameters = toGuestPtr(wibo::heap::guestCalloc(1, sizeof(RTL_USER_PROCESS_PARAMETERS)));
|
peb->ProcessParameters = toGuestPtr(wibo::heap::guestMalloc(sizeof(RTL_USER_PROCESS_PARAMETERS), true));
|
||||||
|
|
||||||
// Create TIB
|
// Create TIB
|
||||||
TEB *tib = reinterpret_cast<TEB *>(wibo::heap::guestCalloc(1, sizeof(TEB)));
|
TEB *tib = reinterpret_cast<TEB *>(wibo::heap::guestMalloc(sizeof(TEB), true));
|
||||||
wibo::tls::initializeTib(tib);
|
wibo::tls::initializeTib(tib);
|
||||||
tib->Tib.Self = toGuestPtr(tib);
|
tib->Tib.Self = toGuestPtr(tib);
|
||||||
tib->Peb = toGuestPtr(peb);
|
tib->Peb = toGuestPtr(peb);
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ TlsArray *allocateTlsArray(size_t capacity) {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
const size_t bytes = sizeof(TlsArray) + capacity * sizeof(GUEST_PTR);
|
const size_t bytes = sizeof(TlsArray) + capacity * sizeof(GUEST_PTR);
|
||||||
auto *arr = static_cast<TlsArray *>(wibo::heap::guestCalloc(1, bytes));
|
auto *arr = static_cast<TlsArray *>(wibo::heap::guestMalloc(bytes, true));
|
||||||
if (!arr) {
|
if (!arr) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
127
test/test_heap.c
127
test/test_heap.c
@@ -1,60 +1,109 @@
|
|||||||
#include <windows.h>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
#include "test_assert.h"
|
#include "test_assert.h"
|
||||||
|
|
||||||
int main(void) {
|
const SIZE_T LARGE_BLOCK_SIZE = 64ULL * 1024ULL * 1024ULL; // 64 MiB
|
||||||
HANDLE processHeap = GetProcessHeap();
|
const SIZE_T SMALL_BLOCK_SIZE = 64ULL * 1024ULL; // 64 KiB
|
||||||
TEST_CHECK(processHeap != NULL);
|
|
||||||
|
|
||||||
uint8_t *block = (uint8_t *)HeapAlloc(processHeap, HEAP_ZERO_MEMORY, 32);
|
static void test_basics() {
|
||||||
TEST_CHECK(block != NULL);
|
HANDLE processHeap = GetProcessHeap();
|
||||||
for (size_t i = 0; i < 32; i++) {
|
TEST_CHECK(processHeap != NULL);
|
||||||
TEST_CHECK(block[i] == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
SIZE_T blockSize = HeapSize(processHeap, 0, block);
|
uint8_t *block = (uint8_t *)HeapAlloc(processHeap, HEAP_ZERO_MEMORY, 32);
|
||||||
TEST_CHECK(blockSize >= 32);
|
TEST_CHECK(block != NULL);
|
||||||
|
for (size_t i = 0; i < 32; i++) {
|
||||||
|
TEST_CHECK(block[i] == 0);
|
||||||
|
}
|
||||||
|
|
||||||
memset(block, 0xAA, 16);
|
SIZE_T blockSize = HeapSize(processHeap, 0, block);
|
||||||
uint8_t *grown = (uint8_t *)HeapReAlloc(processHeap, HEAP_ZERO_MEMORY, block, 64);
|
TEST_CHECK(blockSize >= 32);
|
||||||
TEST_CHECK(grown != NULL);
|
|
||||||
for (size_t i = 0; i < 16; i++) {
|
|
||||||
TEST_CHECK(grown[i] == 0xAA);
|
|
||||||
}
|
|
||||||
for (size_t i = 16; i < 64; i++) {
|
|
||||||
TEST_CHECK(grown[i] == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
SetLastError(0);
|
memset(block, 0xAA, 16);
|
||||||
void *inPlace = HeapReAlloc(processHeap, HEAP_REALLOC_IN_PLACE_ONLY, grown, 2048);
|
uint8_t *grown = (uint8_t *)HeapReAlloc(processHeap, HEAP_ZERO_MEMORY, block, 64);
|
||||||
TEST_CHECK(inPlace == NULL);
|
TEST_CHECK(grown != NULL);
|
||||||
TEST_CHECK_EQ(ERROR_NOT_ENOUGH_MEMORY, GetLastError());
|
for (size_t i = 0; i < 16; i++) {
|
||||||
|
TEST_CHECK(grown[i] == 0xAA);
|
||||||
|
}
|
||||||
|
for (size_t i = 16; i < 64; i++) {
|
||||||
|
TEST_CHECK(grown[i] == 0);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CHECK(HeapFree(processHeap, 0, grown));
|
SetLastError(0);
|
||||||
|
void *inPlace = HeapReAlloc(processHeap, HEAP_REALLOC_IN_PLACE_ONLY, grown, 2048);
|
||||||
|
TEST_CHECK(inPlace == NULL);
|
||||||
|
TEST_CHECK_EQ(ERROR_NOT_ENOUGH_MEMORY, GetLastError());
|
||||||
|
|
||||||
HANDLE privateHeap = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
|
TEST_CHECK(HeapFree(processHeap, 0, grown));
|
||||||
TEST_CHECK(privateHeap != NULL);
|
|
||||||
|
|
||||||
void *privateBlock = HeapAlloc(privateHeap, 0, 8);
|
HANDLE privateHeap = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
|
||||||
TEST_CHECK(privateBlock != NULL);
|
TEST_CHECK(privateHeap != NULL);
|
||||||
|
|
||||||
SetLastError(0);
|
void *privateBlock = HeapAlloc(privateHeap, 0, 8);
|
||||||
// Disabled temporarily; no good way to detect individual heap allocations
|
TEST_CHECK(privateBlock != NULL);
|
||||||
// in mimalloc currently. See https://github.com/microsoft/mimalloc/issues/298
|
|
||||||
|
SetLastError(0);
|
||||||
|
// Disabled temporarily; no good way to detect individual heap allocations
|
||||||
|
// in mimalloc currently. See https://github.com/microsoft/mimalloc/issues/298
|
||||||
#if 0
|
#if 0
|
||||||
TEST_CHECK(!HeapFree(processHeap, 0, privateBlock));
|
TEST_CHECK(!HeapFree(processHeap, 0, privateBlock));
|
||||||
TEST_CHECK_EQ(ERROR_INVALID_PARAMETER, GetLastError());
|
TEST_CHECK_EQ(ERROR_INVALID_PARAMETER, GetLastError());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
TEST_CHECK(HeapFree(privateHeap, 0, privateBlock));
|
TEST_CHECK(HeapFree(privateHeap, 0, privateBlock));
|
||||||
TEST_CHECK(HeapDestroy(privateHeap));
|
TEST_CHECK(HeapDestroy(privateHeap));
|
||||||
|
|
||||||
SetLastError(0);
|
SetLastError(0);
|
||||||
TEST_CHECK(!HeapDestroy(processHeap));
|
TEST_CHECK(!HeapDestroy(processHeap));
|
||||||
TEST_CHECK_EQ(ERROR_INVALID_HANDLE, GetLastError());
|
TEST_CHECK_EQ(ERROR_INVALID_HANDLE, GetLastError());
|
||||||
|
}
|
||||||
return EXIT_SUCCESS;
|
|
||||||
|
static void test_large_alloc() {
|
||||||
|
HANDLE heap = HeapCreate(0, 0, 0);
|
||||||
|
TEST_CHECK(heap != NULL);
|
||||||
|
|
||||||
|
// Test allocating a large block
|
||||||
|
void *largeBlock = HeapAlloc(heap, 0, LARGE_BLOCK_SIZE);
|
||||||
|
*(uint32_t *)largeBlock = 0x12345678;
|
||||||
|
TEST_CHECK(largeBlock != NULL);
|
||||||
|
SIZE_T blockSize = HeapSize(heap, 0, largeBlock);
|
||||||
|
TEST_CHECK(blockSize >= LARGE_BLOCK_SIZE);
|
||||||
|
|
||||||
|
// Test reallocating a large block to a smaller size
|
||||||
|
void *smallBlock = HeapReAlloc(heap, 0, largeBlock, SMALL_BLOCK_SIZE);
|
||||||
|
TEST_CHECK(smallBlock != NULL);
|
||||||
|
TEST_CHECK(*(uint32_t *)smallBlock == 0x12345678);
|
||||||
|
blockSize = HeapSize(heap, 0, smallBlock);
|
||||||
|
TEST_CHECK(blockSize >= SMALL_BLOCK_SIZE);
|
||||||
|
|
||||||
|
// Test reallocating a small block to a larger size
|
||||||
|
largeBlock = HeapReAlloc(heap, 0, smallBlock, LARGE_BLOCK_SIZE);
|
||||||
|
TEST_CHECK(largeBlock != NULL);
|
||||||
|
TEST_CHECK(*(uint32_t *)largeBlock == 0x12345678);
|
||||||
|
TEST_CHECK(HeapFree(heap, 0, largeBlock));
|
||||||
|
|
||||||
|
TEST_CHECK(HeapDestroy(heap));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_heap_expansion() {
|
||||||
|
HANDLE heap = HeapCreate(0, 0, 0);
|
||||||
|
TEST_CHECK(heap != NULL);
|
||||||
|
|
||||||
|
// Test allocating a total of 768 MiB
|
||||||
|
const SIZE_T TOTAL_SIZE = 768ULL * 1024ULL * 1024ULL;
|
||||||
|
for (int i = 0; i < (int)(TOTAL_SIZE / SMALL_BLOCK_SIZE); i++) {
|
||||||
|
void *block = HeapAlloc(heap, 0, SMALL_BLOCK_SIZE);
|
||||||
|
TEST_CHECK(block != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CHECK(HeapDestroy(heap));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
test_basics();
|
||||||
|
test_large_alloc();
|
||||||
|
test_heap_expansion();
|
||||||
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user