From 0621d9e9d01d9dca70f4ce4e771dc6efc835f265 Mon Sep 17 00:00:00 2001 From: nmzik Date: Fri, 17 Jul 2026 18:51:36 +0200 Subject: [PATCH] kernel: preserve placeholders during fixed memory replacement --- src/CMakeLists.txt | 2 + src/kernel/memory.cpp | 169 ++++++++-- src/kernel/memory.h | 8 + src/kernel/memoryAddressSpace.inc | 59 ++-- tests/VirtualMemoryAllocationTests.cpp | 426 ++++++++++++++++++++----- 5 files changed, 535 insertions(+), 129 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 69c8f5855..b77704ef9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -292,6 +292,8 @@ target_include_directories(resource_mutex_tests PRIVATE ${inc_headers}) add_kyty_full_emulator_test(shader_recompiler_compute_tests ../tests/ShaderRecompilerComputeTests.cpp) add_kyty_full_emulator_test(virtual_memory_allocation_tests ../tests/VirtualMemoryAllocationTests.cpp) +target_compile_definitions(virtual_memory_allocation_tests PRIVATE + KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS=1) add_executable(kyty_emulator main.cpp ${kyty_emulator_src}) diff --git a/src/kernel/memory.cpp b/src/kernel/memory.cpp index 47fe73fd7..695e0c914 100644 --- a/src/kernel/memory.cpp +++ b/src/kernel/memory.cpp @@ -713,6 +713,12 @@ private: Common::Mutex m_mutex; }; +#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS) +static uint32_t g_test_physical_memory_unmaps_before_failure = UINT32_MAX; +static uint32_t g_test_host_reservation_pages_before_failure = UINT32_MAX; +static bool g_test_fail_next_fixed_reserve_range_add = false; +#endif + class PhysicalMemory { public: struct AllocatedBlock { @@ -1322,6 +1328,16 @@ bool PhysicalMemory::ReleasePoolExpansion(uint64_t phys_addr, size_t len) { bool PhysicalMemory::Unmap(uint64_t vaddr, uint64_t size, GpuAccessMode* gpu_mode, uint64_t* host_vaddr_to_release) { +#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS) + if (g_test_physical_memory_unmaps_before_failure == 0) { + g_test_physical_memory_unmaps_before_failure = UINT32_MAX; + return false; + } + if (g_test_physical_memory_unmaps_before_failure != UINT32_MAX) { + g_test_physical_memory_unmaps_before_failure--; + } +#endif + EXIT_IF(gpu_mode == nullptr); Common::LockGuard lock(m_mutex); @@ -2943,17 +2959,35 @@ static bool ReserveFixedHostRange(uint64_t start, uint64_t size) { } #endif +#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS) + for (uint64_t addr = start; addr < start + size; addr += PAGE_SIZE) { + if (g_test_host_reservation_pages_before_failure == 0) { + g_test_host_reservation_pages_before_failure = UINT32_MAX; + return false; + } + if (g_test_host_reservation_pages_before_failure != UINT32_MAX) { + g_test_host_reservation_pages_before_failure--; + } + } + g_test_host_reservation_pages_before_failure = UINT32_MAX; +#endif + + bool host_mutated = false; for (uint64_t addr = start; addr < start + size; addr += PAGE_SIZE) { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS MEMORY_BASIC_INFORMATION info {}; if (VirtualQuery(reinterpret_cast(addr), &info, sizeof(info)) != 0) { if (info.State == MEM_COMMIT) { if (!VirtualMemory::Decommit(addr, PAGE_SIZE)) { + if (host_mutated) { + EXIT("reserve-fixed partial host decommit cannot be rolled back safely\n"); + } LOGF_COLOR(Log::Color::Red, "\t reserve-fixed replace: decommit failed at 0x%016" PRIx64 "\n", addr); return false; } + host_mutated = true; continue; } if (info.State == MEM_RESERVE) { @@ -2962,10 +2996,14 @@ static bool ReserveFixedHostRange(uint64_t start, uint64_t size) { } #endif if (!VirtualMemory::ReserveFixed(addr, PAGE_SIZE)) { + if (host_mutated) { + EXIT("reserve-fixed partial host reservation cannot be rolled back safely\n"); + } LOGF_COLOR(Log::Color::Red, "\t reserve-fixed replace: reserve failed at 0x%016" PRIx64 "\n", addr); return false; } + host_mutated = true; } return true; @@ -2978,9 +3016,12 @@ static bool ReplaceFixedRangeWithReserved(uint64_t start, uint64_t size, bool* p struct ReplacedChunk { VirtualRanges::Range range {}; - VirtualMemory::Mode mode = VirtualMemory::Mode::NoAccess; - GpuAccessMode gpu_mode = GpuAccessMode::NoAccess; - bool shared_backing = false; + VirtualMemory::Mode mode = VirtualMemory::Mode::NoAccess; + GpuAccessMode gpu_mode = GpuAccessMode::NoAccess; + bool shared_backing = false; + bool placeholder_restored = false; + bool host_unmapped = false; + bool backend_unmapped = false; }; std::vector chunks; @@ -3026,22 +3067,39 @@ static bool ReplaceFixedRangeWithReserved(uint64_t start, uint64_t size, bool* p bool backend_restored = true; if (chunk.range.type == VirtualRangeType::Direct) { - host_restored = - (chunk.shared_backing - ? g_direct_memory_backing->MapFixed(chunk.range.start, chunk.range.size, - chunk.range.offset, chunk.mode) - : CommitFixedHostRange(chunk.range.start, chunk.range.size, chunk.mode)); - backend_restored = - g_physical_memory->Map(chunk.range.start, chunk.range.offset, chunk.range.size, - chunk.range.protection, chunk.mode, chunk.gpu_mode); + if (chunk.host_unmapped && chunk.placeholder_restored) { + const bool consumed = + g_placeholder_address_space->Consume(chunk.range.start, chunk.range.size); + host_restored = + consumed && + g_direct_memory_backing->MapExistingPlaceholderFixed( + chunk.range.start, chunk.range.size, chunk.range.offset, chunk.mode); + if (consumed && !host_restored) { + g_placeholder_address_space->AddFree(chunk.range.start, chunk.range.size); + } + } else if (chunk.host_unmapped || !chunk.shared_backing) { + host_restored = + (chunk.shared_backing ? g_direct_memory_backing->MapFixed( + chunk.range.start, chunk.range.size, + chunk.range.offset, chunk.mode) + : CommitFixedHostRange(chunk.range.start, + chunk.range.size, chunk.mode)); + } + if (chunk.backend_unmapped) { + backend_restored = g_physical_memory->Map( + chunk.range.start, chunk.range.offset, chunk.range.size, + chunk.range.protection, chunk.mode, chunk.gpu_mode); + } } else if (chunk.range.type == VirtualRangeType::Flexible || chunk.range.type == VirtualRangeType::Stack || chunk.range.type == VirtualRangeType::Pooled) { host_restored = CommitFixedHostRange(chunk.range.start, chunk.range.size, chunk.mode); - backend_restored = g_flexible_memory->Map(chunk.range.start, chunk.range.size, - chunk.range.protection, chunk.mode, - chunk.gpu_mode, chunk.range.name); + if (chunk.backend_unmapped) { + backend_restored = g_flexible_memory->Map(chunk.range.start, chunk.range.size, + chunk.range.protection, chunk.mode, + chunk.gpu_mode, chunk.range.name); + } } const bool range_restored = g_virtual_ranges->Add( @@ -3062,6 +3120,7 @@ static bool ReplaceFixedRangeWithReserved(uint64_t start, uint64_t size, bool* p } const bool gpu_unmapped = std::any_of(chunks.begin(), chunks.end(), [](const auto& chunk) { return IsCommittedRangeType(chunk.range.type) && + chunk.gpu_mode != GpuAccessMode::NoAccess && IsGpuAddressRange(chunk.range.start, chunk.range.size); }); for (const auto& chunk: chunks) { @@ -3079,22 +3138,28 @@ static bool ReplaceFixedRangeWithReserved(uint64_t start, uint64_t size, bool* p if (chunk.range.type == VirtualRangeType::Direct) { if (chunk.shared_backing) { bool chunk_placeholder_restored = false; - if (!g_direct_memory_backing->Unmap(chunk.range.start, chunk.range.size, false, + if (!g_direct_memory_backing->Unmap(chunk.range.start, chunk.range.size, true, &chunk_placeholder_restored)) { unmapped = false; - } else if (chunk_placeholder_restored) { - g_placeholder_address_space->AddFree(chunk.range.start, chunk.range.size); + } else { + chunk.host_unmapped = true; + if (chunk_placeholder_restored) { + g_placeholder_address_space->AddFree(chunk.range.start, chunk.range.size); + chunk.placeholder_restored = true; + } } } if (unmapped) { unmapped = g_physical_memory->Unmap(chunk.range.start, chunk.range.size, &gpu_mode); - chunk.gpu_mode = gpu_mode; + chunk.gpu_mode = gpu_mode; + chunk.backend_unmapped = unmapped; } } else if (chunk.range.type == VirtualRangeType::Flexible || chunk.range.type == VirtualRangeType::Stack || chunk.range.type == VirtualRangeType::Pooled) { unmapped = g_flexible_memory->Unmap(chunk.range.start, chunk.range.size, &gpu_mode); - chunk.gpu_mode = gpu_mode; + chunk.gpu_mode = gpu_mode; + chunk.backend_unmapped = unmapped; } if (!unmapped) { @@ -3108,7 +3173,9 @@ static bool ReplaceFixedRangeWithReserved(uint64_t start, uint64_t size, bool* p ", size=0x%016" PRIx64 ", type=%s\n", chunk.range.start, chunk.range.size, Common::EnumName(chunk.range.type).c_str()); - restore_chunks(); + if (!restore_chunks()) { + EXIT("reserve-fixed backend-unmap rollback failed\n"); + } return false; } } @@ -3121,27 +3188,53 @@ static bool ReplaceFixedRangeWithReserved(uint64_t start, uint64_t size, bool* p " size=0x%016" PRIx64 "\n", start, size); } - restore_chunks(); + if (!restore_chunks()) { + EXIT("reserve-fixed host-reservation rollback failed\n"); + } return false; } - if (!g_virtual_ranges->Add(start, size, 0, 0, 0, VirtualRangeType::Reserved, "anon", false, - *placeholder_backed)) { + bool range_added = false; +#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS) + if (g_test_fail_next_fixed_reserve_range_add) { + g_test_fail_next_fixed_reserve_range_add = false; + } else +#endif + { + range_added = g_virtual_ranges->Add(start, size, 0, 0, 0, VirtualRangeType::Reserved, + "anon", false, *placeholder_backed); + } + if (!range_added) { if (gpu_unmapped) { EXIT("reserve-fixed range registration failed after GPU unmap: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", start, size); } - if (*placeholder_backed) { - g_placeholder_address_space->ReleaseFree(start, size); - } else { + if (!*placeholder_backed) { VirtualMemory::Free(start); } LOGF_COLOR(Log::Color::Red, "\t reserve-fixed replace: range add failed at 0x%016" PRIx64 ", size=0x%016" PRIx64 "\n", start, size); - restore_chunks(); + if (!restore_chunks()) { + EXIT("reserve-fixed range-registration rollback failed\n"); + } + if (*placeholder_backed) { + auto free_start = start; + for (const auto& chunk: chunks) { + if (free_start < chunk.range.start && + !g_placeholder_address_space->ReleaseFree(free_start, + chunk.range.start - free_start)) { + EXIT("reserve-fixed range-registration gap cleanup failed\n"); + } + free_start = chunk.range.start + chunk.range.size; + } + if (free_start < start + size && + !g_placeholder_address_space->ReleaseFree(free_start, start + size - free_start)) { + EXIT("reserve-fixed range-registration tail cleanup failed\n"); + } + } return false; } @@ -3224,6 +3317,28 @@ int KYTY_SYSV_ABI KernelReserveVirtualRange(void** addr, size_t len, int flags, return OK; } +#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS) +void TestFailNextPhysicalMemoryUnmap() { + TestFailPhysicalMemoryUnmapAfter(0); +} + +void TestFailPhysicalMemoryUnmapAfter(uint32_t successful_unmaps) { + g_test_physical_memory_unmaps_before_failure = successful_unmaps; +} + +void TestFailHostReservationAfter(uint32_t successful_pages) { + g_test_host_reservation_pages_before_failure = successful_pages; +} + +void TestFailNextFixedReserveRangeRegistration() { + g_test_fail_next_fixed_reserve_range_add = true; +} + +bool TestPlaceholderRangeIsFree(uint64_t vaddr, uint64_t size) { + return g_placeholder_address_space->TestContainsFree(vaddr, size); +} +#endif + bool KernelHandleReservedRangeAccessViolation(uint64_t vaddr) { std::lock_guard memory_operation_lock(g_memory_operation_mutex); diff --git a/src/kernel/memory.h b/src/kernel/memory.h index c39c37d78..e8bc09b30 100644 --- a/src/kernel/memory.h +++ b/src/kernel/memory.h @@ -159,6 +159,14 @@ void RegisterProgramMemory(uint64_t vaddr, uint64_t size, Common::VirtualMemory: void UpdateProgramMemoryProtection(uint64_t vaddr, uint64_t size, Common::VirtualMemory::Mode mode); void UnregisterProgramMemory(uint64_t vaddr, uint64_t size); +#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS) +void TestFailNextPhysicalMemoryUnmap(); +void TestFailPhysicalMemoryUnmapAfter(uint32_t successful_unmaps); +void TestFailHostReservationAfter(uint32_t successful_pages); +void TestFailNextFixedReserveRangeRegistration(); +bool TestPlaceholderRangeIsFree(uint64_t vaddr, uint64_t size); +#endif + } // namespace Libs::LibKernel::Memory #endif /* EMULATOR_INCLUDE_EMULATOR_KERNEL_MEMORY_H_ */ diff --git a/src/kernel/memoryAddressSpace.inc b/src/kernel/memoryAddressSpace.inc index decac757c..5c04c5de0 100644 --- a/src/kernel/memoryAddressSpace.inc +++ b/src/kernel/memoryAddressSpace.inc @@ -151,10 +151,10 @@ public: return false; } auto* destination = static_cast(data); - return TryTransferBacking(vaddr, size, - [destination](uint8_t* backing, uint64_t data_offset, size_t bytes) { - std::memcpy(destination + data_offset, backing, bytes); - }); + return TryTransferBacking( + vaddr, size, [destination](uint8_t* backing, uint64_t data_offset, size_t bytes) { + std::memcpy(destination + data_offset, backing, bytes); + }); } uint64_t MapAligned(uint64_t search_addr, uint64_t size, uint64_t backing_offset, @@ -342,7 +342,7 @@ public: MapEntry old {}; { std::lock_guard lock(m_mutex); - auto next = m_maps.upper_bound(vaddr); + auto next = m_maps.upper_bound(vaddr); if (next != m_maps.begin()) { auto entry = std::prev(next); if (vaddr + size <= entry->second.vaddr + entry->second.size) { @@ -497,8 +497,8 @@ public: constexpr uint64_t test_value = 0x4b595459414c4941ull; // "KYTYALIA" EXIT_IF(!VirtualMemory::Protect(first, test_size, VirtualMemory::Mode::NoAccess)); - const bool ok = TryWriteBacking(first, &test_value, sizeof(test_value)) && - *second_ptr == test_value; + const bool ok = + TryWriteBacking(first, &test_value, sizeof(test_value)) && *second_ptr == test_value; const uint64_t clear = 0; EXIT_IF(!TryWriteBacking(first, &clear, sizeof(clear))); @@ -518,8 +518,8 @@ private: } std::lock_guard lock(m_mutex); - const auto end = vaddr + size; - auto current = vaddr; + const auto end = vaddr + size; + auto current = vaddr; while (current < end) { MapEntry entry {}; if (!FindContainingUnlocked(current, 1, &entry)) { @@ -609,24 +609,24 @@ private: [[nodiscard]] bool ShouldChunkMap(uint64_t size) const { return size > GetMapChunkSize(); } - uint64_t MapNewPlaceholderAligned(uint64_t search_addr, uint64_t size, - uint64_t backing_offset, VirtualMemory::Mode mode, - uint64_t alignment, FailureReason* failure_reason) { + uint64_t MapNewPlaceholderAligned(uint64_t search_addr, uint64_t size, uint64_t backing_offset, + VirtualMemory::Mode mode, uint64_t alignment, + FailureReason* failure_reason) { MEM_ADDRESS_REQUIREMENTS requirements {}; MEM_EXTENDED_PARAMETER parameter {}; - const auto aligned_search = - (search_addr + alignment - 1u) & ~(alignment - 1u); - requirements.LowestStartingAddress = reinterpret_cast( - search_addr != 0 ? aligned_search : HOST_SYSTEM_MANAGED_MIN); + const auto aligned_search = (search_addr + alignment - 1u) & ~(alignment - 1u); + requirements.LowestStartingAddress = + reinterpret_cast(search_addr != 0 ? aligned_search : HOST_SYSTEM_MANAGED_MIN); requirements.HighestEndingAddress = reinterpret_cast(search_addr != 0 ? HOST_USER_MAX : HOST_SYSTEM_MANAGED_MAX); requirements.Alignment = alignment; - parameter.Type = MemExtendedParameterAddressRequirements; - parameter.Pointer = &requirements; + parameter.Type = MemExtendedParameterAddressRequirements; + parameter.Pointer = &requirements; auto reserve = [&]() { return VirtualAlloc2(GetCurrentProcess(), nullptr, size, - MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, ¶meter, 1); + MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, ¶meter, + 1); }; auto* placeholder = reserve(); if (placeholder == nullptr && search_addr == 0) { @@ -1056,9 +1056,9 @@ private: int m_fd = -1; #endif - uint8_t* m_backing_base = nullptr; - uint64_t m_size = 0; - std::mutex m_mutex; + uint8_t* m_backing_base = nullptr; + uint64_t m_size = 0; + std::mutex m_mutex; std::map m_maps; }; @@ -1098,8 +1098,8 @@ public: if (ptr == nullptr && search_addr == 0) { req.LowestStartingAddress = reinterpret_cast(HOST_USER_MIN); req.HighestEndingAddress = reinterpret_cast(HOST_USER_MAX); - ptr = VirtualAlloc2(GetCurrentProcess(), nullptr, size, - MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, ¶m, 1); + ptr = VirtualAlloc2(GetCurrentProcess(), nullptr, size, + MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, ¶m, 1); } if (ptr == nullptr) { return 0; @@ -1161,6 +1161,17 @@ public: #endif } +#if defined(KYTY_VIRTUAL_MEMORY_ALLOCATION_TESTS) + bool TestContainsFree(uint64_t vaddr, uint64_t size) { +#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS + std::lock_guard lock(m_mutex); + return FindContaining(vaddr, size) != m_free.end(); +#else + return false; +#endif + } +#endif + bool ReleaseFree(uint64_t vaddr, uint64_t size) { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS std::lock_guard lock(m_mutex); diff --git a/tests/VirtualMemoryAllocationTests.cpp b/tests/VirtualMemoryAllocationTests.cpp index c46cf3a63..7104ded74 100644 --- a/tests/VirtualMemoryAllocationTests.cpp +++ b/tests/VirtualMemoryAllocationTests.cpp @@ -1,11 +1,10 @@ #include "common/commonSubsystem.h" +#include "common/emulatorConfig.h" +#include "common/logging/log.h" #include "common/subsystems.h" #include "common/threads.h" - -#include "common/emulatorConfig.h" #include "kernel/memory.h" #include "libs/errno.h" -#include "common/logging/log.h" #include #include @@ -213,7 +212,8 @@ void TestFlexibleMapQueryAndWholeMunmap() { void TestPartialFlexibleMunmapAndFindNext() { const char* test = "PartialFlexibleMunmapAndFindNext"; const auto baseline = AvailableFlexibleMemory(test); - const auto base = MapNamedFlexible(test, SceKernelPageSize * 3, SceKernelProtCpuRw, "prospero_part"); + const auto base = + MapNamedFlexible(test, SceKernelPageSize * 3, SceKernelProtCpuRw, "prospero_part"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base + SceKernelPageSize, SceKernelPageSize), @@ -300,8 +300,8 @@ void TestFixedNoOverwriteRejectsReservedRange() { void* fixed = reinterpret_cast(base); const int ret = Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory( - &fixed, SceKernelPageSize, SceKernelProtCpuRw, SceKernelMapFixed | SceKernelMapNoOverwrite, - "reserved_blocked"); + &fixed, SceKernelPageSize, SceKernelProtCpuRw, SceKernelMapFixed | SceKernelMapNoOverwrite, + "reserved_blocked"); const bool rejected = ret < OK; if (ret == OK) { @@ -319,7 +319,8 @@ void TestFixedNoOverwriteRejectsReservedRange() { } Check(test, rejected, - "MAP_FIXED|MAP_NO_OVERWRITE should reject an already reserved virtual range"); + "MAP_FIXED|MAP_NO_OVERWRITE should reject an already reserved virtual " + "range"); std::printf("[host] %-48s ok\n", test); } @@ -336,17 +337,17 @@ void TestDirectMapQueryOffsetAndPartialMunmap() { void* addr = nullptr; CheckOk(test, - Libs::LibKernel::Memory::KernelMapNamedDirectMemory(&addr, SceKernelPageSize * 4, - SceKernelProtCpuRw, 0, phys_addr, - SceKernelPageSize, "prospero_direct"), + Libs::LibKernel::Memory::KernelMapNamedDirectMemory( + &addr, SceKernelPageSize * 4, SceKernelProtCpuRw, 0, phys_addr, SceKernelPageSize, + "prospero_direct"), "KernelMapNamedDirectMemory"); - const auto base = reinterpret_cast(addr); - const auto phys = static_cast(phys_addr); + const auto base = reinterpret_cast(addr); + const auto phys = static_cast(phys_addr); void* alias = nullptr; CheckOk(test, Libs::LibKernel::Memory::KernelMapNamedDirectMemory( - &alias, SceKernelPageSize * 4, SceKernelProtCpuRw, 0, phys_addr, - SceKernelPageSize, "prospero_direct_alias"), + &alias, SceKernelPageSize * 4, SceKernelProtCpuRw, 0, phys_addr, SceKernelPageSize, + "prospero_direct_alias"), "KernelMapNamedDirectMemory(alias)"); const auto alias_base = reinterpret_cast(alias); @@ -367,7 +368,7 @@ void TestDirectMapQueryOffsetAndPartialMunmap() { backing_read = 0; Check(test, Libs::LibKernel::Memory::TryReadBacking(base + sizeof(uint64_t), &backing_read, - sizeof(backing_read)), + sizeof(backing_read)), "TryReadBacking should resolve an aliased physical offset"); Check(test, backing_read == backing_write, "backing reads and writes should preserve direct-memory aliasing"); @@ -416,8 +417,7 @@ void TestDirectMapQueryOffsetAndPartialMunmap() { test, Libs::LibKernel::Memory::KernelMunmap(base + SceKernelPageSize * 2, SceKernelPageSize * 2), "KernelMunmap(direct right cleanup)"); - CheckOk(test, - Libs::LibKernel::Memory::KernelMunmap(alias_base, SceKernelPageSize * 4), + CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(alias_base, SceKernelPageSize * 4), "KernelMunmap(direct alias cleanup)"); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, SceKernelPageSize * 4), @@ -435,8 +435,7 @@ void TestReleasedReserveCanBeReused() { SceKernelPageSize), "KernelReserveVirtualRange"); const auto base = reinterpret_cast(addr); - CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize), - "KernelMunmap"); + CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize), "KernelMunmap"); void* reused = reinterpret_cast(base); CheckOk(test, @@ -458,8 +457,8 @@ void TestMunmapAcrossAdjacentFlexibleMappings() { void* reserve = nullptr; CheckOk(test, - Libs::LibKernel::Memory::KernelReserveVirtualRange( - &reserve, SceKernelPageSize * 2, 0, SceKernelPageSize), + Libs::LibKernel::Memory::KernelReserveVirtualRange(&reserve, SceKernelPageSize * 2, 0, + SceKernelPageSize), "KernelReserveVirtualRange"); const auto base = reinterpret_cast(reserve); @@ -471,8 +470,7 @@ void TestMunmapAcrossAdjacentFlexibleMappings() { "KernelMapNamedFlexibleMemory(left)"); CheckOk(test, Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory( - &right, SceKernelPageSize, SceKernelProtCpuRw, SceKernelMapFixed, - "adjacent_right"), + &right, SceKernelPageSize, SceKernelProtCpuRw, SceKernelMapFixed, "adjacent_right"), "KernelMapNamedFlexibleMemory(right)"); CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize * 2), @@ -512,24 +510,26 @@ void TestNonzeroDirectOffsetAliasesSharedBacking() { void* second_alias = nullptr; CheckOk(test, Libs::LibKernel::Memory::KernelMapNamedDirectMemory( - &first_alias, SceKernelPageSize, SceKernelProtCpuRw, 0, second, - SceKernelPageSize, "prospero_nonzero_a"), + &first_alias, SceKernelPageSize, SceKernelProtCpuRw, 0, second, SceKernelPageSize, + "prospero_nonzero_a"), "KernelMapNamedDirectMemory(first alias)"); CheckOk(test, Libs::LibKernel::Memory::KernelMapNamedDirectMemory( - &second_alias, SceKernelPageSize, SceKernelProtCpuRw, 0, second, - SceKernelPageSize, "prospero_nonzero_b"), + &second_alias, SceKernelPageSize, SceKernelProtCpuRw, 0, second, SceKernelPageSize, + "prospero_nonzero_b"), "KernelMapNamedDirectMemory(second alias)"); *reinterpret_cast(first_alias) = 0x4b59545931364b42ull; // "KYTY16KB" Check(test, *reinterpret_cast(second_alias) == 0x4b59545931364b42ull, "nonzero-offset mappings must share backing storage"); - CheckOk(test, Libs::LibKernel::Memory::KernelMunmap( - reinterpret_cast(first_alias), SceKernelPageSize), + CheckOk(test, + Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast(first_alias), + SceKernelPageSize), "KernelMunmap(first alias)"); - CheckOk(test, Libs::LibKernel::Memory::KernelMunmap( - reinterpret_cast(second_alias), SceKernelPageSize), + CheckOk(test, + Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast(second_alias), + SceKernelPageSize), "KernelMunmap(second alias)"); CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(second, SceKernelPageSize), "KernelReleaseDirectMemory(second)"); @@ -581,16 +581,16 @@ void TestDirectPhysicalFreeRangeReuseAndCoalescing() { } void TestDirectAlignmentStaysWithinSearchRange() { - const char* test = "DirectAlignmentStaysWithinSearchRange"; + const char* test = "DirectAlignmentStaysWithinSearchRange"; constexpr int64_t search_start = SceKernelPageSize * 2; constexpr uint64_t alignment = SceKernelPageSize * 3; const auto search_end = Libs::LibKernel::Memory::KernelGetDirectMemorySize(); int64_t phys_addr = -1; CheckOk(test, - Libs::LibKernel::Memory::KernelAllocateDirectMemory( - search_start, search_end, SceKernelPageSize, alignment, SceKernelMtypeC, - &phys_addr), + Libs::LibKernel::Memory::KernelAllocateDirectMemory(search_start, search_end, + SceKernelPageSize, alignment, + SceKernelMtypeC, &phys_addr), "KernelAllocateDirectMemory(non-power-of-two alignment)"); Check(test, phys_addr >= search_start, "aligned allocation escaped below search_start"); Check(test, static_cast(phys_addr) % alignment == 0, @@ -600,7 +600,7 @@ void TestDirectAlignmentStaysWithinSearchRange() { constexpr size_t out_of_range_alignment = UINT64_MAX - (SceKernelPageSize - 1); phys_addr = -1; - const int result = Libs::LibKernel::Memory::KernelAllocateDirectMemory( + const int result = Libs::LibKernel::Memory::KernelAllocateDirectMemory( search_start, search_end, SceKernelPageSize, out_of_range_alignment, SceKernelMtypeC, &phys_addr); CheckFailed(test, result, "KernelAllocateDirectMemory(out-of-range alignment)"); @@ -621,34 +621,32 @@ void TestDefaultDirectMapUsesSystemAddressRange() { void* address = nullptr; CheckOk(test, - Libs::LibKernel::Memory::KernelMapNamedDirectMemory( - &address, SceKernelPageSize, SceKernelProtCpuRw, 0, phys_addr, - SceKernelPageSize, "system_direct"), + Libs::LibKernel::Memory::KernelMapNamedDirectMemory(&address, SceKernelPageSize, + SceKernelProtCpuRw, 0, phys_addr, + SceKernelPageSize, "system_direct"), "KernelMapNamedDirectMemory"); Check(test, address != nullptr, "direct mapping returned null"); #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS constexpr uint64_t SystemManagedMin = 0x0000040000ull; constexpr uint64_t SystemManagedMax = 0x07fffeffffull; const auto mapped = reinterpret_cast(address); - Check(test, mapped >= SystemManagedMin && - mapped + SceKernelPageSize - 1 <= SystemManagedMax, + Check(test, mapped >= SystemManagedMin && mapped + SceKernelPageSize - 1 <= SystemManagedMax, "default direct mapping fell outside the system-managed host range"); #endif CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast(address), - SceKernelPageSize), + SceKernelPageSize), "KernelMunmap"); - CheckOk(test, - Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, SceKernelPageSize), + CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, SceKernelPageSize), "KernelReleaseDirectMemory"); std::printf("[host] %-48s ok\n", test); } void TestLargeDirectMapAliasesAcrossChunks() { - const char* test = "LargeDirectMapAliasesAcrossChunks"; - constexpr uint64_t size = 0x400000; + const char* test = "LargeDirectMapAliasesAcrossChunks"; + constexpr uint64_t size = 0x400000; constexpr uint64_t boundary = 0x200000; int64_t phys_addr = 0; @@ -669,19 +667,17 @@ void TestLargeDirectMapAliasesAcrossChunks() { &second_alias, size, SceKernelProtCpuRw, 0, phys_addr, 0x10000, "large_direct_b"), "KernelMapNamedDirectMemory(second alias)"); - auto* first = static_cast(first_alias); - auto* second = static_cast(second_alias); + auto* first = static_cast(first_alias); + auto* second = static_cast(second_alias); *reinterpret_cast(first) = 0x1111222233334444ull; *reinterpret_cast(first + boundary - 8) = 0x5555666677778888ull; *reinterpret_cast(first + boundary) = 0x9999aaaabbbbccccull; *reinterpret_cast(first + size - 8) = 0xddddeeeeffff0001ull; - Check(test, *reinterpret_cast(second) == 0x1111222233334444ull && - *reinterpret_cast(second + boundary - 8) == - 0x5555666677778888ull && - *reinterpret_cast(second + boundary) == - 0x9999aaaabbbbccccull && - *reinterpret_cast(second + size - 8) == - 0xddddeeeeffff0001ull, + Check(test, + *reinterpret_cast(second) == 0x1111222233334444ull && + *reinterpret_cast(second + boundary - 8) == 0x5555666677778888ull && + *reinterpret_cast(second + boundary) == 0x9999aaaabbbbccccull && + *reinterpret_cast(second + size - 8) == 0xddddeeeeffff0001ull, "large direct aliases diverged at a mapping chunk boundary"); CheckOk(test, @@ -728,31 +724,301 @@ void TestDirectMapUnmapReusesHostAddress() { "KernelMunmap"); } - CheckOk(test, - Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, SceKernelPageSize), + CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, SceKernelPageSize), "KernelReleaseDirectMemory"); std::printf("[host] %-48s ok\n", test); } +void TestFixedReserveReplacesPartialDirectMapping() { + const char* test = "FixedReserveReplacesPartialDirectMapping"; + constexpr uint64_t page_count = 13; + constexpr uint64_t keep_pages = 5; + constexpr uint64_t total_size = SceKernelPageSize * page_count; + constexpr uint64_t keep_size = SceKernelPageSize * keep_pages; + constexpr uint64_t replace_size = total_size - keep_size; + + int64_t phys_addr = 0; + CheckOk(test, + Libs::LibKernel::Memory::KernelAllocateDirectMemory( + SceKernelDirectMemoryStart, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), + total_size, SceKernelPageSize, SceKernelMtypeC, &phys_addr), + "KernelAllocateDirectMemory"); + void* alias = nullptr; + CheckOk(test, + Libs::LibKernel::Memory::KernelMapNamedDirectMemory( + &alias, total_size, SceKernelProtCpuRw, 0, phys_addr, SceKernelPageSize, + "partial_replace_alias"), + "KernelMapNamedDirectMemory(alias)"); + *reinterpret_cast(reinterpret_cast(alias) + keep_size) = + 0x4b595459414c4941ull; // "KYTYALIA" + + void* reserve = nullptr; + CheckOk(test, + Libs::LibKernel::Memory::KernelReserveVirtualRange(&reserve, total_size, 0, + SceKernelPageSize), + "KernelReserveVirtualRange(container)"); + const auto base = reinterpret_cast(reserve); + + void* mapped = reserve; + CheckOk(test, + Libs::LibKernel::Memory::KernelMapNamedDirectMemory( + &mapped, total_size, SceKernelProtCpuRw, SceKernelMapFixed | SceKernelMapNoCoalesce, + phys_addr, SceKernelPageSize, "partial_replace_direct"), + "KernelMapNamedDirectMemory"); + Check(test, mapped == reserve, "fixed direct mapping moved"); + *reinterpret_cast(base) = 0x4b5954594b454550ull; // "KYTYKEEP" + + void* replacement = reinterpret_cast(base + keep_size); + CheckOk(test, + Libs::LibKernel::Memory::KernelReserveVirtualRange( + &replacement, replace_size, SceKernelMapFixed | SceKernelMapNoCoalesce, + SceKernelPageSize), + "KernelReserveVirtualRange(partial replacement)"); + Check(test, reinterpret_cast(replacement) == base + keep_size, + "partial fixed reservation moved"); + Check(test, *reinterpret_cast(base) == 0x4b5954594b454550ull, + "partial replacement damaged the neighboring direct mapping"); + ExpectRange(test, Query(test, base), base, base + keep_size, SceKernelProtCpuRw, 0, 1, 0, 1, + "partial_replace_direct"); + ExpectRange(test, Query(test, base + keep_size), base + keep_size, base + total_size, 0, 0, 0, + 0, 0); + + void* remapped = replacement; + CheckOk(test, + Libs::LibKernel::Memory::KernelMapNamedDirectMemory( + &remapped, replace_size, SceKernelProtCpuRw, + SceKernelMapFixed | SceKernelMapNoCoalesce, phys_addr + keep_size, + SceKernelPageSize, "partial_replace_remap"), + "KernelMapNamedDirectMemory(replacement reuse)"); + Check(test, remapped == replacement, "replacement reservation was not reusable in place"); + Check(test, *reinterpret_cast(remapped) == 0x4b595459414c4941ull, + "replacement remap did not preserve its direct-memory backing offset"); + *reinterpret_cast(remapped) = 0x4b59545952455553ull; // "KYTYREUS" + Check(test, + *reinterpret_cast(reinterpret_cast(alias) + keep_size) == + 0x4b59545952455553ull, + "replacement remap did not alias the original direct-memory backing"); + + CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, keep_size), + "KernelMunmap(direct remainder)"); + CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base + keep_size, replace_size), + "KernelMunmap(reused replacement)"); + CheckOk(test, + Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast(alias), total_size), + "KernelMunmap(alias)"); + CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, total_size), + "KernelReleaseDirectMemory"); + + std::printf("[host] %-48s ok\n", test); +} + +void TestFixedReserveRollbackSkipsUntouchedChunks() { + const char* test = "FixedReserveRollbackSkipsUntouchedChunks"; + constexpr uint64_t part_size = SceKernelPageSize * 2; + constexpr uint64_t total_size = part_size * 2; + int64_t left_phys = 0; + int64_t right_phys = 0; + + CheckOk(test, + Libs::LibKernel::Memory::KernelAllocateDirectMemory( + SceKernelDirectMemoryStart, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), + part_size, SceKernelPageSize, SceKernelMtypeC, &left_phys), + "KernelAllocateDirectMemory(left)"); + CheckOk(test, + Libs::LibKernel::Memory::KernelAllocateDirectMemory( + SceKernelDirectMemoryStart, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), + part_size, SceKernelPageSize, SceKernelMtypeC, &right_phys), + "KernelAllocateDirectMemory(right)"); + + void* reserve = nullptr; + CheckOk(test, + Libs::LibKernel::Memory::KernelReserveVirtualRange(&reserve, total_size, 0, + SceKernelPageSize), + "KernelReserveVirtualRange"); + const auto base = reinterpret_cast(reserve); + void* left = reserve; + void* right = reinterpret_cast(base + part_size); + CheckOk(test, + Libs::LibKernel::Memory::KernelMapNamedDirectMemory( + &left, part_size, SceKernelProtCpuRw, SceKernelMapFixed | SceKernelMapNoCoalesce, + left_phys, SceKernelPageSize, "rollback_left"), + "KernelMapNamedDirectMemory(left)"); + CheckOk(test, + Libs::LibKernel::Memory::KernelMapNamedDirectMemory( + &right, part_size, SceKernelProtCpuRw, SceKernelMapFixed | SceKernelMapNoCoalesce, + right_phys, SceKernelPageSize, "rollback_right"), + "KernelMapNamedDirectMemory(right)"); + *reinterpret_cast(left) = 0x4b5954594c454654ull; // "KYTYLEFT" + *reinterpret_cast(right) = 0x4b59545952474854ull; // "KYTYRGHT" + + Libs::LibKernel::Memory::TestFailPhysicalMemoryUnmapAfter(1); + void* replacement = reserve; + CheckFailed(test, + Libs::LibKernel::Memory::KernelReserveVirtualRange( + &replacement, total_size, SceKernelMapFixed | SceKernelMapNoCoalesce, + SceKernelPageSize), + "KernelReserveVirtualRange(second-chunk rollback)"); + Check(test, *reinterpret_cast(left) == 0x4b5954594c454654ull, + "rollback did not restore the mutated first chunk"); + Check(test, *reinterpret_cast(right) == 0x4b59545952474854ull, + "rollback damaged the failing second chunk"); + Check(test, !Libs::LibKernel::Memory::TestPlaceholderRangeIsFree(base, part_size), + "first restored mapping remained recorded as a free placeholder"); + Check(test, !Libs::LibKernel::Memory::TestPlaceholderRangeIsFree(base + part_size, part_size), + "second restored mapping remained recorded as a free placeholder"); + + CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, part_size), "KernelMunmap(left)"); + CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base + part_size, part_size), + "KernelMunmap(right)"); + CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(left_phys, part_size), + "KernelReleaseDirectMemory(left)"); + CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(right_phys, part_size), + "KernelReleaseDirectMemory(right)"); + + std::printf("[host] %-48s ok\n", test); +} + +void TestFixedReserveRollbackConsumesRestoredPlaceholder() { + const char* test = "FixedReserveRollbackConsumesRestoredPlaceholder"; + constexpr uint64_t total_size = SceKernelPageSize * 4; + + int64_t phys_addr = 0; + CheckOk(test, + Libs::LibKernel::Memory::KernelAllocateDirectMemory( + SceKernelDirectMemoryStart, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), + total_size, SceKernelPageSize, SceKernelMtypeC, &phys_addr), + "KernelAllocateDirectMemory"); + + void* reserve = nullptr; + CheckOk(test, + Libs::LibKernel::Memory::KernelReserveVirtualRange(&reserve, total_size, 0, + SceKernelPageSize), + "KernelReserveVirtualRange"); + const auto base = reinterpret_cast(reserve); + + void* mapped = reserve; + CheckOk(test, + Libs::LibKernel::Memory::KernelMapNamedDirectMemory( + &mapped, total_size, SceKernelProtCpuRw, SceKernelMapFixed | SceKernelMapNoCoalesce, + phys_addr, SceKernelPageSize, "rollback_direct"), + "KernelMapNamedDirectMemory"); + *reinterpret_cast(base) = 0x4b595459524f4c4cull; // "KYTYROLL" + + Libs::LibKernel::Memory::TestFailNextPhysicalMemoryUnmap(); + void* replacement = reserve; + CheckFailed(test, + Libs::LibKernel::Memory::KernelReserveVirtualRange( + &replacement, total_size, SceKernelMapFixed | SceKernelMapNoCoalesce, + SceKernelPageSize), + "KernelReserveVirtualRange(injected rollback)"); + Check(test, *reinterpret_cast(base) == 0x4b595459524f4c4cull, + "rollback did not restore direct-memory contents"); + Check(test, !Libs::LibKernel::Memory::TestPlaceholderRangeIsFree(base, total_size), + "rollback left a mapped direct range recorded as a free placeholder"); + + CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, total_size), "KernelMunmap"); + CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, total_size), + "KernelReleaseDirectMemory"); + + std::printf("[host] %-48s ok\n", test); +} + +void TestFixedReserveRollbackRestoresDecommittedHostPages() { + const char* test = "FixedReserveRollbackRestoresDecommittedHostPages"; + constexpr uint64_t size = SceKernelPageSize * 3; + void* mapped = nullptr; + + CheckOk(test, + Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory(&mapped, size, SceKernelProtCpuRw, + 0, "host_reserve_rollback"), + "KernelMapNamedFlexibleMemory"); + const auto base = reinterpret_cast(mapped); + *reinterpret_cast(base) = 0x4b595459484f5354ull; // "KYTYHOST" + *reinterpret_cast(base + SceKernelPageSize * 2) = + 0x4b5954595441494cull; // "KYTYTAIL" + + Libs::LibKernel::Memory::TestFailHostReservationAfter(1); + void* replacement = mapped; + CheckFailed( + test, + Libs::LibKernel::Memory::KernelReserveVirtualRange( + &replacement, size, SceKernelMapFixed | SceKernelMapNoCoalesce, SceKernelPageSize), + "KernelReserveVirtualRange(partial host reservation)"); + Check(test, *reinterpret_cast(base) == 0x4b595459484f5354ull, + "rollback did not restore the first flexible page"); + Check(test, *reinterpret_cast(base + SceKernelPageSize * 2) == 0x4b5954595441494cull, + "rollback damaged the flexible tail page"); + ExpectRange(test, Query(test, base), base, base + size, SceKernelProtCpuRw, 1, 0, 0, 1, + "host_reserve_rollback"); + + CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, size), "KernelMunmap"); + std::printf("[host] %-48s ok\n", test); +} + +void TestFixedReserveRangeAddRollbackKeepsPlaceholder() { + const char* test = "FixedReserveRangeAddRollbackKeepsPlaceholder"; + constexpr uint64_t size = SceKernelPageSize * 4; + int64_t phys_addr = 0; + + CheckOk(test, + Libs::LibKernel::Memory::KernelAllocateDirectMemory( + SceKernelDirectMemoryStart, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), + size, SceKernelPageSize, SceKernelMtypeC, &phys_addr), + "KernelAllocateDirectMemory"); + void* reserve = nullptr; + CheckOk( + test, + Libs::LibKernel::Memory::KernelReserveVirtualRange(&reserve, size, 0, SceKernelPageSize), + "KernelReserveVirtualRange"); + const auto base = reinterpret_cast(reserve); + void* mapped = reserve; + CheckOk(test, + Libs::LibKernel::Memory::KernelMapNamedDirectMemory( + &mapped, size, SceKernelProtCpuRw, SceKernelMapFixed | SceKernelMapNoCoalesce, + phys_addr, SceKernelPageSize, "range_add_rollback"), + "KernelMapNamedDirectMemory"); + *reinterpret_cast(mapped) = 0x4b59545952414e47ull; // "KTYRANG" + + Libs::LibKernel::Memory::TestFailNextFixedReserveRangeRegistration(); + void* replacement = mapped; + CheckFailed( + test, + Libs::LibKernel::Memory::KernelReserveVirtualRange( + &replacement, size, SceKernelMapFixed | SceKernelMapNoCoalesce, SceKernelPageSize), + "KernelReserveVirtualRange(range-add rollback)"); + Check(test, *reinterpret_cast(mapped) == 0x4b59545952414e47ull, + "range-add rollback did not restore direct-memory contents"); + Check(test, !Libs::LibKernel::Memory::TestPlaceholderRangeIsFree(base, size), + "range-add rollback left the restored mapping recorded as free"); + ExpectRange(test, Query(test, base), base, base + size, SceKernelProtCpuRw, 0, 1, 0, 1, + "range_add_rollback"); + + CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, size), "KernelMunmap"); + CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, size), + "KernelReleaseDirectMemory"); + std::printf("[host] %-48s ok\n", test); +} + void TestLargeHintedReserveHostsSmallDirectMap() { const char* test = "LargeHintedReserveHostsSmallDirectMap"; - constexpr uint64_t arena_base = 0x1000000000ull; + constexpr uint64_t arena_base = 0x1000000000ull; constexpr uint64_t arena_size = 0x04000000ull; constexpr uint64_t window_size = 0x00200000ull; void* arena = reinterpret_cast(arena_base); - CheckOk(test, Libs::LibKernel::Memory::KernelReserveVirtualRange( - &arena, arena_size, 0, 0x200000), + CheckOk(test, + Libs::LibKernel::Memory::KernelReserveVirtualRange(&arena, arena_size, 0, 0x200000), "KernelReserveVirtualRange(arena)"); const auto actual_arena = reinterpret_cast(arena); Check(test, actual_arena >= arena_base && (actual_arena & (0x200000 - 1u)) == 0, "large hinted reserve violated its search start or alignment"); void* window = reinterpret_cast(arena_base); - CheckOk(test, Libs::LibKernel::Memory::KernelReserveVirtualRange( - &window, window_size, 0, SceKernelPageSize), + CheckOk(test, + Libs::LibKernel::Memory::KernelReserveVirtualRange(&window, window_size, 0, + SceKernelPageSize), "KernelReserveVirtualRange(window)"); Check(test, reinterpret_cast(window) >= actual_arena + arena_size, "second hinted reserve overlaps the large arena"); @@ -773,17 +1039,17 @@ void TestLargeHintedReserveHostsSmallDirectMap() { Check(test, mapped == window, "fixed direct mapping moved away from the reserved window"); *reinterpret_cast(mapped) = 0x4b59545952455356ull; // "KYTYRESV" - CheckOk(test, Libs::LibKernel::Memory::KernelMunmap( - reinterpret_cast(mapped), SceKernelPageSize * 2), + CheckOk(test, + Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast(mapped), + SceKernelPageSize * 2), "KernelMunmap(direct)"); - CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory( - phys, SceKernelPageSize * 2), + CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys, SceKernelPageSize * 2), "KernelReleaseDirectMemory"); - CheckOk(test, Libs::LibKernel::Memory::KernelMunmap( - reinterpret_cast(window), window_size), + CheckOk(test, + Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast(window), window_size), "KernelMunmap(window reserve)"); - CheckOk(test, Libs::LibKernel::Memory::KernelMunmap( - reinterpret_cast(arena), arena_size), + CheckOk(test, + Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast(arena), arena_size), "KernelMunmap(arena reserve)"); std::printf("[host] %-48s ok\n", test); @@ -1155,17 +1421,16 @@ void TestMemoryPoolCommitDecommitQueryFlags() { void TestProgramMemoryRegistrationAndProtection() { const char* test = "ProgramMemoryRegistrationAndProtection"; const auto size = SceKernelPageSize * 3; - const auto base = Common::VirtualMemory::Alloc(0, size, Common::VirtualMemory::Mode::ReadWrite); + const auto base = Common::VirtualMemory::Alloc(0, size, Common::VirtualMemory::Mode::ReadWrite); Check(test, base != 0, "program host allocation failed"); - Libs::LibKernel::Memory::RegisterProgramMemory(base, size, - Common::VirtualMemory::Mode::ReadWrite, - "program_test"); - ExpectRange(test, Query(test, base), base, base + size, SceKernelProtCpuRead | SceKernelProtCpuRw, - 0, 0, 0, 1, "program_test"); + Libs::LibKernel::Memory::RegisterProgramMemory( + base, size, Common::VirtualMemory::Mode::ReadWrite, "program_test"); + ExpectRange(test, Query(test, base), base, base + size, + SceKernelProtCpuRead | SceKernelProtCpuRw, 0, 0, 0, 1, "program_test"); - Libs::LibKernel::Memory::UpdateProgramMemoryProtection( - base, SceKernelPageSize, Common::VirtualMemory::Mode::Read); + Libs::LibKernel::Memory::UpdateProgramMemoryProtection(base, SceKernelPageSize, + Common::VirtualMemory::Mode::Read); ExpectRange(test, Query(test, base), base, base + SceKernelPageSize, SceKernelProtCpuRead, 0, 0, 0, 1, "program_test"); @@ -1208,6 +1473,11 @@ int main() { RunTest(TestDefaultDirectMapUsesSystemAddressRange); RunTest(TestLargeDirectMapAliasesAcrossChunks); RunTest(TestDirectMapUnmapReusesHostAddress); + RunTest(TestFixedReserveReplacesPartialDirectMapping); + RunTest(TestFixedReserveRollbackConsumesRestoredPlaceholder); + RunTest(TestFixedReserveRollbackSkipsUntouchedChunks); + RunTest(TestFixedReserveRollbackRestoresDecommittedHostPages); + RunTest(TestFixedReserveRangeAddRollbackKeepsPlaceholder); RunTest(TestLargeHintedReserveHostsSmallDirectMap); RunTest(TestMemoryPoolAlignmentContracts); RunTest(TestProsperoSampleMemoryPoolExpandCommit);