#include "prx/libc/include/general/VabiMacros.hpp" #include "prx/libc/include/GuestAllocations.hpp" #include "prx/libc/include/GuestHeap.hpp" #include "prx/libc/include/GuestArena.hpp" #include "prx/libc/include/GuestWriteWatch.hpp" #include "prx/libkernel/File/include/FileFlags.hpp" #include "prx/libkernel/KernelErrors.hpp" #include "prx/libkernel/DirectMemory/DirectMemory.hpp" #include #include #include #include "SceTypes.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__linux__) #include #include #include #include #include #include #include #endif #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #endif extern "C" { void* APS5_VABI mmap_nid_postfix(void*, std::size_t, int, int, int, std::int64_t) noexcept; int APS5_VABI munmap_nid_postfix(void*, std::size_t) noexcept; int APS5_VABI sceKernelMmap(void*, std::size_t, int, int, int, std::int64_t, void**); int APS5_VABI mprotect_nid_postfix(void*, std::size_t, int) noexcept; int APS5_VABI madvise_nid_postfix(void*, std::size_t, int); int* APS5_VABI __error_nid_postfix(); int APS5_VABI sceKernelMapNamedFlexibleMemory(void**, std::size_t, int, int, const char*); int APS5_VABI sceKernelMapNamedFlexibleMemoryInternal(void**, std::size_t, int, int, const char*); int APS5_VABI sceKernelAvailableFlexibleMemorySize(std::size_t*); int APS5_VABI sceKernelInternalMemoryGetAvailableSize(std::size_t*); int APS5_VABI sceKernelMapFlexibleMemory(void**, std::size_t, int, int); int APS5_VABI sceKernelMunmap(void*, std::size_t); int APS5_VABI sceKernelReleaseFlexibleMemory(void*, std::size_t); int APS5_VABI sceKernelMprotect(const void*, std::size_t, int); int APS5_VABI sceKernelMtypeprotect(const void*, std::size_t, int, int); int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry*, int, int*, int); int APS5_VABI sceKernelMemoryPoolBatch(const KernelMemoryPoolBatchEntry*, int, int*, int); int APS5_VABI sceKernelVirtualQuery(const void*, int, VirtualQueryInfo*, std::uint64_t); int APS5_VABI sceKernelSetVirtualRangeName(const void*, std::uint64_t, const char*); int APS5_VABI sceKernelClearVirtualRangeName(const void*, std::uint64_t); int APS5_VABI sceKernelGetPageTableStats(int*, int*, int*, int*); int APS5_VABI sceKernelAllocateDirectMemory(std::int64_t, std::int64_t, std::size_t, std::size_t, int, std::int64_t*); int APS5_VABI sceKernelMapDirectMemory(void**, std::size_t, int, int, std::int64_t, std::size_t); int APS5_VABI sceKernelEnableDmemAliasing(); int APS5_VABI sceKernelReleaseDirectMemory(std::int64_t, std::size_t); int APS5_VABI sceKernelAvailableDirectMemorySize(std::int64_t, std::int64_t, std::size_t, std::int64_t*, std::size_t*); int APS5_VABI sceKernelCheckedReleaseDirectMemory(std::int64_t, std::size_t); int APS5_VABI sceKernelReserveVirtualRange(void**, std::size_t, int, std::size_t); int APS5_VABI sceKernelMemoryPoolReserve(void*, std::size_t, std::size_t, int, void**); int APS5_VABI sceKernelMemoryPoolCommit(void*, std::size_t, int, int, int); int APS5_VABI sceKernelOpen(const char*, int, std::uint16_t); int APS5_VABI sceKernelClose(int); std::int64_t APS5_VABI sceKernelRead(int, void*, std::size_t); std::int64_t APS5_VABI sceKernelPread(int, void*, std::size_t, std::int64_t); int APS5_VABI sceKernelAioInitializeImpl(void*, std::int32_t); int APS5_VABI sceKernelAioSubmitReadCommands(KernelAioRwRequest*, std::int32_t, std::int32_t, std::int32_t*); int APS5_VABI sceKernelAioWaitRequest(std::int32_t, std::int32_t*, std::uint32_t*); int APS5_VABI sceKernelAioDeleteRequest(std::int32_t, std::int32_t*); int APS5_VABI sceKernelMlock_nid_postfix(void*, std::uint64_t); int APS5_VABI sceKernelGetDirectMemoryType(std::int64_t, int*, std::int64_t*, std::int64_t*); int APS5_VABI sceKernelBatchMap2(KernelBatchMapEntry*, int, int*, int); void* APS5_VABI dlopen_nid_postfix(const char*, int); void* APS5_VABI dlsym_nid_postfix(void*, const char*); int APS5_VABI dlclose_nid_postfix(void*); } static void Require(bool condition, std::source_location location = std::source_location::current()) { if (!condition) { std::fprintf(stderr, "Guest memory check failed at %s:%u\n", location.file_name(), static_cast(location.line())); std::abort(); } } static const char* NameAt(const void* address) { static VirtualQueryInfo info; Require(sceKernelVirtualQuery(address, 0, &info, sizeof(info)) == 0); return info.name; } static void CheckReleaseFlexibleMemory() { constexpr std::size_t length = 0x10000; std::size_t before = 0; std::size_t available = 0; Require(sceKernelAvailableFlexibleMemorySize(&before) == 0); void* mapped = nullptr; Require(sceKernelMapFlexibleMemory(&mapped, length, 3, 0) == 0 && mapped != nullptr); static_cast(mapped)[length - 1] = 1; Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - length); Require(sceKernelReleaseFlexibleMemory(mapped, length) == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); void* again = mapped; Require(sceKernelMapFlexibleMemory(&again, length, 3, 0x90) == 0 && again == mapped); Require(static_cast(again)[length - 1] == 0); Require(sceKernelMunmap(again, length) == 0); } static void CheckFlexibleMappingIgnoresFlag8000() { constexpr std::size_t length = 0x10000; std::size_t before = 0; std::size_t available = 0; Require(sceKernelAvailableFlexibleMemorySize(&before) == 0); void* mapped = nullptr; Require(sceKernelMapFlexibleMemory(&mapped, length, 3, 0x8000) == 0 && mapped != nullptr); static_cast(mapped)[length - 1] = 1; Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - length); Require(sceKernelMunmap(mapped, length) == 0); void* again = mapped; Require(sceKernelMapFlexibleMemory(&again, length, 3, 0x8090) == 0 && again == mapped); Require(static_cast(again)[length - 1] == 0); Require(sceKernelMunmap(again, length) == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); bool rejected = false; void* unknown = nullptr; try { sceKernelMapFlexibleMemory(&unknown, length, 3, 0x20000); } catch (const std::exception&) { rejected = true; } Require(rejected && unknown == nullptr); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); } static void CheckPageTableStats() { int cpuTotal = -1, cpuAvailable = -1, gpuTotal = -1, gpuAvailable = -1; Require(sceKernelGetPageTableStats(&cpuTotal, &cpuAvailable, &gpuTotal, &gpuAvailable) == 0); Require(cpuTotal > 0 && gpuTotal > 0); Require(cpuAvailable >= 0 && cpuAvailable <= cpuTotal); Require(gpuAvailable >= 0 && gpuAvailable <= gpuTotal); Require(sceKernelGetPageTableStats(nullptr, &cpuAvailable, &gpuTotal, &gpuAvailable) == SCE_KERNEL_ERROR_EFAULT); Require(sceKernelGetPageTableStats(&cpuTotal, nullptr, &gpuTotal, &gpuAvailable) == SCE_KERNEL_ERROR_EFAULT); Require(sceKernelGetPageTableStats(&cpuTotal, &cpuAvailable, nullptr, &gpuAvailable) == SCE_KERNEL_ERROR_EFAULT); Require(sceKernelGetPageTableStats(&cpuTotal, &cpuAvailable, &gpuTotal, nullptr) == SCE_KERNEL_ERROR_EFAULT); } static void CheckInternalMemoryAvailableSize() { std::size_t available = 1; Require(sceKernelInternalMemoryGetAvailableSize(&available) == 0 && available == 0); Require(sceKernelInternalMemoryGetAvailableSize(nullptr) == static_cast(0x80020016u)); } static void CheckNamedAndHintedMappings() { constexpr std::size_t length = 0x10000; void* first = nullptr; Require(sceKernelMapNamedFlexibleMemory(&first, length, 3, 0, "first mapping") == 0); Require(std::strcmp(NameAt(first), "first mapping") == 0); auto* middle = static_cast(first) + 0x4000; Require(sceKernelSetVirtualRangeName(middle, 0x4000, "middle") == 0); Require(std::strcmp(NameAt(first), "first mapping") == 0); Require(std::strcmp(NameAt(middle), "middle") == 0); Require(sceKernelClearVirtualRangeName(first, length) == 0); Require(NameAt(middle)[0] == '\0'); Require(sceKernelSetVirtualRangeName(nullptr, length, "x") != 0); void* hinted = first; Require(sceKernelMapFlexibleMemory(&hinted, length, 3, 0) == 0); Require(hinted > first && (reinterpret_cast(hinted) & 0x3fff) == 0); static_cast(hinted)[length - 1] = 1; void* overwrite = first; Require(sceKernelMapFlexibleMemory(&overwrite, 0x4000, 3, 0x90) == static_cast(0x8002000cu)); Require(overwrite == first); Require(sceKernelMunmap(hinted, length) == 0); void* exclusive = hinted; Require(sceKernelMapFlexibleMemory(&exclusive, length, 3, 0x90) == 0); Require(exclusive == hinted); static_cast(exclusive)[0] = 1; Require(sceKernelMunmap(exclusive, length) == 0); Require(sceKernelMunmap(first, length) == 0); } static void CheckAudioCoprocessorProtection() { constexpr std::size_t length = 0x4000; void* writable = nullptr; Require(sceKernelMapFlexibleMemory(&writable, length, 0x200, 0) == 0); static_cast(writable)[length - 1] = 7; Require(static_cast(writable)[length - 1] == 7); Require(sceKernelMprotect(writable, length, 0x100) == 0); Require(static_cast(writable)[length - 1] == 7); Require(sceKernelMprotect(writable, length, 0x3f2) == 0); static_cast(writable)[0] = 9; Require(sceKernelMunmap(writable, length) == 0); bool rejected = false; void* undefined = nullptr; try { sceKernelMapFlexibleMemory(&undefined, length, 0x400, 0); } catch (const std::invalid_argument&) { rejected = true; } Require(rejected && undefined == nullptr); } static void CheckInternalNamedFlexibleMapping() { constexpr std::size_t length = 0x10000; std::size_t before = 0; std::size_t available = 0; Require(sceKernelAvailableFlexibleMemorySize(&before) == 0); void* mapped = nullptr; Require(sceKernelMapNamedFlexibleMemoryInternal(&mapped, length, 3, 0, "internal mapping") == 0 && mapped != nullptr); Require(std::strcmp(NameAt(mapped), "internal mapping") == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - length); Require(sceKernelMunmap(mapped, length) == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); void* flagged = nullptr; Require(sceKernelMapNamedFlexibleMemoryInternal(&flagged, length, 3, 0x8000, "internal flag") == 0 && flagged != nullptr); Require(std::strcmp(NameAt(flagged), "internal flag") == 0); static_cast(flagged)[length - 1] = 1; Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before - length); Require(sceKernelMunmap(flagged, length) == 0); bool rejected = false; void* unknown = nullptr; try { sceKernelMapNamedFlexibleMemoryInternal(&unknown, length, 3, 0x20000, "internal mapping"); } catch (const std::exception&) { rejected = true; } Require(rejected && unknown == nullptr); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); } static void CheckGuestModuleUsesFlexibleMemory() { constexpr std::size_t moduleData = 0x10000; const auto path = std::filesystem::relative(GUEST_MEMORY_MODULE).generic_string(); std::size_t before = 0; std::size_t available = 0; Require(sceKernelAvailableFlexibleMemorySize(&before) == 0); void* module = dlopen_nid_postfix(path.c_str(), 2); Require(module != nullptr); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available + moduleData <= before); void* mapped = nullptr; Require(sceKernelMapFlexibleMemory(&mapped, before, 3, 0) == SCE_KERNEL_ERROR_ENOMEM && mapped == nullptr); Require(dlclose_nid_postfix(module) == 0); Require(sceKernelAvailableFlexibleMemorySize(&available) == 0 && available == before); } static void CheckBatchMapStopsAtInvalidEntry() { constexpr std::size_t page = 0x4000; constexpr int mapFlexible = 3; constexpr int unmap = 1; constexpr int protect = 2; const auto flexible = [&] { return KernelBatchMapEntry{nullptr, 0, page, 3, 0, 0, mapFlexible}; }; for (const std::int32_t operation : {5, 6, -1, std::numeric_limits::max()}) { KernelBatchMapEntry entries[3] = {flexible(), flexible(), flexible()}; entries[1].operation = operation; int processed = -1; Require(sceKernelBatchMap2(entries, 3, &processed, 0) == SCE_KERNEL_ERROR_EINVAL); Require(processed == 1); Require(entries[0].start != nullptr && entries[1].start == nullptr && entries[2].start == nullptr); Require(sceKernelMunmap(entries[0].start, page) == 0); } KernelBatchMapEntry entries[3] = {flexible(), flexible(), flexible()}; entries[1].operation = protect; entries[1].length = 0; int processed = -1; Require(sceKernelBatchMap2(entries, 3, &processed, 0) == SCE_KERNEL_ERROR_EINVAL); Require(processed == 1 && entries[0].start != nullptr); Require(entries[1].start == nullptr && entries[2].start == nullptr); KernelBatchMapEntry unmaps[2] = {entries[0], entries[0]}; unmaps[0].operation = unmap; unmaps[1].operation = unmap; unmaps[1].length = 0; processed = -1; Require(sceKernelBatchMap2(unmaps, 2, &processed, 0) == SCE_KERNEL_ERROR_EINVAL && processed == 1); Require(sceKernelBatchMap2(entries, 1, &processed, 0) == 0 && processed == 1); Require(sceKernelMunmap(entries[0].start, page) == 0); } static void CheckCheckedReleaseDirectMemory() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); Require(sceKernelCheckedReleaseDirectMemory(phys + 1, page) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelCheckedReleaseDirectMemory(phys, page + 1) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelCheckedReleaseDirectMemory(phys, 0) == 0); Require(sceKernelCheckedReleaseDirectMemory(phys, page * 3) == SCE_KERNEL_ERROR_ENOENT); void* mapped = nullptr; Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0, phys, 0) == 0); Require(sceKernelMunmap(mapped, page * 2) == 0); Require(sceKernelCheckedReleaseDirectMemory(phys + page, page) == 0); Require(sceKernelCheckedReleaseDirectMemory(phys, page * 2) == SCE_KERNEL_ERROR_ENOENT); Require(sceKernelCheckedReleaseDirectMemory(phys, page) == 0); Require(sceKernelCheckedReleaseDirectMemory(phys, page) == SCE_KERNEL_ERROR_ENOENT); } static void CheckDirectMemoryFollowsPhysicalPages() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); void* first = nullptr; Require(sceKernelMapDirectMemory(&first, page * 2, 3, 0, phys, 0) == 0); static_cast(first)[0] = 11; static_cast(first)[page + 5] = 22; Require(sceKernelEnableDmemAliasing() == 0); void* alias = nullptr; Require(sceKernelMapDirectMemory(&alias, page, 3, 0, phys + page, 0) == 0); Require(alias != first && static_cast(alias)[5] == 22); static_cast(alias)[5] = 37; Require(static_cast(first)[page + 5] == 37); static_cast(first)[page + 6] = 48; Require(static_cast(alias)[6] == 48); Require(sceKernelMprotect(alias, page, 1) == 0); static_cast(first)[page + 5] = 59; Require(static_cast(alias)[5] == 59); Require(sceKernelMprotect(alias, page, 3) == 0); static_cast(alias)[5] = 22; Require(static_cast(first)[page + 5] == 22); Require(sceKernelMunmap(alias, page) == 0); static_cast(first)[page + 5] = 22; Require(sceKernelMunmap(first, page * 2) == 0); void* filler = nullptr; Require(sceKernelMapFlexibleMemory(&filler, page * 2, 3, 0) == 0); void* second = nullptr; Require(sceKernelMapDirectMemory(&second, page, 3, 0, phys + page, 0) == 0); Require(second != first && static_cast(second)[5] == 22); void* reserved = nullptr; Require(sceKernelReserveVirtualRange(&reserved, page, 0, 0) == 0); void* fixed = reserved; Require(sceKernelMapDirectMemory(&fixed, page, 1, 0x10, phys, 0) == 0); Require(fixed == reserved && static_cast(fixed)[0] == 11); Require(sceKernelMunmap(fixed, page) == 0); Require(sceKernelMunmap(second, page) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); std::int64_t again = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &again) == 0 && again == phys); void* fresh = nullptr; Require(sceKernelMapDirectMemory(&fresh, page * 2, 3, 0, again, 0) == 0); Require(static_cast(fresh)[0] == 0 && static_cast(fresh)[page + 5] == 0); Require(sceKernelMunmap(fresh, page * 2) == 0); Require(sceKernelMunmap(filler, page * 2) == 0); Require(sceKernelReleaseDirectMemory(again, page * 2) == 0); } static void CheckSparseDirectMemoryBacking() { constexpr std::size_t page = 0x4000; constexpr std::size_t size = 256u * 1024u * 1024u; std::int64_t physical = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, size, 0, 0, &physical) == 0); void* first = nullptr; void* last = nullptr; Require(sceKernelMapDirectMemory(&first, page, 3, 0, physical, 0) == 0); Require(sceKernelMapDirectMemory(&last, page, 3, 0, physical + size - page, 0) == 0); auto* firstBytes = static_cast(first); auto* lastBytes = static_cast(last); Require(firstBytes[0] == 0 && firstBytes[page - 1] == 0 && lastBytes[0] == 0 && lastBytes[page - 1] == 0); firstBytes[0] = 17; lastBytes[page - 1] = 29; void* alias = nullptr; Require(sceKernelMapDirectMemory(&alias, page, 3, 0, physical + size - page, 0) == 0); auto* aliasBytes = static_cast(alias); Require(aliasBytes[page - 1] == 29 && aliasBytes[0] == 0); aliasBytes[page - 1] = 41; Require(lastBytes[page - 1] == 41 && firstBytes[0] == 17); Require(sceKernelMunmap(last, page) == 0 && sceKernelMunmap(alias, page) == 0); last = nullptr; Require(sceKernelMapDirectMemory(&last, page, 3, 0, physical + size - page, 0) == 0); Require(static_cast(last)[page - 1] == 41); Require(sceKernelMunmap(first, page) == 0 && sceKernelMunmap(last, page) == 0); Require(sceKernelReleaseDirectMemory(physical, size) == 0); } static void CheckReleaseDirectMemoryClearsMappings() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); void* mapped = nullptr; Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0, phys, 0) == 0); VirtualQueryInfo before{}; Require(sceKernelVirtualQuery(mapped, 0, &before, sizeof(before)) == 0); Require(before.is_direct && before.offset == static_cast(phys)); Require(sceKernelReleaseDirectMemory(phys + page, page) == 0); VirtualQueryInfo split{}; Require(sceKernelVirtualQuery(mapped, 0, &split, sizeof(split)) == 0); Require(split.is_direct && split.offset == static_cast(phys)); VirtualQueryInfo dropped{}; const int droppedResult = sceKernelVirtualQuery(static_cast(mapped) + page, 0, &dropped, sizeof(dropped)); Require(droppedResult != 0 || !dropped.is_direct); void* alias = nullptr; Require(sceKernelMapDirectMemory(&alias, page, 3, 0, phys, 0) == 0); Require(sceKernelReleaseDirectMemory(phys, page) == 0); for (void* view : {mapped, alias}) { VirtualQueryInfo cleared{}; Require(sceKernelVirtualQuery(view, 0, &cleared, sizeof(cleared)) != 0); VirtualQueryInfo next{}; if (sceKernelVirtualQuery(view, 1, &next, sizeof(next)) == 0) Require(next.start != reinterpret_cast(view)); } } static void CheckReleaseDirectMemoryRejectsInvalidRanges() { constexpr std::size_t page = 0x4000; Require(sceKernelReleaseDirectMemory(-1, page) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelReleaseDirectMemory(0, 0) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelReleaseDirectMemory(0, 1) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelReleaseDirectMemory(0, page - 1) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelReleaseDirectMemory(1, page) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelReleaseDirectMemory(0, std::numeric_limits::max()) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelReleaseDirectMemory(0, DIRECT_MEMORY_SIZE + page) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelReleaseDirectMemory(DIRECT_MEMORY_SIZE - page, page * 2) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelReleaseDirectMemory(DIRECT_MEMORY_SIZE, page) == SCE_KERNEL_ERROR_EINVAL); } static void CheckFixedMappingReplacesPartialOverlap() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 3, 0, 0, &phys) == 0); void* reserved = nullptr; Require(sceKernelMapDirectMemory(&reserved, page * 3, 3, 0, phys, 0) == 0); Require(sceKernelMunmap(reserved, page * 3) == 0); void* head = reserved; Require(sceKernelMapDirectMemory(&head, page, 3, 0x10, phys, 0) == 0 && head == reserved); void* fixed = reserved; Require(sceKernelMapDirectMemory(&fixed, page * 3, 3, 0x10, phys, 0) == 0); Require(fixed == reserved); static_cast(fixed)[page * 2 + 7] = 0x5c; VirtualQueryInfo info{}; Require(sceKernelVirtualQuery(fixed, 0, &info, sizeof(info)) == 0); Require(info.is_direct && info.offset == static_cast(phys)); Require(static_cast(fixed)[page * 2 + 7] == 0x5c); Require(sceKernelMunmap(fixed, page * 3) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); } static void CheckFixedMappingOverHoleInsideAllocation() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 4, 0, 0, &phys) == 0); void* mapped = nullptr; Require(sceKernelMapDirectMemory(&mapped, page * 4, 3, 0, phys, 0) == 0); auto* bytes = static_cast(mapped); bytes[7] = 0x11; bytes[page * 3 + 7] = 0x44; Require(sceKernelMunmap(bytes + page, page) == 0); void* fixed = bytes + page; Require(sceKernelMapDirectMemory(&fixed, page * 2, 3, 0x10, phys + static_cast(page), 0) == 0); Require(fixed == bytes + page); bytes[page * 2 + 7] = 0x33; Require(bytes[7] == 0x11 && bytes[page * 2 + 7] == 0x33 && bytes[page * 3 + 7] == 0x44); Require(sceKernelMunmap(bytes + page * 2, page * 2) == 0); Require(sceKernelMunmap(mapped, page * 4) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 4) == 0); } static void CheckGetDirectMemoryType() { constexpr std::size_t page = 0x4000; std::int64_t first = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 3, &first) == 0); std::int64_t second = 0; Require(sceKernelAllocateDirectMemory(first + page * 2, first + page * 3, page, 0, 1, &second) == 0 && second == first + page * 2); int type = -1; std::int64_t start = -1; std::int64_t end = -1; Require(sceKernelGetDirectMemoryType(first, &type, &start, &end) == 0); Require(type == 3 && start == first && end == first + page * 2); type = -1; Require(sceKernelGetDirectMemoryType(first + page * 2 - 1, &type, &start, &end) == 0); Require(type == 3 && start == first && end == first + page * 2); Require(sceKernelGetDirectMemoryType(first + page * 2, &type, &start, &end) == 0); Require(type == 1 && start == second && end == second + page); Require(sceKernelGetDirectMemoryType(first, nullptr, &start, &end) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelGetDirectMemoryType(first, &type, nullptr, &end) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelGetDirectMemoryType(first, &type, &start, nullptr) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelReleaseDirectMemory(first, page) == 0); type = -1; start = -1; end = -1; Require(sceKernelGetDirectMemoryType(first, &type, &start, &end) == SCE_KERNEL_ERROR_ENOENT); Require(type == -1 && start == -1 && end == -1); Require(sceKernelGetDirectMemoryType(-1, &type, &start, &end) == SCE_KERNEL_ERROR_ENOENT); Require(sceKernelGetDirectMemoryType(first + page, &type, &start, &end) == 0); Require(type == 3 && start == first + page && end == first + page * 2); Require(sceKernelReleaseDirectMemory(first + page, page * 2) == 0); Require(sceKernelGetDirectMemoryType(second, &type, &start, &end) == SCE_KERNEL_ERROR_ENOENT); } static void CheckMtypeprotect() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 3, 0, 0, &phys) == 0); void* mapped = nullptr; Require(sceKernelMapDirectMemory(&mapped, page * 3, 3, 0, phys, 0) == 0); auto* bytes = static_cast(mapped); Require(sceKernelMtypeprotect(bytes + page + 1, 1, 3, 1) == 0); const auto check = [&](std::size_t index, int expectedType, int expectedProtection) { VirtualQueryInfo info{}; Require(sceKernelVirtualQuery(bytes + page * index, 0, &info, sizeof(info)) == 0); Require(info.is_direct && info.memory_type == expectedType && info.protection == expectedProtection); Require(info.start == reinterpret_cast(bytes + page * index) && info.end == info.start + page); Require(info.offset == static_cast(phys) + page * index); int type = -1; std::int64_t start = -1; std::int64_t end = -1; Require(sceKernelGetDirectMemoryType(phys + static_cast(page * index), &type, &start, &end) == 0); Require(type == expectedType && start == phys + static_cast(page * index) && end == start + static_cast(page)); }; check(0, 0, 3); check(1, 3, 1); check(2, 0, 3); KernelBatchMapEntry entry{}; entry.start = bytes + page * 2; entry.length = page; entry.protection = 3; entry.type = 5; entry.operation = 4; int processed = -1; Require(sceKernelBatchMap2(&entry, 1, &processed, 0) == 0 && processed == 1); check(0, 0, 3); check(1, 3, 1); check(2, 5, 3); KernelMemoryPoolBatchEntry poolEntry{}; poolEntry.op = 4; poolEntry.type_protect.addr = bytes + page * 2; poolEntry.type_protect.len = page; poolEntry.type_protect.prot = 1; poolEntry.type_protect.type = 4; processed = -1; Require(sceKernelMemoryPoolBatch(&poolEntry, 1, &processed, 0) == 0 && processed == 1); check(1, 3, 1); check(2, 4, 1); Require(sceKernelMemoryPoolCommit(bytes + page * 2, page, 6, 3, 0) == 0); check(2, 6, 3); check(1, 3, 1); KernelMemoryPoolBatchEntry commitEntry{}; commitEntry.op = 1; commitEntry.commit.addr = bytes + page * 2; commitEntry.commit.len = page; commitEntry.commit.prot = 1; commitEntry.commit.type = 7; processed = -1; Require(sceKernelMemoryPoolBatch(&commitEntry, 1, &processed, 0) == 0 && processed == 1); check(1, 3, 1); check(2, 7, 1); Require(sceKernelMtypeprotect(bytes, page * 3, 2, 3) == 0); VirtualQueryInfo whole{}; Require(sceKernelVirtualQuery(bytes, 0, &whole, sizeof(whole)) == 0); Require(whole.memory_type == 2 && whole.protection == 3); int type = -1; std::int64_t start = -1; std::int64_t end = -1; Require(sceKernelGetDirectMemoryType(phys + static_cast(page * 2), &type, &start, &end) == 0 && type == 2); Require(sceKernelMunmap(mapped, page * 3) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); } static void CheckDirectMemoryGpuProtBits() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &phys) == 0); void* mapping = nullptr; Require(sceKernelMapDirectMemory(&mapping, page, 0x3f2, 0, phys, 0) == 0); static_cast(mapping)[0] = 11; Require(static_cast(mapping)[0] == 11); Require(sceKernelMunmap(mapping, page) == 0); Require(sceKernelReleaseDirectMemory(phys, page) == 0); } static bool GpuMapped(const void* pointer) { GuestAllocations::Mutation mutation; return mutation.Find(pointer).gpu; } static void CheckGpuAccessFollowsProtection() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); void* cpu = nullptr; Require(sceKernelMapDirectMemory(&cpu, page, 3, 0, phys, 0) == 0); Require(!GpuMapped(cpu)); void* gpu = nullptr; Require(sceKernelMapDirectMemory(&gpu, page, 0x32, 0, phys + static_cast(page), 0) == 0); Require(GpuMapped(gpu)); Require(sceKernelMprotect(cpu, page, 0x13) == 0); Require(GpuMapped(cpu)); Require(sceKernelMprotect(gpu, page, 3) == 0); Require(!GpuMapped(gpu)); static int imageProbe = 0; const auto probe = reinterpret_cast(&imageProbe); { GuestAllocations::Mutation mutation; mutation.RegisterMainImage(); } { const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix(); const auto image = std::find_if(lease.begin(), lease.end(), [&](const auto& range) { return probe >= range->address && probe - range->address < range->bytes; }); Require(image != lease.end() && !(*image)->releasable && !(*image)->gpu); } Require(sceKernelMunmap(cpu, page) == 0); Require(sceKernelMunmap(gpu, page) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); void* flexible = nullptr; Require(sceKernelMapNamedFlexibleMemory(&flexible, page, 3, 0, "cpu") == 0); Require(!GpuMapped(flexible)); Require(sceKernelMunmap(flexible, page) == 0); Require(sceKernelMapNamedFlexibleMemory(&flexible, page, 0x33, 0, "gpu") == 0); Require(GpuMapped(flexible)); Require(sceKernelMunmap(flexible, page) == 0); } static std::vector> gpuMapped; static std::atomic observerEntered{false}; static std::atomic observerDone{false}; static void CheckClearingTheObserverWaitsForItsCalls() { GuestAllocations::GuestAllocationsSetGpuMapObserver_nid_postfix([](const GuestAllocations::Mapped&, std::uint64_t) { observerEntered = true; std::this_thread::sleep_for(std::chrono::milliseconds(100)); observerDone = true; }); constexpr std::size_t page = 0x4000; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &phys) == 0); void* gpu = nullptr; std::thread mapper([&] { Require(sceKernelMapDirectMemory(&gpu, page, 0x32, 0, phys, 0) == 0); }); while (!observerEntered) std::this_thread::yield(); GuestAllocations::GuestAllocationsSetGpuMapObserver_nid_postfix(nullptr); Require(observerDone); mapper.join(); Require(sceKernelMunmap(gpu, page) == 0); Require(sceKernelReleaseDirectMemory(phys, page) == 0); } static void CheckGpuMapsAreObserved() { GuestAllocations::GuestAllocationsSetGpuMapObserver_nid_postfix([](const GuestAllocations::Mapped& mapped, std::uint64_t) { const auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix(); for (const auto& weak : mapped) { const auto range = weak.lock(); if (range == nullptr) continue; const bool registered = std::any_of(lease.begin(), lease.end(), [&](const auto& current) { return current == range; }); gpuMapped.emplace_back(registered && range->gpu ? range->address : 0, range->bytes); } }); constexpr std::size_t page = 0x4000; void* heap = GuestHeap::GuestHeapAlign_nid_postfix(page, page); Require(heap != nullptr && gpuMapped.empty()); GuestHeap::GuestHeapFree_nid_postfix(heap); std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); void* cpu = nullptr; Require(sceKernelMapDirectMemory(&cpu, page, 3, 0, phys, 0) == 0); Require(gpuMapped.empty()); void* gpu = nullptr; Require(sceKernelMapDirectMemory(&gpu, page, 0x32, 0, phys + static_cast(page), 0) == 0); Require(gpuMapped.size() == 1 && gpuMapped.front().first == reinterpret_cast(gpu) && gpuMapped.front().second == page); Require(sceKernelMprotect(cpu, page, 0x13) == 0); Require(gpuMapped.size() == 1); std::int64_t remapPhys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &remapPhys) == 0); void* reserved = nullptr; Require(sceKernelReserveVirtualRange(&reserved, page * 2, 0, 0) == 0); void* cpuRemap = reserved; Require(sceKernelMapDirectMemory(&cpuRemap, page, 3, 0x10, remapPhys, 0) == 0 && cpuRemap == reserved); Require(gpuMapped.size() == 1); void* gpuRemap = static_cast(reserved) + page; void* const gpuTarget = gpuRemap; Require(sceKernelMapDirectMemory(&gpuRemap, page, 0x32, 0x10, remapPhys + static_cast(page), 0) == 0 && gpuRemap == gpuTarget); Require(gpuMapped.size() == 2 && gpuMapped.back().first == reinterpret_cast(gpuTarget) && gpuMapped.back().second == page); GuestAllocations::GuestAllocationsSetGpuMapObserver_nid_postfix(nullptr); Require(sceKernelMunmap(reserved, page * 2) == 0); Require(sceKernelReleaseDirectMemory(remapPhys, page * 2) == 0); Require(sceKernelMunmap(cpu, page) == 0); Require(sceKernelMunmap(gpu, page) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); } static void CheckHintedVirtualReservation() { constexpr std::size_t page = 0x4000; void* probe = nullptr; Require(sceKernelReserveVirtualRange(&probe, page * 8, 0, 0) == 0); Require(sceKernelMunmap(probe, page * 8) == 0); void* const hint = static_cast(probe) + page * 2; void* placed = hint; Require(sceKernelReserveVirtualRange(&placed, page * 2, 0, 0) == 0); Require(placed == hint); void* above = hint; Require(sceKernelReserveVirtualRange(&above, page * 2, 0, 0) == 0); Require(above > hint); Require(sceKernelMunmap(above, page * 2) == 0); Require(sceKernelMunmap(placed, page * 2) == 0); } static void CheckFixedVirtualReservation() { constexpr std::size_t page = 0x4000; void* probe = nullptr; Require(sceKernelReserveVirtualRange(&probe, page * 4, 0, 0) == 0); Require(sceKernelMunmap(probe, page * 4) == 0); void* const requested = static_cast(probe) + page; void* fixed = requested; Require(sceKernelReserveVirtualRange(&fixed, page * 2, 0x400010, 0) == 0); Require(fixed == requested); void* again = requested; Require(sceKernelReserveVirtualRange(&again, page * 2, 0x400010, 0) == 0); Require(again == requested); std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); void* mapped = requested; Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0x10, phys, 0) == 0); Require(mapped == requested); static_cast(mapped)[0] = 11; VirtualQueryInfo before{}; Require(sceKernelVirtualQuery(mapped, 0, &before, sizeof(before)) == 0); Require(before.is_direct); void* reserved = requested; Require(sceKernelReserveVirtualRange(&reserved, page * 2, 0x10, 0) == 0); Require(reserved == requested); VirtualQueryInfo after{}; Require(sceKernelVirtualQuery(reserved, 0, &after, sizeof(after)) == 0); Require(!after.is_committed && !after.is_direct && after.protection == 0); void* remapped = requested; Require(sceKernelMapDirectMemory(&remapped, page * 2, 3, 0x10, phys, 0) == 0); Require(remapped == requested); VirtualQueryInfo revived{}; Require(sceKernelVirtualQuery(remapped, 0, &revived, sizeof(revived)) == 0); Require(revived.is_direct); bool refused = false; try { sceKernelReserveVirtualRange(&reserved, page * 2, 0x90, 0); } catch (const std::exception&) { refused = true; } Require(refused); Require(sceKernelMunmap(reserved, page * 2) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); void* pooled = nullptr; Require(sceKernelMemoryPoolReserve(requested, page * 2, 0, 0x10, &pooled) == 0); Require(pooled == requested); Require(sceKernelMunmap(pooled, page * 2) == 0); } static void CheckHintInsideReservation() { constexpr std::size_t page = 0x4000; void* reserved = nullptr; Require(sceKernelReserveVirtualRange(&reserved, page * 4, 0, 0) == 0); auto* base = static_cast(reserved); std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 3, 0, 0, &phys) == 0); void* direct = base + page; Require(sceKernelMapDirectMemory(&direct, page, 3, 0, phys, 0) == 0); Require(direct == base + page); static_cast(direct)[0] = 21; void* flexible = base + page * 2; Require(sceKernelMapFlexibleMemory(&flexible, page, 3, 0) == 0); Require(flexible == base + page * 2); static_cast(flexible)[0] = 22; VirtualQueryInfo info{}; Require(sceKernelVirtualQuery(direct, 0, &info, sizeof(info)) == 0); Require(info.is_direct && info.offset == static_cast(phys)); Require(sceKernelVirtualQuery(flexible, 0, &info, sizeof(info)) == 0); Require(info.is_flexible); Require(sceKernelVirtualQuery(base + page * 3, 0, &info, sizeof(info)) == 0); Require(!info.is_committed && info.protection == 0); void* overlapping = base + page; Require(sceKernelMapDirectMemory(&overlapping, page * 2, 3, 0, phys + page, 0) == 0); Require(overlapping != base + page); Require(static_cast(direct)[0] == 21 && static_cast(flexible)[0] == 22); Require(sceKernelMunmap(overlapping, page * 2) == 0); Require(sceKernelMunmap(reserved, page * 4) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); } static void CheckReservedRangeIsNotCommitted() { constexpr std::size_t page = 0x4000; void* reserved = nullptr; Require(sceKernelReserveVirtualRange(&reserved, page * 2, 0, 0) == 0); const auto start = reinterpret_cast(reserved); VirtualQueryInfo info{}; Require(sceKernelVirtualQuery(reserved, 0, &info, sizeof(info)) == 0); Require(!info.is_committed && !info.is_direct && !info.is_flexible && info.protection == 0); Require(info.start == start && info.end == start + page * 2); std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &phys) == 0); void* fixed = reserved; Require(sceKernelMapDirectMemory(&fixed, page, 3, 0x10, phys, 0) == 0); Require(fixed == reserved); static_cast(fixed)[0] = 7; Require(sceKernelVirtualQuery(reserved, 0, &info, sizeof(info)) == 0); Require(info.is_committed && info.is_direct && info.protection == 3); Require(info.start == start && info.end == start + page); Require(sceKernelVirtualQuery(static_cast(reserved) + page, 0, &info, sizeof(info)) == 0); Require(!info.is_committed && info.protection == 0); Require(info.start == start + page && info.end == start + page * 2); Require(sceKernelMunmap(reserved, page * 2) == 0); Require(sceKernelReleaseDirectMemory(phys, page) == 0); } static void CheckNoOverwriteRefusesLiveMapping() { constexpr std::size_t page = 0x4000; constexpr int outOfMemory = static_cast(0x8002000cu); std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); void* mapped = nullptr; Require(sceKernelMapDirectMemory(&mapped, page, 3, 0, phys, 0) == 0); static_cast(mapped)[0] = 13; void* again = mapped; Require(sceKernelMapDirectMemory(&again, page, 3, 0x90, phys + page, 0) == outOfMemory); Require(again == mapped); void* flexible = mapped; Require(sceKernelMapFlexibleMemory(&flexible, page, 3, 0x90) == outOfMemory); Require(static_cast(mapped)[0] == 13); VirtualQueryInfo info{}; Require(sceKernelVirtualQuery(mapped, 0, &info, sizeof(info)) == 0); Require(info.is_direct && info.offset == static_cast(phys)); Require(sceKernelMunmap(mapped, page) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); } #ifdef _WIN32 constexpr std::uint32_t ReadWriteProtection = PAGE_READWRITE; #else constexpr std::uint32_t ReadWriteProtection = 0x04; #endif static void CheckNoOverwriteRejectsHostOccupiedMapping() { constexpr std::size_t page = 0x4000; void* reservation = nullptr; Require(sceKernelReserveVirtualRange(&reservation, page * 4, 0, 0) == 0); Require(sceKernelMunmap(reservation, page * 4) == 0); void* target = static_cast(reservation) + page; GuestArena::GuestArenaCommit_nid_postfix(target, page, ReadWriteProtection, page); std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &phys) == 0); void* fixed = target; bool rejected = false; try { rejected = sceKernelMapDirectMemory(&fixed, page, 3, 0x90, phys, 0) != 0; } catch (const std::exception&) { rejected = true; } Require(rejected); GuestArena::GuestArenaReset_nid_postfix(target, page); Require(sceKernelReleaseDirectMemory(phys, page) == 0); } #ifdef _WIN32 static DWORD ImageProtection(const void* pointer) { MEMORY_BASIC_INFORMATION memory{}; Require(VirtualQuery(pointer, &memory, sizeof(memory)) == sizeof(memory)); return memory.Protect & 0xffu; } static unsigned char* GuestModulePage(void* module, std::size_t page) { auto* data = static_cast(dlsym_nid_postfix(module, "guestMemoryModuleData")); Require(data != nullptr); auto* target = reinterpret_cast((reinterpret_cast(data) + page - 1) & ~(page - 1)); Require(target + page <= data + 0x10000); return target; } static bool RegisteredGuestRange(const void* pointer, std::size_t bytes) { GuestAllocations::Mutation mutation; return mutation.Overlaps(pointer, bytes); } static void CheckGuestModuleImageProtection() { constexpr std::size_t page = 0x4000; const auto path = std::filesystem::relative(GUEST_MEMORY_MODULE).generic_string(); void* module = dlopen_nid_postfix(path.c_str(), 2); Require(module != nullptr); auto* target = GuestModulePage(module, page); Require(!RegisteredGuestRange(target, page)); Require(sceKernelMprotect(target, page, 1) == 0); Require(RegisteredGuestRange(target, page)); Require(ImageProtection(target) == PAGE_READONLY); Require(sceKernelMprotect(target, page, 3) == 0); Require(ImageProtection(target) == PAGE_READWRITE || ImageProtection(target) == PAGE_WRITECOPY); target[1] = 7; Require(target[1] == 7); Require(sceKernelMprotect(target, page, 1) == 0); Require(ImageProtection(target) == PAGE_READONLY); { auto held = GuestAllocations::GuestAllocationsAcquire_nid_postfix(); bool failed = false; try { dlclose_nid_postfix(module); } catch (const std::runtime_error&) { failed = true; } Require(failed); Require(GuestModulePage(module, page) == target); Require(RegisteredGuestRange(target, page)); Require(ImageProtection(target) == PAGE_READONLY); } auto lease = GuestAllocations::GuestAllocationsAcquire_nid_postfix(); std::atomic closed{false}; std::thread closer([&] { Require(dlclose_nid_postfix(module) == 0); closed = true; }); std::this_thread::sleep_for(std::chrono::milliseconds(200)); Require(!closed); Require(ImageProtection(target) == PAGE_READONLY); lease.clear(); closer.join(); Require(!RegisteredGuestRange(target, page)); { GuestAllocations::Mutation mutation; mutation.Add(target, page, true, true, false); mutation.Remove(target); } module = dlopen_nid_postfix(path.c_str(), 2); Require(module != nullptr); target = GuestModulePage(module, page); Require(!RegisteredGuestRange(target, page)); Require(sceKernelMprotect(target, page, 1) == 0); Require(ImageProtection(target) == PAGE_READONLY); Require(RegisteredGuestRange(target, page)); Require(dlclose_nid_postfix(module) == 0); Require(!RegisteredGuestRange(target, page)); bool rejected = false; try { sceKernelMprotect(GetModuleHandleA("kernel32.dll"), page, 1); } catch (const std::invalid_argument&) { rejected = true; } Require(rejected); } #endif static void CheckUnhintedKernelMappingsLandInTheWindow() { constexpr std::size_t page = 0x4000; std::uintptr_t windowStart = 0; std::size_t windowSize = 0; GuestArena::GuestArenaRange_nid_postfix(&windowStart, &windowSize); const auto windowEnd = windowStart + windowSize; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &phys) == 0); void* direct = nullptr; Require(sceKernelMapDirectMemory(&direct, page, 3, 0, phys, 0) == 0); Require(direct != nullptr); const auto directAddress = reinterpret_cast(direct); Require(directAddress >= windowStart); Require(directAddress + page <= windowEnd); void* flexible = nullptr; Require(sceKernelMapNamedFlexibleMemory(&flexible, page, 3, 0, "arena-window-test") == 0); Require(flexible != nullptr); const auto flexibleAddress = reinterpret_cast(flexible); Require(flexibleAddress >= windowStart); Require(flexibleAddress + page <= windowEnd); Require(sceKernelMunmap(direct, page) == 0); Require(sceKernelMunmap(flexible, page) == 0); Require(sceKernelReleaseDirectMemory(phys, page) == 0); } #if defined(__linux__) static void CheckUnmappedArenaRangeStaysReserved() { constexpr std::size_t page = 0x4000; void* address = nullptr; Require(sceKernelMapNamedFlexibleMemory(&address, page, 3, 0, "reserved-after-unmap") == 0); Require(address != nullptr); Require(sceKernelMunmap(address, page) == 0); void* hostile = mmap(address, page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0); Require(hostile == MAP_FAILED && errno == EEXIST); void* again = nullptr; Require(sceKernelMapNamedFlexibleMemory(&again, page, 3, 0, "reserved-after-unmap-2") == 0); Require(again != nullptr); Require(sceKernelMunmap(again, page) == 0); } #endif static void CheckArenaPlacesUnhintedMappingsInsideTheWindow() { constexpr std::size_t page = 0x4000; std::uintptr_t base = 0; std::size_t size = 0; GuestArena::GuestArenaRange_nid_postfix(&base, &size); Require(size != 0); auto* placed = GuestArena::GuestArenaAllocate_nid_postfix(page, page); Require(placed != nullptr); const auto address = reinterpret_cast(placed); Require(address >= base); Require(address + page <= base + size); GuestArena::GuestArenaRelease_nid_postfix(placed, page); } static void CheckArenaReusesAFreedRange() { constexpr std::size_t page = 0x4000; auto* first = GuestArena::GuestArenaAllocate_nid_postfix(page, page); Require(first != nullptr); GuestArena::GuestArenaRelease_nid_postfix(first, page); auto* second = GuestArena::GuestArenaAllocate_nid_postfix(page, page); Require(second == first); GuestArena::GuestArenaRelease_nid_postfix(second, page); } static void CheckArenaHonoursHintsWithoutGoingBelowThem() { constexpr std::size_t page = 0x4000; std::uintptr_t base = 0; std::size_t size = 0; GuestArena::GuestArenaRange_nid_postfix(&base, &size); const auto hint = base + page; auto* placed = GuestArena::GuestArenaAllocateAtOrAbove_nid_postfix(hint, page, page); Require(placed != nullptr); Require(reinterpret_cast(placed) >= hint); Require(GuestArena::GuestArenaContains_nid_postfix(placed, page)); GuestArena::GuestArenaRelease_nid_postfix(placed, page); } static void CheckFixedMappingsReachTheApplicationAreaEnd() { constexpr std::size_t page = 0x4000; std::uintptr_t base = 0; std::size_t size = 0; GuestArena::GuestArenaRange_nid_postfix(&base, &size); constexpr std::uintptr_t expectedOffset = #ifdef ANYPS5_ENABLE_ASAN 0x0000100000000000ull; #else 0; #endif constexpr std::uintptr_t applicationAreaEnd = 0xFC00000000ull + expectedOffset; Require(base == 0x200000000ull + expectedOffset && base + size == applicationAreaEnd); std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); void* const requested = reinterpret_cast(applicationAreaEnd - page * 2); void* mapped = requested; Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0x90, phys, 0) == 0); Require(mapped == requested); static_cast(mapped)[page * 2 - 1] = 7; void* alias = nullptr; Require(sceKernelMapDirectMemory(&alias, page, 3, 0, phys + page, 0) == 0); Require(static_cast(alias)[page - 1] == 7); VirtualQueryInfo info{}; Require(sceKernelVirtualQuery(mapped, 0, &info, sizeof(info)) == 0 && info.is_direct && info.end == applicationAreaEnd); void* beyond = reinterpret_cast(applicationAreaEnd); bool refused = false; try { refused = sceKernelMapDirectMemory(&beyond, page, 3, 0x10, phys, 0) != 0; } catch (const std::exception&) { refused = true; } Require(refused && beyond == reinterpret_cast(applicationAreaEnd)); Require(sceKernelMunmap(alias, page) == 0); Require(sceKernelMunmap(mapped, page * 2) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); } #ifdef _WIN32 static void CheckAliasOfReorderedSharedPages() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); void* mapped = nullptr; Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0, phys, 0) == 0); auto* bytes = static_cast(mapped); bytes[5] = 0x11; bytes[page + 5] = 0x22; void* low = mapped; Require(sceKernelMapDirectMemory(&low, page, 3, 0x10, phys + page, 0) == 0); void* high = static_cast(mapped) + page; Require(sceKernelMapDirectMemory(&high, page, 3, 0x10, phys, 0) == 0); Require(bytes[5] == 0x22 && bytes[page + 5] == 0x11); auto* alias = static_cast(GuestArena::GuestArenaMapAlias_nid_postfix(reinterpret_cast(mapped), page * 2)); Require(alias != nullptr && alias[5] == 0x22 && alias[page + 5] == 0x11); alias[6] = 0x33; alias[page + 6] = 0x44; Require(bytes[6] == 0x33 && bytes[page + 6] == 0x44); GuestArena::GuestArenaUnmapAlias_nid_postfix(const_cast(alias)); Require(bytes[5] == 0x22 && bytes[page + 5] == 0x11); Require(sceKernelMunmap(mapped, page * 2) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); } static void CheckAliasAcrossDirectAllocations() { constexpr std::size_t page = 0x4000; std::int64_t first = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &first) == 0); std::int64_t second = 0; Require(sceKernelAllocateDirectMemory(first + page * 2, first + page * 3, page, 0, 0, &second) == 0 && second == first + page * 2); void* mapped = nullptr; Require(sceKernelMapDirectMemory(&mapped, page * 3, 3, 0, first, 0) == 0); auto* bytes = static_cast(mapped); for (std::size_t index = 0; index < 3; ++index) bytes[page * index + 5] = static_cast(0x11 * (index + 1)); auto* alias = static_cast(GuestArena::GuestArenaMapAlias_nid_postfix(reinterpret_cast(mapped), page * 3)); Require(alias != nullptr); MEMORY_BASIC_INFORMATION run{}; Require(VirtualQuery(alias, &run, sizeof(run)) == sizeof(run) && run.RegionSize == page * 2); Require(VirtualQuery(alias + page * 2, &run, sizeof(run)) == sizeof(run) && run.AllocationBase == alias + page * 2 && run.RegionSize == page); auto* through = static_cast(alias); for (std::size_t index = 0; index < 3; ++index) { Require(through[page * index + 5] == 0x11 * (index + 1)); through[page * index + 6] = static_cast(0x44 + index); Require(bytes[page * index + 6] == 0x44 + index); } GuestArena::GuestArenaUnmapAlias_nid_postfix(alias); for (std::size_t offset = 0; offset < page * 3; offset += page) { MEMORY_BASIC_INFORMATION released{}; Require(VirtualQuery(alias + offset, &released, sizeof(released)) == sizeof(released) && released.State == MEM_FREE); } Require(bytes[5] == 0x11 && bytes[page + 5] == 0x22 && bytes[page * 2 + 5] == 0x33); Require(sceKernelMunmap(mapped, page * 3) == 0); Require(sceKernelReleaseDirectMemory(first, page * 3) == 0); } static void CheckAliasRefusesPagesThatAreNotSharedViews() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &phys) == 0); void* reserved = nullptr; Require(sceKernelReserveVirtualRange(&reserved, page * 2, 0, 0) == 0); void* shared = reserved; Require(sceKernelMapDirectMemory(&shared, page, 3, 0x10, phys, 0) == 0); void* privatePage = static_cast(reserved) + page; Require(sceKernelMapFlexibleMemory(&privatePage, page, 3, 0x10) == 0); bool refused = false; try { GuestArena::GuestArenaMapAlias_nid_postfix(reinterpret_cast(reserved), page * 2); } catch (const std::runtime_error&) { refused = true; } Require(refused); Require(sceKernelMunmap(reserved, page * 2) == 0); Require(sceKernelReleaseDirectMemory(phys, page) == 0); } #endif #if defined(__linux__) && !defined(ANYPS5_ENABLE_ASAN) static std::size_t LockedKilobytes() { std::ifstream status("/proc/self/status"); std::string line; while (std::getline(status, line)) { if (line.rfind("VmLck:", 0) == 0) return std::strtoull(line.c_str() + 6, nullptr, 10); } return 0; } #endif static void CheckMlock() { constexpr std::size_t page = 0x4000; #ifdef _WIN32 constexpr std::size_t length = 0x400000; #else constexpr std::size_t length = 0x10000; #endif constexpr int outOfMemory = static_cast(0x8002000cu); constexpr int invalid = static_cast(0x80020016u); void* mapped = nullptr; Require(sceKernelMapFlexibleMemory(&mapped, length, 3, 0) == 0); auto* bytes = static_cast(mapped); Require(sceKernelMlock_nid_postfix(mapped, 0) == 0); #if defined(__linux__) && !defined(ANYPS5_ENABLE_ASAN) const auto lockedBefore = LockedKilobytes(); #endif Require(sceKernelMlock_nid_postfix(bytes + 1, length - page) == 0); #ifdef _WIN32 SIZE_T minimum = 0; SIZE_T maximum = 0; DWORD limits = 0; Require(GetProcessWorkingSetSizeEx(GetCurrentProcess(), &minimum, &maximum, &limits) && minimum >= length && maximum > minimum); Require(VirtualUnlock(mapped, length)); Require(!VirtualUnlock(mapped, length) && GetLastError() == ERROR_NOT_LOCKED); #elif defined(__linux__) && !defined(ANYPS5_ENABLE_ASAN) Require(LockedKilobytes() - lockedBefore == length / 1024); #endif Require(sceKernelMlock_nid_postfix(mapped, length) == 0); Require(sceKernelMlock_nid_postfix(mapped, length) == 0); bytes[length - 1] = 7; Require(sceKernelMlock_nid_postfix(reinterpret_cast(std::numeric_limits::max() - page + 1), page * 2) == invalid); void* reserved = nullptr; Require(sceKernelReserveVirtualRange(&reserved, page, 0, 0) == 0); #ifndef ANYPS5_ENABLE_ASAN Require(sceKernelMlock_nid_postfix(reserved, page) == outOfMemory); #endif Require(sceKernelMunmap(reserved, page) == 0); Require(sceKernelMunmap(mapped, length) == 0); #ifndef ANYPS5_ENABLE_ASAN Require(sceKernelMlock_nid_postfix(mapped, page) == outOfMemory); #endif } static void CheckSharedDirectMemoryLifecycle() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 3, 0, 0, &phys) == 0); void* first = nullptr; void* second = nullptr; Require(sceKernelMapDirectMemory(&first, page * 3, 3, 0, phys, 0) == 0); Require(sceKernelMapDirectMemory(&second, page * 3, 3, 0, phys, 0) == 0); VirtualQueryInfo info{}; Require(sceKernelVirtualQuery(second, 0, &info, sizeof(info)) == 0); Require(info.is_direct && !info.is_flexible && info.offset == static_cast(phys)); auto* left = static_cast(first); auto* right = static_cast(second); left[0] = 31; right[page] = 47; left[page * 2] = 63; Require(right[0] == 31 && left[page] == 47 && right[page * 2] == 63); void* inaccessible = nullptr; Require(sceKernelMapDirectMemory(&inaccessible, page, 0, 0, phys + page, 0) == 0); left[page] = 48; Require(sceKernelMprotect(inaccessible, page, 1) == 0); Require(static_cast(inaccessible)[0] == 48); Require(sceKernelMunmap(inaccessible, page) == 0); Require(sceKernelMunmap(left + page, page) == 0); right[page] = 79; Require(left[0] == 31 && left[page * 2] == 63); void* middle = left + page; Require(sceKernelMapDirectMemory(&middle, page, 3, 0x10, phys + page, 0) == 0); Require(left[page] == 79); Require(sceKernelVirtualQuery(middle, 0, &info, sizeof(info)) == 0); Require(info.offset == static_cast(phys) + page && info.start == reinterpret_cast(middle)); Require(sceKernelMprotect(second, page * 3, 0) == 0); left[page] = 95; Require(sceKernelMprotect(second, page * 3, 1) == 0); Require(right[page] == 95); Require(sceKernelMunmap(first, page) == 0); Require(sceKernelMunmap(left + page * 2, page) == 0); Require(sceKernelMunmap(middle, page) == 0); Require(sceKernelMprotect(second, page * 3, 3) == 0); right[page * 2] = 111; void* reserved = nullptr; Require(sceKernelReserveVirtualRange(&reserved, page * 3, 0, 0) == 0); void* fixed = static_cast(reserved) + page; Require(sceKernelMapDirectMemory(&fixed, page, 3, 0x10, phys + page * 2, 0) == 0); Require(static_cast(fixed)[0] == 111); static_cast(fixed)[0] = 127; Require(right[page * 2] == 127); Require(sceKernelMapFlexibleMemory(&fixed, page, 3, 0x10) == 0); Require(static_cast(fixed)[0] == 0); Require(sceKernelVirtualQuery(fixed, 0, &info, sizeof(info)) == 0); Require(!info.is_direct && info.is_flexible && info.offset == 0); static_cast(fixed)[0] = 143; Require(right[page * 2] == 127); Require(sceKernelMunmap(reserved, page * 3) == 0); Require(sceKernelMunmap(second, page * 3) == 0); Require(sceKernelReleaseDirectMemory(phys + page, page) == 0); std::int64_t replacement = 0; Require(sceKernelAllocateDirectMemory(phys + page, phys + page * 2, page, 0, 0, &replacement) == 0); Require(replacement == phys + page); void* mixed = nullptr; Require(sceKernelMapDirectMemory(&mixed, page * 3, 3, 0, phys, 0) == 0); const auto* data = static_cast(mixed); Require(data[0] == 31 && data[page] == 0 && data[page * 2] == 127); Require(sceKernelMunmap(mixed, page * 3) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); } static void CheckHeapAlignment() { std::vector> blocks; for (std::size_t bytes = 1; bytes <= 600; ++bytes) blocks.emplace_back(static_cast(GuestHeap::GuestHeapAllocate_nid_postfix(bytes)), bytes); for (const std::size_t bytes : {std::size_t{4000}, std::size_t{70000}, std::size_t{0x30000}}) blocks.emplace_back(static_cast(GuestHeap::GuestHeapAllocate_nid_postfix(bytes)), bytes); auto* grown = static_cast(GuestHeap::GuestHeapReallocate_nid_postfix(GuestHeap::GuestHeapAllocate_nid_postfix(8), 333)); blocks.emplace_back(grown, 333); for (std::size_t index = 0; index < blocks.size(); ++index) { const auto [pointer, bytes] = blocks[index]; Require((reinterpret_cast(pointer) & 31u) == 0); std::memset(pointer, static_cast(index & 0xffu), bytes); } for (std::size_t index = 0; index < blocks.size(); ++index) { const auto [pointer, bytes] = blocks[index]; for (std::size_t at = 0; at < bytes; ++at) Require(pointer[at] == static_cast(index & 0xffu)); GuestHeap::GuestHeapFree_nid_postfix(pointer); } } static void CheckHeapAfterMappingReuse() { constexpr std::size_t bytes = 0x30000; auto* pointer = static_cast(GuestHeap::GuestHeapAllocate_nid_postfix(bytes)); std::memset(pointer, 0x5a, bytes); Require(pointer[0] == 0x5a && pointer[bytes - 1] == 0x5a); GuestHeap::GuestHeapFree_nid_postfix(pointer); pointer = static_cast(GuestHeap::GuestHeapAllocate_nid_postfix(bytes)); std::memset(pointer, 0xa5, bytes); Require(pointer[0] == 0xa5 && pointer[bytes - 1] == 0xa5); GuestHeap::GuestHeapFree_nid_postfix(pointer); } static void CheckSharedWriteTracking() { #ifdef _WIN32 constexpr std::size_t page = 0x4000; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 3, 0, 0, &phys) == 0); void* first = nullptr; void* second = nullptr; Require(sceKernelMapDirectMemory(&first, page * 3, 3, 0, phys, 0) == 0); Require(sceKernelMapDirectMemory(&second, page * 3, 3, 0, phys, 0) == 0); const auto collect = [](void* address, std::size_t bytes, bool clear = true) { std::array pages{}; std::size_t count = pages.size(); Require(GuestArena::GuestArenaCollectWrites_nid_postfix(reinterpret_cast(address), bytes, pages.data(), &count, clear)); return count; }; Require(collect(first, page * 3) == 12); Require(collect(second, page * 3) == 12); Require(collect(first, page * 3) == 0); Require(collect(second, page * 3) == 0); auto* left = static_cast(first); auto* right = static_cast(second); left[page + 5] = 21; Require(right[page + 5] == 21); Require(collect(first, page * 3, false) == 1); Require(collect(first, page * 3, false) == 1); Require(collect(first, page * 3) == 1); Require(collect(second, page * 3) == 1); Require(collect(first, page * 3) == 0); right[page * 2] = 42; Require(collect(first, page * 3) == 1); Require(collect(second, page * 3) == 1); Require(collect(second, page * 3) == 0); Require(sceKernelMprotect(first, page * 3, 1) == 0); collect(first, page * 3); collect(second, page * 3); right[0] = 63; Require(left[0] == 63 && collect(first, page * 3) == 1); Require(sceKernelMprotect(first, page * 3, 3) == 0); collect(first, page * 3); left[0] = 84; Require(right[0] == 84 && collect(second, page * 3) != 0); void* third = nullptr; Require(sceKernelMapDirectMemory(&third, page, 3, 0, phys + page, 0) == 0); collect(first, page * 3); collect(second, page * 3); static_cast(third)[0] = 105; Require(collect(first, page * 3) == 1 && collect(second, page * 3) == 1); Require(sceKernelMunmap(third, page) == 0); Require(sceKernelMunmap(first, page * 3) == 0); Require(sceKernelMunmap(second, page * 3) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); #endif } static void CheckSharedWritePages() { #ifdef _WIN32 constexpr std::size_t page = 0x4000; constexpr std::size_t small = 0x1000; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); void* mapped = nullptr; Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0, phys, 0) == 0); auto* bytes = static_cast(mapped); const auto collect = [&](std::size_t offset, std::size_t size) { std::array pages{}; std::size_t count = pages.size(); Require(GuestArena::GuestArenaCollectWrites_nid_postfix(reinterpret_cast(mapped) + offset, size, pages.data(), &count, true)); Require(count == 0 || reinterpret_cast(pages[0]) >= reinterpret_cast(mapped) + offset); return count; }; collect(0, page * 2); Require(collect(0, page * 2) == 0); static_cast(bytes)[small + 3] = 1; Require(collect(small * 2, small * 2) == 0); Require(collect(0, page * 2) == 1); Require(collect(0, page * 2) == 0); std::memset(bytes + small * 2 - 8, 7, 16); Require(collect(0, page * 2) == 2); Require(collect(0, page * 2) == 0); const auto path = std::filesystem::path("anyps5-shared-pages-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".bin"); { std::ofstream stream(path, std::ios::binary); const std::vector seed(small, 5); stream.write(seed.data(), static_cast(seed.size())); } const int fd = sceKernelOpen(path.string().c_str(), SCE_KERNEL_O_RDONLY, 0); Require(fd >= 0); Require(sceKernelPread(fd, bytes + page + small, small, 0) == static_cast(small)); Require(sceKernelClose(fd) == 0); Require(bytes[page + small] == 5 && bytes[page + small * 2 - 1] == 5); Require(collect(page, small) == 0); Require(collect(0, page * 2) == 1); Require(sceKernelMunmap(mapped, page * 2) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); std::filesystem::remove(path); #endif } static void CheckSharedWritesDuringHostWrite() { #ifdef _WIN32 constexpr std::size_t page = 0x4000; constexpr std::size_t small = 0x1000; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &phys) == 0); void* first = nullptr; void* second = nullptr; Require(sceKernelMapDirectMemory(&first, page, 3, 0, phys, 0) == 0); Require(sceKernelMapDirectMemory(&second, page, 3, 0, phys, 0) == 0); auto* left = static_cast(first); auto* right = static_cast(second); const auto collect = [](void* address) { std::array pages{}; std::size_t count = pages.size(); Require(GuestArena::GuestArenaCollectWrites_nid_postfix(reinterpret_cast(address), page, pages.data(), &count, true)); std::vector offsets; for (std::size_t i = 0; i < count; ++i) offsets.push_back(reinterpret_cast(pages[i]) - reinterpret_cast(address)); return offsets; }; const std::vector open{small, small * 2}; collect(first); collect(second); Require(collect(first).empty() && collect(second).empty()); left[small] = 1; auto* target = const_cast(left + small * 2); Require(GuestArena::GuestArenaBeginHostWrite_nid_postfix(target, small)); Require(collect(first) == open); Require(collect(second) == open); left[small + 8] = 2; Require(collect(first) == open); Require(collect(second) == open); GuestArena::GuestArenaEndHostWrite_nid_postfix(target, small); Require(collect(first) == open && collect(second) == open); Require(collect(first).empty() && collect(second).empty()); left[small + 16] = 3; Require(right[small + 16] == 3 && collect(second) == std::vector{small}); Require(sceKernelMunmap(first, page) == 0); Require(sceKernelMunmap(second, page) == 0); Require(sceKernelReleaseDirectMemory(phys, page) == 0); #endif } static void CheckFailedCollectKeepsWrites() { #ifdef _WIN32 constexpr std::size_t page = 0x4000; const auto collect = [](void* address, std::size_t bytes, std::size_t& count) { std::array pages{}; count = pages.size(); return GuestArena::GuestArenaCollectWrites_nid_postfix(reinterpret_cast(address), bytes, pages.data(), &count, true); }; std::size_t count = 0; auto* block = static_cast(GuestArena::GuestArenaAllocate_nid_postfix(page * 3, page)); GuestArena::GuestArenaCommit_nid_postfix(block, page, PAGE_READWRITE, page); GuestArena::GuestArenaCommit_nid_postfix(block + page * 2, page, PAGE_READWRITE, page); Require(collect(block, page, count) && collect(block + page * 2, page, count)); static_cast(block)[8] = 1; Require(!collect(block, page * 3, count)); Require(collect(block, page, count) && count == 1); GuestArena::GuestArenaRelease_nid_postfix(block, page * 3); std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); void* mapped = nullptr; Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0, phys, 0) == 0); auto* shared = static_cast(mapped); Require(collect(mapped, page * 2, count)); shared[8] = 2; Require(sceKernelMprotect(const_cast(shared + page), page, 0) == 0); Require(!collect(mapped, page * 2, count)); Require(collect(mapped, page, count) && count == 1); Require(sceKernelMunmap(mapped, page * 2) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); #endif } static void CheckPinnedSharedPages() { #ifdef _WIN32 constexpr std::size_t page = 0x4000; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 3, 0, 0, &phys) == 0); void* mapped = nullptr; Require(sceKernelMapDirectMemory(&mapped, page * 3, 3, 0, phys, 0) == 0); auto* bytes = static_cast(mapped); const auto collect = [&] { std::array pages{}; std::size_t count = pages.size(); Require(GuestArena::GuestArenaCollectWrites_nid_postfix(reinterpret_cast(mapped), page * 3, pages.data(), &count, true)); return count; }; const auto protection = [&](std::size_t offset) { MEMORY_BASIC_INFORMATION info{}; Require(VirtualQuery(const_cast(bytes + offset), &info, sizeof(info)) == sizeof(info)); return info.Protect; }; collect(); Require(collect() == 0); Require(protection(0) == PAGE_READONLY && protection(page) == PAGE_READONLY); GuestArena::GuestArenaPinWritable_nid_postfix(const_cast(bytes + page), page); Require(protection(page) == PAGE_READWRITE && protection(0) == PAGE_READONLY && protection(page * 2) == PAGE_READONLY); Require(collect() == 4); Require(collect() == 4); Require(protection(page) == PAGE_READWRITE); bytes[page + 8] = 7; bytes[0] = 9; Require(collect() == 5); Require(collect() == 4); Require(protection(0) == PAGE_READONLY && protection(page) == PAGE_READWRITE); GuestArena::GuestArenaUnpinWritable_nid_postfix(const_cast(bytes + page), page); collect(); Require(collect() == 0); Require(protection(page) == PAGE_READONLY); bytes[page + 8] = 11; Require(collect() == 1); Require(sceKernelMunmap(mapped, page * 3) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); #endif } static void CheckReadsIntoSharedWriteTracking() { #ifdef _WIN32 constexpr std::size_t page = 0x4000; const auto path = std::filesystem::path("anyps5-tracked-read-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + ".bin"); std::vector seed(page * 2); for (std::size_t i = 0; i < seed.size(); ++i) seed[i] = static_cast(i * 7 + 1); { std::ofstream stream(path, std::ios::binary); stream.write(seed.data(), static_cast(seed.size())); } std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 2, 0, 0, &phys) == 0); void* mapped = nullptr; Require(sceKernelMapDirectMemory(&mapped, page * 2, 3, 0, phys, 0) == 0); auto* bytes = static_cast(mapped); const auto collect = [&] { std::array pages{}; std::size_t count = pages.size(); Require(GuestArena::GuestArenaCollectWrites_nid_postfix(reinterpret_cast(mapped), page * 2, pages.data(), &count, true)); return count; }; const int fd = sceKernelOpen(path.string().c_str(), SCE_KERNEL_O_RDONLY, 0); Require(fd >= 0); collect(); Require(collect() == 0); Require(sceKernelPread(fd, mapped, page * 2, 0) == static_cast(page * 2)); Require(std::memcmp(bytes, seed.data(), page * 2) == 0); Require(collect() == 8); const int sequential = sceKernelOpen(path.string().c_str(), SCE_KERNEL_O_RDONLY, 0); Require(sequential >= 0); Require(sceKernelRead(sequential, bytes + page, page) == static_cast(page)); Require(sceKernelClose(sequential) == 0); Require(std::memcmp(bytes + page, seed.data(), page) == 0); Require(collect() == 4); Require(sceKernelAioInitializeImpl(nullptr, 0) == 0); KernelAioResult result{-1, 0}; KernelAioRwRequest request{static_cast(page), page, bytes, &result, fd}; std::int32_t id = 0; std::int32_t state = 0; Require(sceKernelAioSubmitReadCommands(&request, 1, 0, &id) == 0); Require(sceKernelAioWaitRequest(id, &state, nullptr) == 0); Require(result.state == 3 && result.return_value == static_cast(page)); Require(std::memcmp(bytes, seed.data() + page, page) == 0); Require(collect() == 4); std::int32_t deleted = -1; Require(sceKernelAioDeleteRequest(id, &deleted) == 0); Require(sceKernelMprotect(mapped, page * 2, 1) == 0); Require(sceKernelPread(fd, mapped, page, 0) == static_cast(SCE_KERNEL_ERROR_EFAULT)); KernelAioResult refusedResult{-1, 0}; KernelAioRwRequest refusedRequest{0, page, bytes, &refusedResult, fd}; Require(sceKernelAioSubmitReadCommands(&refusedRequest, 1, 0, &id) == 0); Require(sceKernelAioWaitRequest(id, &state, nullptr) == 0); Require(refusedResult.return_value == static_cast(SCE_KERNEL_ERROR_EFAULT)); Require(sceKernelAioDeleteRequest(id, &deleted) == 0); Require(sceKernelClose(fd) == 0); Require(sceKernelMunmap(mapped, page * 2) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 2) == 0); std::filesystem::remove(path); #endif } #if defined(__linux__) using PageRuns = std::vector>; static bool CollectRuns(const void* base, std::size_t offset, std::size_t bytes, PageRuns& runs) { runs.clear(); const auto address = reinterpret_cast(base); std::pair context{address, &runs}; return GuestWriteWatch::GuestWriteWatchCollect_nid_postfix(address + offset, bytes, [](void* context, std::uintptr_t begin, std::uintptr_t end) { auto& [origin, into] = *static_cast*>(context); if (!into->empty() && into->back().second == (begin - origin) / 4096) into->back().second = (end - origin) / 4096; else into->emplace_back((begin - origin) / 4096, (end - origin) / 4096); }, &context); } static bool Written(const void* base, std::size_t bytes, PageRuns expected) { PageRuns runs; const bool complete = CollectRuns(base, 0, bytes, runs); if (complete && runs == expected) return true; std::fprintf(stderr, "write watch collect %s, written pages:", complete ? "complete" : "incomplete"); for (const auto& [first, last] : runs) std::fprintf(stderr, " [%zu, %zu)", static_cast(first), static_cast(last)); std::fputs("\n", stderr); return false; } static bool CollectArmedRuns(const void* base, std::size_t offset, std::size_t bytes, PageRuns& runs) { runs.clear(); const auto address = reinterpret_cast(base); std::pair context{address, &runs}; return GuestWriteWatch::GuestWriteWatchCollectArmed_nid_postfix(address + offset, bytes, [](void* context, std::uintptr_t begin, std::uintptr_t end) { auto& [origin, into] = *static_cast*>(context); if (!into->empty() && into->back().second == (begin - origin) / 4096) into->back().second = (end - origin) / 4096; else into->emplace_back((begin - origin) / 4096, (end - origin) / 4096); }, &context); } static void CheckWriteWatchArmedCollect() { if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) return; constexpr std::size_t length = 0x100000; constexpr std::size_t small = 4096; void* mapping = nullptr; Require(sceKernelMapFlexibleMemory(&mapping, length, 3, 0) == 0); auto* bytes = static_cast(mapping); PageRuns runs; Require(CollectRuns(mapping, 0, length / 2, runs) && runs == PageRuns{{0, length / small / 2}}); bytes[5 * small] = 1; bytes[200 * small] = 1; Require(CollectArmedRuns(mapping, 0, length, runs) && runs == PageRuns{{5, 6}}); Require(Written(mapping, length, {{length / small / 2, length / small}})); Require(Written(mapping, length, {})); Require(sceKernelMunmap(mapping, length) == 0); } static bool CollectFreshRuns(const void* base, std::size_t offset, std::size_t bytes, PageRuns& runs) { runs.clear(); const auto address = reinterpret_cast(base); std::pair context{address, &runs}; return GuestWriteWatch::GuestWriteWatchCollectFresh_nid_postfix(address + offset, bytes, [](void* context, std::uintptr_t begin, std::uintptr_t end) { auto& [origin, into] = *static_cast*>(context); if (!into->empty() && into->back().second == (begin - origin) / 4096) into->back().second = (end - origin) / 4096; else into->emplace_back((begin - origin) / 4096, (end - origin) / 4096); }, &context); } static void CheckWriteWatchFreshCollect() { if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) return; constexpr std::size_t length = 0x100000; constexpr std::size_t small = 4096; void* mapping = nullptr; Require(sceKernelMapFlexibleMemory(&mapping, length, 3, 0) == 0); auto* bytes = static_cast(mapping); PageRuns runs; Require(CollectRuns(mapping, 0, length / 2, runs) && runs == PageRuns{{0, length / small / 2}}); bytes[5 * small] = 1; Require(CollectFreshRuns(mapping, 0, length, runs) && runs == PageRuns{{length / small / 2, length / small}}); Require(CollectFreshRuns(mapping, 0, length, runs) && runs.empty()); bytes[200 * small] = 1; Require(Written(mapping, length, {{5, 6}, {200, 201}})); Require(sceKernelMunmap(mapping, length) == 0); } static void CheckWriteWatch() { if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) { std::puts("write watch unavailable: not tested"); return; } constexpr std::size_t length = 0x100000; constexpr std::size_t small = 4096; void* mapping = nullptr; Require(sceKernelMapFlexibleMemory(&mapping, length, 3, 0) == 0); auto* bytes = static_cast(mapping); const auto address = reinterpret_cast(mapping); Require(GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address, length)); Require(GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address + small, small)); Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address, length + small)); Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(reinterpret_cast(&length), sizeof(length))); Require(Written(mapping, length, {{0, length / small}})); Require(Written(mapping, length, {})); bytes[5 * small + 17] = 1; Require(Written(mapping, length, {{5, 6}})); Require(Written(mapping, length, {})); static_cast(bytes[10 * small]); Require(Written(mapping, length, {})); bytes[7 * small] = 1; bytes[9 * small] = 1; PageRuns runs; Require(CollectRuns(mapping, 8 * small, small, runs) && runs.empty()); Require(CollectRuns(mapping, 7 * small + 100, 1, runs) && runs == PageRuns{{7, 8}}); Require(Written(mapping, length, {{9, 10}})); int pipe[2]; Require(::pipe(pipe) == 0); Require(::write(pipe[1], "kernel", 6) == 6); Require(::read(pipe[0], const_cast(bytes + 20 * small + 8), 6) == 6); ::close(pipe[0]); ::close(pipe[1]); Require(bytes[20 * small + 8] == 'k' && Written(mapping, length, {{20, 21}})); std::thread([&] { bytes[30 * small + 5] = 3; }).join(); Require(Written(mapping, length, {{30, 31}})); std::vector source(2 * small, 0xab); std::memcpy(const_cast(bytes + 40 * small + 2048), source.data(), source.size()); Require(Written(mapping, length, {{40, 43}})); Require(sceKernelMprotect(mapping, length, 1) == 0); Require(sceKernelMprotect(mapping, length, 3) == 0); Require(Written(mapping, length, {})); bytes[50 * small] = 1; Require(Written(mapping, length, {{50, 51}})); constexpr std::size_t guestPage = 0x4000; auto* middle = const_cast(bytes + 4 * guestPage); Require(sceKernelMunmap(middle, guestPage) == 0); Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address, length)); Require(!CollectRuns(mapping, 0, length, runs) && runs.empty()); void* fixed = middle; Require(sceKernelMapFlexibleMemory(&fixed, guestPage, 3, 0x10) == 0 && fixed == middle); Require(GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address, length)); Require(Written(mapping, length, {{16, 20}})); Require(Written(mapping, length, {})); middle[1] = 1; Require(Written(mapping, length, {{16, 17}})); Require(sceKernelMunmap(mapping, length) == 0); Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address, small)); constexpr std::size_t tableSpan = 0x200000; constexpr std::size_t spanned = 2 * tableSpan; void* raw = mmap(nullptr, spanned + tableSpan, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); Require(raw != MAP_FAILED); const auto rawAddress = reinterpret_cast(raw); void* region = reinterpret_cast((rawAddress + tableSpan - 1) & ~(tableSpan - 1)); GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(region, spanned); Require(GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(reinterpret_cast(region), spanned)); Require(Written(region, spanned, {{0, spanned / small}})); Require(Written(region, spanned, {})); static_cast(region)[tableSpan + 3 * small] = 1; static_cast(region)[7 * small] = 1; Require(Written(region, spanned, {{7, 8}, {tableSpan / small + 3, tableSpan / small + 4}})); Require(Written(region, spanned, {})); GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(region, spanned); Require(munmap(raw, spanned + tableSpan) == 0); } static std::string BackingOf(const void* address) { std::ifstream maps("/proc/self/maps"); std::string line; while (std::getline(maps, line)) { if (std::strtoull(line.c_str(), nullptr, 16) != reinterpret_cast(address)) continue; const auto path = line.find('/'); return path == std::string::npos ? std::string() : line.substr(path); } return {}; } static void CheckDirectMemoryBackingNeedsNoFilesystem() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &phys) == 0); void* mapped = nullptr; Require(sceKernelMapDirectMemory(&mapped, page, 3, 0, phys, 0) == 0); const auto backing = BackingOf(mapped); if (backing.rfind("/memfd:", 0) != 0) std::fprintf(stderr, "direct memory backing: %s\n", backing.c_str()); Require(backing.rfind("/memfd:", 0) == 0); Require(sceKernelMunmap(mapped, page) == 0); Require(sceKernelReleaseDirectMemory(phys, page) == 0); } static void CheckDirectMemorySharedBacking() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 3, 0, 0, &phys) == 0); void* first = nullptr; Require(sceKernelMapDirectMemory(&first, page * 2, 3, 0, phys + page, 0) == 0); const auto address = reinterpret_cast(first); static_cast(first)[page + 5] = 0x5a; int file = -1; std::uint64_t offset = 0; const auto base = static_cast(phys); Require(GuestArena::GuestArenaSharedBacking_nid_postfix(address + page, page, &file, &offset)); Require(offset == base + page * 2); unsigned char byte = 0; Require(pread(file, &byte, 1, static_cast(offset + 5)) == 1 && byte == 0x5a); const int seals = fcntl(file, F_GET_SEALS); Require(seals >= 0 && (seals & F_SEAL_SHRINK) != 0 && (seals & F_SEAL_GROW) != 0); Require((fcntl(file, F_GETFD) & FD_CLOEXEC) != 0); close(file); Require(GuestArena::GuestArenaSharedBacking_nid_postfix(address, page * 2, &file, &offset) && offset == base + page); close(file); Require(GuestArena::GuestArenaSharedBacking_nid_postfix(address + page + 0x1000, 0x1000, &file, &offset) && offset == page * 2 + 0x1000); close(file); Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(address, page * 3, &file, &offset)); Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(address, 0, &file, &offset)); void* flexible = nullptr; Require(sceKernelMapFlexibleMemory(&flexible, page * 2, 3, 0) == 0); const auto flexibleAddress = reinterpret_cast(flexible); Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(flexibleAddress, page, &file, &offset)); void* low = flexible; Require(sceKernelMapDirectMemory(&low, page, 3, 0x10, phys, 0) == 0); void* high = static_cast(flexible) + page; Require(sceKernelMapDirectMemory(&high, page, 3, 0x10, phys + page, 0) == 0); Require(GuestArena::GuestArenaSharedBacking_nid_postfix(flexibleAddress, page * 2, &file, &offset) && offset == base); close(file); Require(sceKernelMapDirectMemory(&high, page, 3, 0x10, phys + page * 2, 0) == 0); Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(flexibleAddress, page * 2, &file, &offset)); Require(GuestArena::GuestArenaSharedBacking_nid_postfix(flexibleAddress + page, page, &file, &offset) && offset == base + page * 2); close(file); Require(sceKernelMunmap(high, page) == 0); Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(flexibleAddress + page, page, &file, &offset)); Require(sceKernelMunmap(flexible, page) == 0); Require(sceKernelMunmap(first, page * 2) == 0); Require(!GuestArena::GuestArenaSharedBacking_nid_postfix(address, page, &file, &offset)); Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); std::int64_t left = 0; std::int64_t right = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &left) == 0); Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page, 0, 0, &right) == 0); void* leftView = nullptr; void* rightView = nullptr; Require(sceKernelMapDirectMemory(&leftView, page, 3, 0, left, 0) == 0); Require(sceKernelMapDirectMemory(&rightView, page, 3, 0, right, 0) == 0); int leftFile = -1; int rightFile = -1; std::uint64_t leftOffset = 0; std::uint64_t rightOffset = 0; Require(GuestArena::GuestArenaSharedBacking_nid_postfix(reinterpret_cast(leftView), page, &leftFile, &leftOffset) && leftOffset == static_cast(left)); Require(GuestArena::GuestArenaSharedBacking_nid_postfix(reinterpret_cast(rightView), page, &rightFile, &rightOffset) && rightOffset == static_cast(right)); struct stat leftInfo {}; struct stat rightInfo {}; Require(fstat(leftFile, &leftInfo) == 0 && fstat(rightFile, &rightInfo) == 0 && leftInfo.st_ino == rightInfo.st_ino && leftInfo.st_dev == rightInfo.st_dev); close(leftFile); close(rightFile); std::memset(leftView, 0x77, page); Require(sceKernelMunmap(leftView, page) == 0); Require(sceKernelReleaseDirectMemory(left, page) == 0); std::int64_t again = 0; Require(sceKernelAllocateDirectMemory(left, left + static_cast(page), page, 0, 0, &again) == 0 && again == left); void* againView = nullptr; Require(sceKernelMapDirectMemory(&againView, page, 3, 0, again, 0) == 0); for (std::size_t i = 0; i < page; ++i) Require(static_cast(againView)[i] == 0); Require(sceKernelMunmap(againView, page) == 0); Require(sceKernelMunmap(rightView, page) == 0); Require(sceKernelReleaseDirectMemory(again, page) == 0); Require(sceKernelReleaseDirectMemory(right, page) == 0); } static void CheckDirectMemoryWriteWatch() { if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) { std::puts("write watch unavailable: direct memory not tested"); return; } constexpr std::size_t page = 0x4000; constexpr std::size_t small = 4096; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 3, 0, 0, &phys) == 0); void* first = nullptr; Require(sceKernelMapDirectMemory(&first, page * 3, 3, 0, phys, 0) == 0); const auto address = reinterpret_cast(first); auto* bytes = static_cast(first); Require(GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address, page * 3)); Require(Written(first, page * 3, {{0, page * 3 / small}})); Require(Written(first, page * 3, {})); bytes[page + 5] = 1; Require(Written(first, page * 3, {{page / small, page / small + 1}})); void* alias = nullptr; Require(sceKernelMapDirectMemory(&alias, page, 3, 0, phys + page, 0) == 0); const auto aliasAddress = reinterpret_cast(alias); Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(aliasAddress, page)); Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address + page, page)); Require(GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address, page) && GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address + page * 2, page)); PageRuns runs; Require(!CollectRuns(first, 0, page * 3, runs)); bytes[0] = 2; static_cast(alias)[7] = 3; Require(bytes[page + 7] == 3); Require(CollectRuns(first, 0, page, runs) && runs == PageRuns{{0, 1}}); Require(CollectRuns(first, page * 2, page, runs) && runs.empty()); Require(sceKernelMunmap(alias, page) == 0); Require(sceKernelMunmap(first, page * 3) == 0); Require(!GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(address, page)); void* flexible = nullptr; Require(sceKernelMapFlexibleMemory(&flexible, page * 3, 3, 0) == 0); Require(Written(flexible, page * 3, {{0, page * 3 / small}})); Require(Written(flexible, page * 3, {})); void* fixed = static_cast(flexible) + page; Require(sceKernelMapDirectMemory(&fixed, page, 3, 0x10, phys + page * 2, 0) == 0); Require(fixed == static_cast(flexible) + page); Require(GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(reinterpret_cast(flexible), page * 3)); Require(Written(flexible, page * 3, {{page / small, page * 2 / small}})); Require(Written(flexible, page * 3, {})); static_cast(fixed)[9] = 4; Require(Written(flexible, page * 3, {{page / small, page / small + 1}})); Require(sceKernelMunmap(flexible, page * 3) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); } static void CheckDirectMemoryWriteWatchFollowsRemovedMappings() { if (!GuestWriteWatch::GuestWriteWatchAvailable_nid_postfix()) { std::puts("write watch unavailable: removed direct mappings not tested"); return; } constexpr std::size_t page = 0x4000; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 4, 0, 0, &phys) == 0); void* whole = nullptr; Require(sceKernelMapDirectMemory(&whole, page * 4, 3, 0, phys, 0) == 0); const auto address = reinterpret_cast(whole); const auto watched = [](const void* pointer, std::size_t offset) { return GuestWriteWatch::GuestWriteWatchCovers_nid_postfix(reinterpret_cast(pointer) + offset, page); }; void* pair = nullptr; Require(sceKernelMapDirectMemory(&pair, page * 2, 3, 0, phys + page, 0) == 0); Require(watched(whole, 0) && !watched(whole, page) && !watched(whole, page * 2) && watched(whole, page * 3)); Require(!watched(pair, 0) && !watched(pair, page)); Require(sceKernelMunmap(pair, page) == 0); void* single = nullptr; Require(sceKernelMapDirectMemory(&single, page, 3, 0, phys + page, 0) == 0); Require(!watched(single, 0) && watched(whole, 0) && watched(whole, page * 3)); Require(sceKernelMunmap(single, page) == 0); Require(sceKernelMunmap(static_cast(pair) + page, page) == 0); void* replaced = whole; Require(sceKernelMapDirectMemory(&replaced, page, 3, 0x10, phys + page * 3, 0) == 0 && replaced == whole); Require(!watched(whole, 0) && !watched(whole, page * 3)); std::uintptr_t start = 0; std::uintptr_t end = 0; std::uint64_t offset = 0; int type = 0; Require(QueryDirectMapping(address, &start, &end, &offset, &type) && start == address && end == address + page && offset == static_cast(phys) + page * 3); void* alone = nullptr; Require(sceKernelMapDirectMemory(&alone, page, 3, 0, phys, 0) == 0); Require(watched(alone, 0)); void* again = nullptr; Require(sceKernelMapDirectMemory(&again, page, 3, 0, phys, 0) == 0); Require(!watched(alone, 0) && !watched(again, 0)); Require(sceKernelMunmap(again, page) == 0); Require(sceKernelMunmap(alone, page) == 0); Require(sceKernelMunmap(whole, page * 4) == 0); Require(sceKernelReleaseDirectMemory(phys, page * 4) == 0); } static double MapAcrossBackingsSeconds(std::int64_t phys, std::size_t backings) { constexpr std::size_t page = 0x4000; const auto threadSeconds = [] { timespec now{}; Require(clock_gettime(CLOCK_THREAD_CPUTIME_ID, &now) == 0); return static_cast(now.tv_sec) + static_cast(now.tv_nsec) * 1e-9; }; auto best = std::numeric_limits::max(); for (int attempt = 0; attempt < 3; ++attempt) { void* mapped = nullptr; const auto before = threadSeconds(); Require(sceKernelMapDirectMemory(&mapped, page * backings, 3, 0, phys, 0) == 0); best = std::min(best, threadSeconds() - before); const auto last = reinterpret_cast(mapped) + page * (backings - 1); std::uintptr_t start = 0; std::uintptr_t end = 0; std::uint64_t offset = 0; int type = 0; Require(QueryDirectMapping(last, &start, &end, &offset, &type) && start == last && end == last + page); Require(sceKernelMunmap(mapped, page * backings) == 0); } return best; } static void CheckMappingCostFollowsTheMappingCount() { constexpr std::size_t page = 0x4000; constexpr std::size_t few = 1024; constexpr std::size_t many = few * 16; std::int64_t phys = 0; Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * many, 0, 0, &phys) == 0); Require(sceKernelReleaseDirectMemory(phys, page * many) == 0); for (std::size_t index = 0; index < many; ++index) { const auto wanted = phys + static_cast(page * index); std::int64_t piece = 0; Require(sceKernelAllocateDirectMemory(wanted, wanted + static_cast(page), page, 0, 0, &piece) == 0 && piece == wanted); } const auto small = MapAcrossBackingsSeconds(phys, few); const auto large = MapAcrossBackingsSeconds(phys, many); Require(large <= small * 48); Require(sceKernelReleaseDirectMemory(phys, page * many) == 0); } #endif static void CheckVirtualQuerySplitFlexibleRanges() { constexpr std::size_t page = 0x4000; void* mapped = nullptr; Require(sceKernelMapFlexibleMemory(&mapped, page * 3, 3, 0) == 0); auto* bytes = static_cast(mapped); bytes[0] = 17; bytes[page * 2] = 29; Require(sceKernelMprotect(bytes + page, page, 1) == 0); for (std::size_t index = 0; index < 3; ++index) { VirtualQueryInfo info{}; Require(sceKernelVirtualQuery(bytes + page * index, 0, &info, sizeof(info)) == 0); Require(info.start == reinterpret_cast(bytes + page * index)); Require(info.end == info.start + page); Require(info.protection == (index == 1 ? 1 : 3)); Require(info.is_committed && info.is_flexible && !info.is_direct); } Require(sceKernelMunmap(bytes + page, page) == 0); for (std::size_t index : {std::size_t{0}, std::size_t{2}}) { VirtualQueryInfo info{}; Require(sceKernelVirtualQuery(bytes + page * index, 0, &info, sizeof(info)) == 0); Require(info.start == reinterpret_cast(bytes + page * index)); Require(info.end == info.start + page); } VirtualQueryInfo next{}; Require(sceKernelVirtualQuery(bytes + page, 1, &next, sizeof(next)) == 0); Require(next.start == reinterpret_cast(bytes + page * 2)); Require(next.end == next.start + page && next.protection == 3); Require(bytes[0] == 17 && bytes[page * 2] == 29); Require(sceKernelMunmap(bytes, page) == 0); Require(sceKernelMunmap(bytes + page * 2, page) == 0); } static void CheckVirtualQueryForNonReadableGuestRange() { constexpr std::size_t page = 0x4000; auto* mapped = static_cast(mmap_nid_postfix(nullptr, page, 3, 0x1002, -1, 0)); const auto failed = reinterpret_cast(static_cast(-1)); Require(mapped != failed); Require(mprotect_nid_postfix(mapped, page, 0) == 0); VirtualQueryInfo info{}; Require(sceKernelVirtualQuery(mapped, 0, &info, sizeof(info)) == 0); Require(info.start == reinterpret_cast(mapped)); Require(info.end == info.start + page && info.protection == 0); Require(sceKernelMunmap(mapped, page) == 0); } static void CheckSceKernelMmap() { constexpr std::size_t page = 0x4000; void* mapped = nullptr; Require(sceKernelMmap(nullptr, page, 3, 0x1002, -1, 0, &mapped) == 0 && mapped != nullptr); static_cast(mapped)[page - 1] = 7; Require(static_cast(mapped)[page - 1] == 7); Require(sceKernelMunmap(mapped, page) == 0); void* untouched = &mapped; Require(sceKernelMmap(nullptr, 0, 3, 0x1002, -1, 0, &untouched) == static_cast(0x80020016u)); Require(untouched == &mapped); } static void CheckVirtualQueryPartialMunmap() { constexpr std::size_t page = 0x4000; const auto failed = reinterpret_cast(static_cast(-1)); void* raw = mmap_nid_postfix(nullptr, page * 3, 3, 0x1002, -1, 0); Require(raw != failed); auto* memory = static_cast(raw); VirtualQueryInfo before{}; Require(sceKernelVirtualQuery(memory, 0, &before, sizeof(before)) == 0); Require(before.start == reinterpret_cast(memory)); Require(before.end == reinterpret_cast(memory) + page * 3); Require(munmap_nid_postfix(memory + page, page) == 0); VirtualQueryInfo first{}; Require(sceKernelVirtualQuery(memory, 0, &first, sizeof(first)) == 0); Require(first.start == reinterpret_cast(memory)); Require(first.end == reinterpret_cast(memory) + page); VirtualQueryInfo middle{}; Require(sceKernelVirtualQuery(memory + page, 0, &middle, sizeof(middle)) == SCE_KERNEL_ERROR_EACCES); VirtualQueryInfo next{}; Require(sceKernelVirtualQuery(memory + page, 1, &next, sizeof(next)) == 0); Require(next.start == reinterpret_cast(memory) + page * 2); Require(next.end == reinterpret_cast(memory) + page * 3); VirtualQueryInfo tail{}; Require(sceKernelVirtualQuery(memory + page * 2, 0, &tail, sizeof(tail)) == 0); Require(tail.start == reinterpret_cast(memory) + page * 2); Require(tail.end == reinterpret_cast(memory) + page * 3); Require(munmap_nid_postfix(memory, page) == 0); Require(munmap_nid_postfix(memory + page * 2, page) == 0); } static void CheckAvailableDirectMemoryIsLargestFreeBlock() { constexpr std::int64_t window = 0x4000000; const std::int64_t top = static_cast(DIRECT_MEMORY_SIZE) - window; std::int64_t first = -1; std::int64_t gap = -1; std::int64_t last = -1; Require(sceKernelAllocateDirectMemory(top, top + window, 0x200000, 0x200000, 0, &first) == 0 && first == top); Require(sceKernelAllocateDirectMemory(top, top + window, 0x400000, 0x200000, 0, &gap) == 0 && gap == top + 0x200000); Require(sceKernelAllocateDirectMemory(top, top + window, 0x200000, 0x200000, 0, &last) == 0 && last == top + 0x600000); Require(sceKernelReleaseDirectMemory(gap, 0x400000) == 0); std::int64_t start = -1; std::size_t size = 0; Require(sceKernelAvailableDirectMemorySize(top, top + window, 0, &start, &size) == 0); Require(start == top + 0x800000 && size == static_cast(window - 0x800000)); Require(sceKernelAvailableDirectMemorySize(top, top + 0x800000, 0, &start, &size) == 0); Require(start == top + 0x200000 && size == 0x400000); Require(sceKernelAvailableDirectMemorySize(top, top + window, 0x1000000, &start, &size) == 0); Require(start == top + 0x1000000 && size == static_cast(window - 0x1000000)); Require(sceKernelAvailableDirectMemorySize(top, top + 0x200000, 0, &start, &size) == SCE_KERNEL_ERROR_EAGAIN); Require(start == 0 && size == 0); Require(sceKernelAvailableDirectMemorySize(top, top + window, 0, nullptr, &size) == SCE_KERNEL_ERROR_EINVAL); Require(sceKernelReleaseDirectMemory(first, 0x200000) == 0); Require(sceKernelReleaseDirectMemory(last, 0x200000) == 0); Require(sceKernelAvailableDirectMemorySize(top, top + window, 0, &start, &size) == 0); Require(start == top && size == static_cast(window)); } static void CheckMunmapOverUnmappedPages() { constexpr std::size_t page = 0x4000; const auto failed = reinterpret_cast(static_cast(-1)); void* raw = mmap_nid_postfix(nullptr, page * 3, 3, 0x1002, -1, 0); Require(raw != failed); auto* memory = static_cast(raw); memory[0] = 1; memory[page * 2] = 2; Require(munmap_nid_postfix(memory + page, page) == 0); Require(munmap_nid_postfix(memory, page * 3) == 0); { GuestAllocations::Mutation mutation; Require(!mutation.Overlaps(memory, page * 3)); } VirtualQueryInfo info{}; Require(sceKernelVirtualQuery(memory, 0, &info, sizeof(info)) == SCE_KERNEL_ERROR_EACCES); Require(sceKernelVirtualQuery(memory + page * 2, 0, &info, sizeof(info)) == SCE_KERNEL_ERROR_EACCES); Require(munmap_nid_postfix(memory, page * 3) == 0); Require(sceKernelMunmap(memory, page * 3) == 0); Require(sceKernelMunmap(memory, 0) == SCE_KERNEL_ERROR_EINVAL); } int main() { #ifdef _WIN32 _putenv_s("APS5_PIN_WAIT_MS", "1000"); #endif CheckAvailableDirectMemoryIsLargestFreeBlock(); CheckVirtualQuerySplitFlexibleRanges(); CheckVirtualQueryForNonReadableGuestRange(); CheckReleaseFlexibleMemory(); CheckFlexibleMappingIgnoresFlag8000(); CheckSceKernelMmap(); CheckInternalMemoryAvailableSize(); CheckVirtualQueryPartialMunmap(); CheckMunmapOverUnmappedPages(); CheckPageTableStats(); CheckNamedAndHintedMappings(); CheckInternalNamedFlexibleMapping(); CheckGuestModuleUsesFlexibleMemory(); CheckBatchMapStopsAtInvalidEntry(); CheckCheckedReleaseDirectMemory(); CheckAudioCoprocessorProtection(); CheckDirectMemoryFollowsPhysicalPages(); CheckSparseDirectMemoryBacking(); CheckReleaseDirectMemoryClearsMappings(); CheckReleaseDirectMemoryRejectsInvalidRanges(); CheckFixedMappingReplacesPartialOverlap(); CheckFixedMappingOverHoleInsideAllocation(); CheckDirectMemoryGpuProtBits(); CheckGpuAccessFollowsProtection(); CheckGpuMapsAreObserved(); CheckClearingTheObserverWaitsForItsCalls(); CheckHintedVirtualReservation(); CheckFixedVirtualReservation(); CheckReservedRangeIsNotCommitted(); CheckHintInsideReservation(); CheckNoOverwriteRefusesLiveMapping(); CheckMlock(); CheckSharedDirectMemoryLifecycle(); CheckGetDirectMemoryType(); CheckMtypeprotect(); CheckHeapAlignment(); CheckHeapAfterMappingReuse(); CheckNoOverwriteRejectsHostOccupiedMapping(); CheckFixedMappingsReachTheApplicationAreaEnd(); #ifdef _WIN32 CheckGuestModuleImageProtection(); CheckAliasAcrossDirectAllocations(); CheckAliasOfReorderedSharedPages(); CheckAliasRefusesPagesThatAreNotSharedViews(); #endif CheckUnhintedKernelMappingsLandInTheWindow(); #if defined(__linux__) CheckUnmappedArenaRangeStaysReserved(); #endif CheckArenaPlacesUnhintedMappingsInsideTheWindow(); CheckArenaReusesAFreedRange(); CheckArenaHonoursHintsWithoutGoingBelowThem(); CheckSharedWriteTracking(); CheckSharedWritePages(); CheckSharedWritesDuringHostWrite(); CheckReadsIntoSharedWriteTracking(); CheckPinnedSharedPages(); CheckFailedCollectKeepsWrites(); #if defined(__linux__) CheckWriteWatch(); CheckWriteWatchArmedCollect(); CheckWriteWatchFreshCollect(); CheckDirectMemoryWriteWatch(); CheckDirectMemoryWriteWatchFollowsRemovedMappings(); CheckMappingCostFollowsTheMappingCount(); CheckDirectMemoryBackingNeedsNoFilesystem(); CheckDirectMemorySharedBacking(); #endif constexpr std::size_t page = 0x4000; const auto failed = reinterpret_cast(static_cast(-1)); const auto reject = [&](std::size_t length, int protection, int flags, int fd, std::int64_t offset, int error) { *__error_nid_postfix() = 0; Require(mmap_nid_postfix(nullptr, length, protection, flags, fd, offset) == failed); Require(*__error_nid_postfix() == error); }; reject(0, 3, 0x1002, -1, 0, 22); reject(std::numeric_limits::max(), 3, 0x1002, -1, 0, 22); reject(page, 8, 0x1002, -1, 0, 22); reject(page, 3, 0x1002, 0, 0, 22); reject(page, 3, 0x1002, -1, 1, 22); reject(page, 3, 0x1001, -1, 0, 45); // shared reject(page, 3, 0x1012, -1, 0, 45); // fixed reject(page, 3, 0x2, 0, 0, 45); // file-backed reject(page, 3, 0x22, -1, 0, 45); // Linux MAP_ANON is not guest MAP_ANON auto* memory = static_cast(mmap_nid_postfix(nullptr, page * 3 - 1, 3, 0x1002, -1, 0)); Require(memory != failed && (reinterpret_cast(memory) & (page - 1)) == 0); for (std::size_t i = 0; i < page * 3; ++i) Require(memory[i] == 0); memory[0] = 42; memory[page * 2] = 73; { GuestAllocations::Mutation mutation; const auto range = mutation.Find(memory); Require(range.bytes == page * 3 && range.readable && range.writable); } Require(mprotect_nid_postfix(memory, 0, 1) == 0); Require(mprotect_nid_postfix(nullptr, page, 1) == -1 && *__error_nid_postfix() == 22); Require(mprotect_nid_postfix(memory + 1, 1, 1) == 0); { GuestAllocations::Mutation mutation; const auto range = mutation.Find(memory); Require(range.readable && !range.writable); } Require(memory[0] == 42); Require(mprotect_nid_postfix(memory, page, 3) == 0); { GuestAllocations::Mutation mutation; Require(mutation.Find(memory).writable); } for (int advice = 0; advice <= 10; ++advice) Require(madvise_nid_postfix(memory, page * 3, advice) == 0); Require(madvise_nid_postfix(reinterpret_cast(0x800000000000), 1, 10) == 0); Require(madvise_nid_postfix(memory, std::numeric_limits::max(), 10) == 0); Require(memory[0] == 42 && memory[page * 2] == 73); Require(madvise_nid_postfix(memory + 1, 0, 4) == 0); Require(madvise_nid_postfix(memory, page, 11) == -1 && *__error_nid_postfix() == 22); *__error_nid_postfix() = 0; Require(madvise_nid_postfix(memory, page, -1) == -1 && *__error_nid_postfix() == 22); *__error_nid_postfix() = 0; Require(madvise_nid_postfix(memory, 0, 11) == -1 && *__error_nid_postfix() == 22); *__error_nid_postfix() = 0; Require(madvise_nid_postfix(memory, std::numeric_limits::max(), 0) == -1 && *__error_nid_postfix() == 22); *__error_nid_postfix() = 0; Require(madvise_nid_postfix(reinterpret_cast(0x800000000000), 1, 0) == -1 && *__error_nid_postfix() == 22); Require(madvise_nid_postfix(reinterpret_cast(0x7fffffffc000), 0x4000, 4) == 0); Require(munmap_nid_postfix(memory + 1, page) == -1 && *__error_nid_postfix() == 22); Require(munmap_nid_postfix(memory, 0) == -1 && *__error_nid_postfix() == 22); Require(memory[0] == 42); Require(munmap_nid_postfix(memory + page, 1) == 0); // round to one guest page Require(memory[0] == 42 && memory[page * 2] == 73); Require(munmap_nid_postfix(memory, page) == 0); Require(memory[page * 2] == 73); Require(munmap_nid_postfix(memory + page * 2, page) == 0); Require(munmap_nid_postfix(memory, page) == 0); Require(madvise_nid_postfix(memory, page, 4) == 0); *__error_nid_postfix() = 0; Require(mprotect_nid_postfix(memory, page, 1) == -1 && *__error_nid_postfix() == 22); *__error_nid_postfix() = 0; Require(mprotect_nid_postfix(memory, std::numeric_limits::max(), 1) == -1 && *__error_nid_postfix() == 22); for (int protection : {0, 1, 3, 5}) { void* mapped = mmap_nid_postfix(memory, 1, protection, 0x1002, -1, 0); Require(mapped != failed); { GuestAllocations::Mutation mutation; const auto range = mutation.Find(mapped); Require(range.readable == ((protection & 3) != 0)); Require(range.writable == ((protection & 2) != 0)); } Require(munmap_nid_postfix(mapped, 1) == 0); } reject(page, 3, 0x2001002, -1, 0, 45); constexpr std::size_t superpage = std::size_t{1} << 21; auto* aligned = static_cast(mmap_nid_postfix(nullptr, superpage + page, 3, 0x1001002, -1, 0)); Require(aligned != failed && (reinterpret_cast(aligned) & (superpage - 1)) == 0); aligned[0] = 1; aligned[superpage + page - 1] = 2; { GuestAllocations::Mutation mutation; const auto range = mutation.Find(aligned); Require(range.bytes == superpage + page && range.readable && range.writable); } Require(munmap_nid_postfix(aligned, superpage + page) == 0); }