#include "prx/libc/include/ApplicationHeap.hpp" #include "prx/libc/include/GuestHeap.hpp" #include "prx/libc/include/general/VabiMacros.hpp" #include #include #include #include #include #include #include #include #include #include namespace { using Allocate = void* (APS5_VABI *)(std::size_t); using Free = void (APS5_VABI *)(void*); using Reallocate = void* (APS5_VABI *)(void*, std::size_t); using Calloc = void* (APS5_VABI *)(std::size_t, std::size_t); using Align = void* (APS5_VABI *)(std::size_t, std::size_t); using Realign = void* (APS5_VABI *)(void*, std::size_t, std::size_t); using PosixAlign = int (APS5_VABI *)(void**, std::size_t, std::size_t); using Initialize = void (APS5_VABI *)(); using Stats = int (APS5_VABI *)(void*); using UsableSize = std::size_t (APS5_VABI *)(void*); std::mutex heapMutex; std::array heapApi{}; std::once_flag heapInitialization; std::exception_ptr heapFailure; Initialize heapFinalize = nullptr; bool heapFinalized = false; thread_local bool heapCallbackActive = false; void* APS5_VABI defaultAllocate(std::size_t bytes) { return GuestHeap::GuestHeapAllocate_nid_postfix(bytes); } void APS5_VABI defaultFree(void* pointer) { GuestHeap::GuestHeapFree_nid_postfix(pointer); } void* APS5_VABI defaultReallocate(void* pointer, std::size_t bytes) { return GuestHeap::GuestHeapReallocate_nid_postfix(pointer, bytes); } void* APS5_VABI defaultCalloc(std::size_t count, std::size_t bytes) { if (bytes != 0 && count > std::numeric_limits::max() / bytes) throw std::bad_alloc(); auto* pointer = defaultAllocate(count * bytes); std::memset(pointer, 0, count * bytes); return pointer; } void* APS5_VABI defaultAlign(std::size_t alignment, std::size_t bytes) { return GuestHeap::GuestHeapAlign_nid_postfix(alignment, bytes); } void* APS5_VABI defaultRealign(void* pointer, std::size_t bytes, std::size_t alignment) { return GuestHeap::GuestHeapRealign_nid_postfix(pointer, bytes, alignment); } int APS5_VABI defaultPosixAlign(void** pointer, std::size_t alignment, std::size_t bytes) { if (pointer == nullptr || alignment < sizeof(void*) || (alignment & (alignment - 1)) != 0) return 22; try { *pointer = defaultAlign(alignment, bytes); return 0; } catch (const std::bad_alloc&) { return 12; } catch (const std::length_error&) { return 12; } } std::array defaultApi() { return {reinterpret_cast(defaultAllocate), reinterpret_cast(defaultFree), reinterpret_cast(defaultCalloc), reinterpret_cast(defaultReallocate), reinterpret_cast(defaultAlign), nullptr, reinterpret_cast(defaultPosixAlign)}; } class CallbackScope { public: CallbackScope() { if (heapCallbackActive) throw std::runtime_error("application heap: recursive libc allocator callback"); heapCallbackActive = true; } ~CallbackScope() { heapCallbackActive = false; } CallbackScope(const CallbackScope&) = delete; CallbackScope& operator=(const CallbackScope&) = delete; }; template TValue read(const void* pointer, std::size_t offset) { if (pointer == nullptr) throw std::invalid_argument("application heap: null metadata"); TValue value; std::memcpy(&value, static_cast(pointer) + offset, sizeof(value)); return value; } template TCallback callback(std::size_t index) { std::lock_guard lock(heapMutex); if (heapFailure) std::rethrow_exception(heapFailure); if (heapFinalized) throw std::runtime_error("application heap: allocator has been finalized"); if (heapApi[index] == nullptr) throw std::runtime_error("application heap: allocator API is not registered"); static_assert(sizeof(TCallback) == sizeof(void*)); TCallback result; std::memcpy(&result, &heapApi[index], sizeof(result)); return result; } void* requireAllocation(void* pointer) { if (pointer == nullptr) throw std::bad_alloc(); return pointer; } void requireAlignment(std::size_t alignment) { if (alignment == 0 || (alignment & (alignment - 1)) != 0) throw std::invalid_argument("application heap: invalid alignment"); } void finalize() { Initialize finalizeCallback = nullptr; { std::lock_guard lock(heapMutex); if (heapFinalized || heapFailure) return; finalizeCallback = heapFinalize; } if (finalizeCallback != nullptr) finalizeCallback(); std::lock_guard lock(heapMutex); heapFinalized = true; } struct HeapFinalizerRegistration { HeapFinalizerRegistration() { if (std::atexit(finalize) != 0) std::terminate(); } }; const HeapFinalizerRegistration heapFinalizerRegistration; } void ApplicationHeapRegister_nid_no_patch(void* const* api) { if (api == nullptr) throw std::invalid_argument("application heap: null allocator API"); std::array replacement; std::memcpy(replacement.data(), api, sizeof(replacement)); if (std::all_of(replacement.begin(), replacement.end(), [](const void* entry) { return entry == nullptr; })) { replacement = defaultApi(); } else { for (std::size_t index = 0; index < 7; ++index) { if (replacement[index] == nullptr) throw std::invalid_argument("application heap: incomplete allocator API"); } } std::lock_guard lock(heapMutex); if (heapFailure) std::rethrow_exception(heapFailure); if (heapFinalized) throw std::runtime_error("application heap: allocator has been finalized"); if (heapApi[0] != nullptr && heapApi != replacement) throw std::runtime_error("application heap: cannot replace an active allocator"); heapApi = replacement; } void ApplicationHeapInitialize_nid_no_patch(const void* processParameters) { std::call_once(heapInitialization, [processParameters] { try { if (read(processParameters, 0) < 0x40 || read(processParameters, 8) != 0x4942524f) throw std::runtime_error("application heap: invalid process parameters"); const auto* libcParameters = read(processParameters, 0x38); if (read(libcParameters, 0) < 0x38) throw std::runtime_error("application heap: invalid libc parameters"); const auto* replacement = read(libcParameters, 0x30); if (read(replacement, 0) != 0x78 || read(replacement, 8) != 2) throw std::runtime_error("application heap: unsupported allocator replacement table"); std::array api; std::memcpy(api.data(), static_cast(replacement) + 0x20, sizeof(api)); ApplicationHeapRegister_nid_no_patch(api.data()); const auto initialize = read(replacement, 0x10); if (initialize != nullptr) initialize(); { std::lock_guard lock(heapMutex); heapFinalize = read(replacement, 0x18); } } catch (...) { std::lock_guard lock(heapMutex); heapFailure = std::current_exception(); } }); std::lock_guard lock(heapMutex); if (heapFailure) std::rethrow_exception(heapFailure); } void* ApplicationHeapAllocate_nid_no_patch(std::size_t bytes) { const auto allocate = callback(0); CallbackScope scope; return requireAllocation(allocate(bytes)); } void ApplicationHeapFree_nid_no_patch(void* pointer) { if (pointer == nullptr) return; const auto free = callback(1); CallbackScope scope; free(pointer); } void* ApplicationHeapReallocate_nid_no_patch(void* pointer, std::size_t bytes) { if (bytes == 0 && pointer != nullptr) { ApplicationHeapFree_nid_no_patch(pointer); return nullptr; } const auto reallocate = callback(3); CallbackScope scope; return requireAllocation(reallocate(pointer, bytes)); } void* ApplicationHeapAlign_nid_no_patch(std::size_t alignment, std::size_t bytes) { requireAlignment(alignment); const auto align = callback(4); CallbackScope scope; void* pointer = requireAllocation(align(alignment, bytes)); if (reinterpret_cast(pointer) % alignment != 0) throw std::runtime_error("application heap: allocator returned a misaligned pointer"); return pointer; } void* ApplicationHeapRealign_nid_no_patch(void* pointer, std::size_t bytes, std::size_t alignment) { if (bytes == 0) { ApplicationHeapFree_nid_no_patch(pointer); return nullptr; } requireAlignment(alignment); Realign realign; { std::lock_guard lock(heapMutex); if (heapFailure) std::rethrow_exception(heapFailure); if (heapFinalized) throw std::runtime_error("application heap: allocator has been finalized"); if (heapApi[0] == nullptr) throw std::runtime_error("application heap: allocator API is not registered"); if (heapApi[5] != nullptr) std::memcpy(&realign, &heapApi[5], sizeof(realign)); else realign = defaultRealign; } CallbackScope scope; void* result = requireAllocation(realign(pointer, bytes, alignment)); if (reinterpret_cast(result) % alignment != 0) throw std::runtime_error("application heap: allocator returned a misaligned pointer"); return result; } void* ApplicationHeapCalloc_nid_no_patch(std::size_t count, std::size_t bytes) { if (bytes != 0 && count > std::numeric_limits::max() / bytes) throw std::length_error("application heap: calloc size overflow"); const auto calloc = callback(2); CallbackScope scope; return requireAllocation(calloc(count, bytes)); } int ApplicationHeapPosixAlign_nid_no_patch(void** pointer, std::size_t alignment, std::size_t bytes) { if (pointer == nullptr) throw std::invalid_argument("application heap: null allocation output"); requireAlignment(alignment); if (alignment < sizeof(void*)) throw std::invalid_argument("application heap: invalid POSIX alignment"); const auto align = callback(6); CallbackScope scope; void* result = nullptr; const int error = align(&result, alignment, bytes); if (error != 0) return error; requireAllocation(result); if (reinterpret_cast(result) % alignment != 0) throw std::runtime_error("application heap: allocator returned a misaligned pointer"); *pointer = result; return 0; } int ApplicationHeapStatsFast_nid_no_patch(void* stats) { Stats statsFast; { std::lock_guard lock(heapMutex); if (heapFailure) std::rethrow_exception(heapFailure); if (heapFinalized) throw std::runtime_error("application heap: allocator has been finalized"); if (heapApi[0] == nullptr) throw std::runtime_error("application heap: allocator API is not registered"); if (heapApi[8] == nullptr) throw std::runtime_error("malloc_stats_fast: implemented only for an application allocator that provides it"); std::memcpy(&statsFast, &heapApi[8], sizeof(statsFast)); } CallbackScope scope; return statsFast(stats); } std::size_t ApplicationHeapUsableSize_nid_no_patch(void* pointer) { UsableSize usableSize = nullptr; { std::lock_guard lock(heapMutex); if (heapFailure) std::rethrow_exception(heapFailure); if (heapFinalized) throw std::runtime_error("application heap: allocator has been finalized"); if (heapApi[0] == nullptr) throw std::runtime_error("application heap: allocator API is not registered"); if (heapApi != defaultApi()) { if (heapApi[9] == nullptr) throw std::runtime_error("malloc_usable_size: implemented only for an application allocator that provides it"); std::memcpy(&usableSize, &heapApi[9], sizeof(usableSize)); } } if (usableSize == nullptr) return GuestHeap::GuestHeapUsableSize_nid_postfix(pointer); CallbackScope scope; return usableSize(pointer); }