kernel: preserve placeholders during fixed memory replacement

This commit is contained in:
nmzik
2026-07-17 18:51:36 +02:00
parent 866d761216
commit 0621d9e9d0
5 changed files with 535 additions and 129 deletions
+2
View File
@@ -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(shader_recompiler_compute_tests ../tests/ShaderRecompilerComputeTests.cpp)
add_kyty_full_emulator_test(virtual_memory_allocation_tests ../tests/VirtualMemoryAllocationTests.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}) add_executable(kyty_emulator main.cpp ${kyty_emulator_src})
+142 -27
View File
@@ -713,6 +713,12 @@ private:
Common::Mutex m_mutex; 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 { class PhysicalMemory {
public: public:
struct AllocatedBlock { 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, bool PhysicalMemory::Unmap(uint64_t vaddr, uint64_t size, GpuAccessMode* gpu_mode,
uint64_t* host_vaddr_to_release) { 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); EXIT_IF(gpu_mode == nullptr);
Common::LockGuard lock(m_mutex); Common::LockGuard lock(m_mutex);
@@ -2943,17 +2959,35 @@ static bool ReserveFixedHostRange(uint64_t start, uint64_t size) {
} }
#endif #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) { for (uint64_t addr = start; addr < start + size; addr += PAGE_SIZE) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
MEMORY_BASIC_INFORMATION info {}; MEMORY_BASIC_INFORMATION info {};
if (VirtualQuery(reinterpret_cast<const void*>(addr), &info, sizeof(info)) != 0) { if (VirtualQuery(reinterpret_cast<const void*>(addr), &info, sizeof(info)) != 0) {
if (info.State == MEM_COMMIT) { if (info.State == MEM_COMMIT) {
if (!VirtualMemory::Decommit(addr, PAGE_SIZE)) { 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, LOGF_COLOR(Log::Color::Red,
"\t reserve-fixed replace: decommit failed at 0x%016" PRIx64 "\n", "\t reserve-fixed replace: decommit failed at 0x%016" PRIx64 "\n",
addr); addr);
return false; return false;
} }
host_mutated = true;
continue; continue;
} }
if (info.State == MEM_RESERVE) { if (info.State == MEM_RESERVE) {
@@ -2962,10 +2996,14 @@ static bool ReserveFixedHostRange(uint64_t start, uint64_t size) {
} }
#endif #endif
if (!VirtualMemory::ReserveFixed(addr, PAGE_SIZE)) { 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, LOGF_COLOR(Log::Color::Red,
"\t reserve-fixed replace: reserve failed at 0x%016" PRIx64 "\n", addr); "\t reserve-fixed replace: reserve failed at 0x%016" PRIx64 "\n", addr);
return false; return false;
} }
host_mutated = true;
} }
return true; return true;
@@ -2978,9 +3016,12 @@ static bool ReplaceFixedRangeWithReserved(uint64_t start, uint64_t size, bool* p
struct ReplacedChunk { struct ReplacedChunk {
VirtualRanges::Range range {}; VirtualRanges::Range range {};
VirtualMemory::Mode mode = VirtualMemory::Mode::NoAccess; VirtualMemory::Mode mode = VirtualMemory::Mode::NoAccess;
GpuAccessMode gpu_mode = GpuAccessMode::NoAccess; GpuAccessMode gpu_mode = GpuAccessMode::NoAccess;
bool shared_backing = false; bool shared_backing = false;
bool placeholder_restored = false;
bool host_unmapped = false;
bool backend_unmapped = false;
}; };
std::vector<ReplacedChunk> chunks; std::vector<ReplacedChunk> chunks;
@@ -3026,22 +3067,39 @@ static bool ReplaceFixedRangeWithReserved(uint64_t start, uint64_t size, bool* p
bool backend_restored = true; bool backend_restored = true;
if (chunk.range.type == VirtualRangeType::Direct) { if (chunk.range.type == VirtualRangeType::Direct) {
host_restored = if (chunk.host_unmapped && chunk.placeholder_restored) {
(chunk.shared_backing const bool consumed =
? g_direct_memory_backing->MapFixed(chunk.range.start, chunk.range.size, g_placeholder_address_space->Consume(chunk.range.start, chunk.range.size);
chunk.range.offset, chunk.mode) host_restored =
: CommitFixedHostRange(chunk.range.start, chunk.range.size, chunk.mode)); consumed &&
backend_restored = g_direct_memory_backing->MapExistingPlaceholderFixed(
g_physical_memory->Map(chunk.range.start, chunk.range.offset, chunk.range.size, chunk.range.start, chunk.range.size, chunk.range.offset, chunk.mode);
chunk.range.protection, chunk.mode, chunk.gpu_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 || } else if (chunk.range.type == VirtualRangeType::Flexible ||
chunk.range.type == VirtualRangeType::Stack || chunk.range.type == VirtualRangeType::Stack ||
chunk.range.type == VirtualRangeType::Pooled) { chunk.range.type == VirtualRangeType::Pooled) {
host_restored = host_restored =
CommitFixedHostRange(chunk.range.start, chunk.range.size, chunk.mode); CommitFixedHostRange(chunk.range.start, chunk.range.size, chunk.mode);
backend_restored = g_flexible_memory->Map(chunk.range.start, chunk.range.size, if (chunk.backend_unmapped) {
chunk.range.protection, chunk.mode, backend_restored = g_flexible_memory->Map(chunk.range.start, chunk.range.size,
chunk.gpu_mode, chunk.range.name); chunk.range.protection, chunk.mode,
chunk.gpu_mode, chunk.range.name);
}
} }
const bool range_restored = g_virtual_ranges->Add( 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) { const bool gpu_unmapped = std::any_of(chunks.begin(), chunks.end(), [](const auto& chunk) {
return IsCommittedRangeType(chunk.range.type) && return IsCommittedRangeType(chunk.range.type) &&
chunk.gpu_mode != GpuAccessMode::NoAccess &&
IsGpuAddressRange(chunk.range.start, chunk.range.size); IsGpuAddressRange(chunk.range.start, chunk.range.size);
}); });
for (const auto& chunk: chunks) { 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.range.type == VirtualRangeType::Direct) {
if (chunk.shared_backing) { if (chunk.shared_backing) {
bool chunk_placeholder_restored = false; 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)) { &chunk_placeholder_restored)) {
unmapped = false; unmapped = false;
} else if (chunk_placeholder_restored) { } else {
g_placeholder_address_space->AddFree(chunk.range.start, chunk.range.size); 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) { if (unmapped) {
unmapped = g_physical_memory->Unmap(chunk.range.start, chunk.range.size, &gpu_mode); 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 || } else if (chunk.range.type == VirtualRangeType::Flexible ||
chunk.range.type == VirtualRangeType::Stack || chunk.range.type == VirtualRangeType::Stack ||
chunk.range.type == VirtualRangeType::Pooled) { chunk.range.type == VirtualRangeType::Pooled) {
unmapped = g_flexible_memory->Unmap(chunk.range.start, chunk.range.size, &gpu_mode); 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) { if (!unmapped) {
@@ -3108,7 +3173,9 @@ static bool ReplaceFixedRangeWithReserved(uint64_t start, uint64_t size, bool* p
", size=0x%016" PRIx64 ", type=%s\n", ", size=0x%016" PRIx64 ", type=%s\n",
chunk.range.start, chunk.range.size, chunk.range.start, chunk.range.size,
Common::EnumName(chunk.range.type).c_str()); Common::EnumName(chunk.range.type).c_str());
restore_chunks(); if (!restore_chunks()) {
EXIT("reserve-fixed backend-unmap rollback failed\n");
}
return false; return false;
} }
} }
@@ -3121,27 +3188,53 @@ static bool ReplaceFixedRangeWithReserved(uint64_t start, uint64_t size, bool* p
" size=0x%016" PRIx64 "\n", " size=0x%016" PRIx64 "\n",
start, size); start, size);
} }
restore_chunks(); if (!restore_chunks()) {
EXIT("reserve-fixed host-reservation rollback failed\n");
}
return false; return false;
} }
if (!g_virtual_ranges->Add(start, size, 0, 0, 0, VirtualRangeType::Reserved, "anon", false, bool range_added = false;
*placeholder_backed)) { #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) { if (gpu_unmapped) {
EXIT("reserve-fixed range registration failed after GPU unmap: addr=0x%016" PRIx64 EXIT("reserve-fixed range registration failed after GPU unmap: addr=0x%016" PRIx64
" size=0x%016" PRIx64 "\n", " size=0x%016" PRIx64 "\n",
start, size); start, size);
} }
if (*placeholder_backed) { if (!*placeholder_backed) {
g_placeholder_address_space->ReleaseFree(start, size);
} else {
VirtualMemory::Free(start); VirtualMemory::Free(start);
} }
LOGF_COLOR(Log::Color::Red, LOGF_COLOR(Log::Color::Red,
"\t reserve-fixed replace: range add failed at 0x%016" PRIx64 "\t reserve-fixed replace: range add failed at 0x%016" PRIx64
", size=0x%016" PRIx64 "\n", ", size=0x%016" PRIx64 "\n",
start, size); 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; return false;
} }
@@ -3224,6 +3317,28 @@ int KYTY_SYSV_ABI KernelReserveVirtualRange(void** addr, size_t len, int flags,
return OK; 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) { bool KernelHandleReservedRangeAccessViolation(uint64_t vaddr) {
std::lock_guard<std::recursive_mutex> memory_operation_lock(g_memory_operation_mutex); std::lock_guard<std::recursive_mutex> memory_operation_lock(g_memory_operation_mutex);
+8
View File
@@ -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 UpdateProgramMemoryProtection(uint64_t vaddr, uint64_t size, Common::VirtualMemory::Mode mode);
void UnregisterProgramMemory(uint64_t vaddr, uint64_t size); 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 } // namespace Libs::LibKernel::Memory
#endif /* EMULATOR_INCLUDE_EMULATOR_KERNEL_MEMORY_H_ */ #endif /* EMULATOR_INCLUDE_EMULATOR_KERNEL_MEMORY_H_ */
+35 -24
View File
@@ -151,10 +151,10 @@ public:
return false; return false;
} }
auto* destination = static_cast<uint8_t*>(data); auto* destination = static_cast<uint8_t*>(data);
return TryTransferBacking(vaddr, size, return TryTransferBacking(
[destination](uint8_t* backing, uint64_t data_offset, size_t bytes) { vaddr, size, [destination](uint8_t* backing, uint64_t data_offset, size_t bytes) {
std::memcpy(destination + data_offset, backing, bytes); std::memcpy(destination + data_offset, backing, bytes);
}); });
} }
uint64_t MapAligned(uint64_t search_addr, uint64_t size, uint64_t backing_offset, uint64_t MapAligned(uint64_t search_addr, uint64_t size, uint64_t backing_offset,
@@ -342,7 +342,7 @@ public:
MapEntry old {}; MapEntry old {};
{ {
std::lock_guard lock(m_mutex); 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()) { if (next != m_maps.begin()) {
auto entry = std::prev(next); auto entry = std::prev(next);
if (vaddr + size <= entry->second.vaddr + entry->second.size) { if (vaddr + size <= entry->second.vaddr + entry->second.size) {
@@ -497,8 +497,8 @@ public:
constexpr uint64_t test_value = 0x4b595459414c4941ull; // "KYTYALIA" constexpr uint64_t test_value = 0x4b595459414c4941ull; // "KYTYALIA"
EXIT_IF(!VirtualMemory::Protect(first, test_size, VirtualMemory::Mode::NoAccess)); EXIT_IF(!VirtualMemory::Protect(first, test_size, VirtualMemory::Mode::NoAccess));
const bool ok = TryWriteBacking(first, &test_value, sizeof(test_value)) && const bool ok =
*second_ptr == test_value; TryWriteBacking(first, &test_value, sizeof(test_value)) && *second_ptr == test_value;
const uint64_t clear = 0; const uint64_t clear = 0;
EXIT_IF(!TryWriteBacking(first, &clear, sizeof(clear))); EXIT_IF(!TryWriteBacking(first, &clear, sizeof(clear)));
@@ -518,8 +518,8 @@ private:
} }
std::lock_guard lock(m_mutex); std::lock_guard lock(m_mutex);
const auto end = vaddr + size; const auto end = vaddr + size;
auto current = vaddr; auto current = vaddr;
while (current < end) { while (current < end) {
MapEntry entry {}; MapEntry entry {};
if (!FindContainingUnlocked(current, 1, &entry)) { if (!FindContainingUnlocked(current, 1, &entry)) {
@@ -609,24 +609,24 @@ private:
[[nodiscard]] bool ShouldChunkMap(uint64_t size) const { return size > GetMapChunkSize(); } [[nodiscard]] bool ShouldChunkMap(uint64_t size) const { return size > GetMapChunkSize(); }
uint64_t MapNewPlaceholderAligned(uint64_t search_addr, uint64_t size, uint64_t MapNewPlaceholderAligned(uint64_t search_addr, uint64_t size, uint64_t backing_offset,
uint64_t backing_offset, VirtualMemory::Mode mode, VirtualMemory::Mode mode, uint64_t alignment,
uint64_t alignment, FailureReason* failure_reason) { FailureReason* failure_reason) {
MEM_ADDRESS_REQUIREMENTS requirements {}; MEM_ADDRESS_REQUIREMENTS requirements {};
MEM_EXTENDED_PARAMETER parameter {}; MEM_EXTENDED_PARAMETER parameter {};
const auto aligned_search = const auto aligned_search = (search_addr + alignment - 1u) & ~(alignment - 1u);
(search_addr + alignment - 1u) & ~(alignment - 1u); requirements.LowestStartingAddress =
requirements.LowestStartingAddress = reinterpret_cast<void*>( reinterpret_cast<void*>(search_addr != 0 ? aligned_search : HOST_SYSTEM_MANAGED_MIN);
search_addr != 0 ? aligned_search : HOST_SYSTEM_MANAGED_MIN);
requirements.HighestEndingAddress = requirements.HighestEndingAddress =
reinterpret_cast<void*>(search_addr != 0 ? HOST_USER_MAX : HOST_SYSTEM_MANAGED_MAX); reinterpret_cast<void*>(search_addr != 0 ? HOST_USER_MAX : HOST_SYSTEM_MANAGED_MAX);
requirements.Alignment = alignment; requirements.Alignment = alignment;
parameter.Type = MemExtendedParameterAddressRequirements; parameter.Type = MemExtendedParameterAddressRequirements;
parameter.Pointer = &requirements; parameter.Pointer = &requirements;
auto reserve = [&]() { auto reserve = [&]() {
return VirtualAlloc2(GetCurrentProcess(), nullptr, size, return VirtualAlloc2(GetCurrentProcess(), nullptr, size,
MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, &parameter, 1); MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, &parameter,
1);
}; };
auto* placeholder = reserve(); auto* placeholder = reserve();
if (placeholder == nullptr && search_addr == 0) { if (placeholder == nullptr && search_addr == 0) {
@@ -1056,9 +1056,9 @@ private:
int m_fd = -1; int m_fd = -1;
#endif #endif
uint8_t* m_backing_base = nullptr; uint8_t* m_backing_base = nullptr;
uint64_t m_size = 0; uint64_t m_size = 0;
std::mutex m_mutex; std::mutex m_mutex;
std::map<uint64_t, MapEntry> m_maps; std::map<uint64_t, MapEntry> m_maps;
}; };
@@ -1098,8 +1098,8 @@ public:
if (ptr == nullptr && search_addr == 0) { if (ptr == nullptr && search_addr == 0) {
req.LowestStartingAddress = reinterpret_cast<void*>(HOST_USER_MIN); req.LowestStartingAddress = reinterpret_cast<void*>(HOST_USER_MIN);
req.HighestEndingAddress = reinterpret_cast<void*>(HOST_USER_MAX); req.HighestEndingAddress = reinterpret_cast<void*>(HOST_USER_MAX);
ptr = VirtualAlloc2(GetCurrentProcess(), nullptr, size, ptr = VirtualAlloc2(GetCurrentProcess(), nullptr, size,
MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, &param, 1); MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, &param, 1);
} }
if (ptr == nullptr) { if (ptr == nullptr) {
return 0; return 0;
@@ -1161,6 +1161,17 @@ public:
#endif #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) { bool ReleaseFree(uint64_t vaddr, uint64_t size) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
std::lock_guard lock(m_mutex); std::lock_guard lock(m_mutex);
+348 -78
View File
@@ -1,11 +1,10 @@
#include "common/commonSubsystem.h" #include "common/commonSubsystem.h"
#include "common/emulatorConfig.h"
#include "common/logging/log.h"
#include "common/subsystems.h" #include "common/subsystems.h"
#include "common/threads.h" #include "common/threads.h"
#include "common/emulatorConfig.h"
#include "kernel/memory.h" #include "kernel/memory.h"
#include "libs/errno.h" #include "libs/errno.h"
#include "common/logging/log.h"
#include <cinttypes> #include <cinttypes>
#include <cstdint> #include <cstdint>
@@ -213,7 +212,8 @@ void TestFlexibleMapQueryAndWholeMunmap() {
void TestPartialFlexibleMunmapAndFindNext() { void TestPartialFlexibleMunmapAndFindNext() {
const char* test = "PartialFlexibleMunmapAndFindNext"; const char* test = "PartialFlexibleMunmapAndFindNext";
const auto baseline = AvailableFlexibleMemory(test); 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, CheckOk(test,
Libs::LibKernel::Memory::KernelMunmap(base + SceKernelPageSize, SceKernelPageSize), Libs::LibKernel::Memory::KernelMunmap(base + SceKernelPageSize, SceKernelPageSize),
@@ -300,8 +300,8 @@ void TestFixedNoOverwriteRejectsReservedRange() {
void* fixed = reinterpret_cast<void*>(base); void* fixed = reinterpret_cast<void*>(base);
const int ret = Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory( const int ret = Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory(
&fixed, SceKernelPageSize, SceKernelProtCpuRw, SceKernelMapFixed | SceKernelMapNoOverwrite, &fixed, SceKernelPageSize, SceKernelProtCpuRw, SceKernelMapFixed | SceKernelMapNoOverwrite,
"reserved_blocked"); "reserved_blocked");
const bool rejected = ret < OK; const bool rejected = ret < OK;
if (ret == OK) { if (ret == OK) {
@@ -319,7 +319,8 @@ void TestFixedNoOverwriteRejectsReservedRange() {
} }
Check(test, rejected, 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); std::printf("[host] %-48s ok\n", test);
} }
@@ -336,17 +337,17 @@ void TestDirectMapQueryOffsetAndPartialMunmap() {
void* addr = nullptr; void* addr = nullptr;
CheckOk(test, CheckOk(test,
Libs::LibKernel::Memory::KernelMapNamedDirectMemory(&addr, SceKernelPageSize * 4, Libs::LibKernel::Memory::KernelMapNamedDirectMemory(
SceKernelProtCpuRw, 0, phys_addr, &addr, SceKernelPageSize * 4, SceKernelProtCpuRw, 0, phys_addr, SceKernelPageSize,
SceKernelPageSize, "prospero_direct"), "prospero_direct"),
"KernelMapNamedDirectMemory"); "KernelMapNamedDirectMemory");
const auto base = reinterpret_cast<uint64_t>(addr); const auto base = reinterpret_cast<uint64_t>(addr);
const auto phys = static_cast<uint64_t>(phys_addr); const auto phys = static_cast<uint64_t>(phys_addr);
void* alias = nullptr; void* alias = nullptr;
CheckOk(test, CheckOk(test,
Libs::LibKernel::Memory::KernelMapNamedDirectMemory( Libs::LibKernel::Memory::KernelMapNamedDirectMemory(
&alias, SceKernelPageSize * 4, SceKernelProtCpuRw, 0, phys_addr, &alias, SceKernelPageSize * 4, SceKernelProtCpuRw, 0, phys_addr, SceKernelPageSize,
SceKernelPageSize, "prospero_direct_alias"), "prospero_direct_alias"),
"KernelMapNamedDirectMemory(alias)"); "KernelMapNamedDirectMemory(alias)");
const auto alias_base = reinterpret_cast<uint64_t>(alias); const auto alias_base = reinterpret_cast<uint64_t>(alias);
@@ -367,7 +368,7 @@ void TestDirectMapQueryOffsetAndPartialMunmap() {
backing_read = 0; backing_read = 0;
Check(test, Check(test,
Libs::LibKernel::Memory::TryReadBacking(base + sizeof(uint64_t), &backing_read, Libs::LibKernel::Memory::TryReadBacking(base + sizeof(uint64_t), &backing_read,
sizeof(backing_read)), sizeof(backing_read)),
"TryReadBacking should resolve an aliased physical offset"); "TryReadBacking should resolve an aliased physical offset");
Check(test, backing_read == backing_write, Check(test, backing_read == backing_write,
"backing reads and writes should preserve direct-memory aliasing"); "backing reads and writes should preserve direct-memory aliasing");
@@ -416,8 +417,7 @@ void TestDirectMapQueryOffsetAndPartialMunmap() {
test, test,
Libs::LibKernel::Memory::KernelMunmap(base + SceKernelPageSize * 2, SceKernelPageSize * 2), Libs::LibKernel::Memory::KernelMunmap(base + SceKernelPageSize * 2, SceKernelPageSize * 2),
"KernelMunmap(direct right cleanup)"); "KernelMunmap(direct right cleanup)");
CheckOk(test, CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(alias_base, SceKernelPageSize * 4),
Libs::LibKernel::Memory::KernelMunmap(alias_base, SceKernelPageSize * 4),
"KernelMunmap(direct alias cleanup)"); "KernelMunmap(direct alias cleanup)");
CheckOk(test, CheckOk(test,
Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, SceKernelPageSize * 4), Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, SceKernelPageSize * 4),
@@ -435,8 +435,7 @@ void TestReleasedReserveCanBeReused() {
SceKernelPageSize), SceKernelPageSize),
"KernelReserveVirtualRange"); "KernelReserveVirtualRange");
const auto base = reinterpret_cast<uint64_t>(addr); const auto base = reinterpret_cast<uint64_t>(addr);
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize), CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize), "KernelMunmap");
"KernelMunmap");
void* reused = reinterpret_cast<void*>(base); void* reused = reinterpret_cast<void*>(base);
CheckOk(test, CheckOk(test,
@@ -458,8 +457,8 @@ void TestMunmapAcrossAdjacentFlexibleMappings() {
void* reserve = nullptr; void* reserve = nullptr;
CheckOk(test, CheckOk(test,
Libs::LibKernel::Memory::KernelReserveVirtualRange( Libs::LibKernel::Memory::KernelReserveVirtualRange(&reserve, SceKernelPageSize * 2, 0,
&reserve, SceKernelPageSize * 2, 0, SceKernelPageSize), SceKernelPageSize),
"KernelReserveVirtualRange"); "KernelReserveVirtualRange");
const auto base = reinterpret_cast<uint64_t>(reserve); const auto base = reinterpret_cast<uint64_t>(reserve);
@@ -471,8 +470,7 @@ void TestMunmapAcrossAdjacentFlexibleMappings() {
"KernelMapNamedFlexibleMemory(left)"); "KernelMapNamedFlexibleMemory(left)");
CheckOk(test, CheckOk(test,
Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory( Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory(
&right, SceKernelPageSize, SceKernelProtCpuRw, SceKernelMapFixed, &right, SceKernelPageSize, SceKernelProtCpuRw, SceKernelMapFixed, "adjacent_right"),
"adjacent_right"),
"KernelMapNamedFlexibleMemory(right)"); "KernelMapNamedFlexibleMemory(right)");
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize * 2), CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelPageSize * 2),
@@ -512,24 +510,26 @@ void TestNonzeroDirectOffsetAliasesSharedBacking() {
void* second_alias = nullptr; void* second_alias = nullptr;
CheckOk(test, CheckOk(test,
Libs::LibKernel::Memory::KernelMapNamedDirectMemory( Libs::LibKernel::Memory::KernelMapNamedDirectMemory(
&first_alias, SceKernelPageSize, SceKernelProtCpuRw, 0, second, &first_alias, SceKernelPageSize, SceKernelProtCpuRw, 0, second, SceKernelPageSize,
SceKernelPageSize, "prospero_nonzero_a"), "prospero_nonzero_a"),
"KernelMapNamedDirectMemory(first alias)"); "KernelMapNamedDirectMemory(first alias)");
CheckOk(test, CheckOk(test,
Libs::LibKernel::Memory::KernelMapNamedDirectMemory( Libs::LibKernel::Memory::KernelMapNamedDirectMemory(
&second_alias, SceKernelPageSize, SceKernelProtCpuRw, 0, second, &second_alias, SceKernelPageSize, SceKernelProtCpuRw, 0, second, SceKernelPageSize,
SceKernelPageSize, "prospero_nonzero_b"), "prospero_nonzero_b"),
"KernelMapNamedDirectMemory(second alias)"); "KernelMapNamedDirectMemory(second alias)");
*reinterpret_cast<uint64_t*>(first_alias) = 0x4b59545931364b42ull; // "KYTY16KB" *reinterpret_cast<uint64_t*>(first_alias) = 0x4b59545931364b42ull; // "KYTY16KB"
Check(test, *reinterpret_cast<const uint64_t*>(second_alias) == 0x4b59545931364b42ull, Check(test, *reinterpret_cast<const uint64_t*>(second_alias) == 0x4b59545931364b42ull,
"nonzero-offset mappings must share backing storage"); "nonzero-offset mappings must share backing storage");
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap( CheckOk(test,
reinterpret_cast<uint64_t>(first_alias), SceKernelPageSize), Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast<uint64_t>(first_alias),
SceKernelPageSize),
"KernelMunmap(first alias)"); "KernelMunmap(first alias)");
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap( CheckOk(test,
reinterpret_cast<uint64_t>(second_alias), SceKernelPageSize), Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast<uint64_t>(second_alias),
SceKernelPageSize),
"KernelMunmap(second alias)"); "KernelMunmap(second alias)");
CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(second, SceKernelPageSize), CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(second, SceKernelPageSize),
"KernelReleaseDirectMemory(second)"); "KernelReleaseDirectMemory(second)");
@@ -581,16 +581,16 @@ void TestDirectPhysicalFreeRangeReuseAndCoalescing() {
} }
void TestDirectAlignmentStaysWithinSearchRange() { void TestDirectAlignmentStaysWithinSearchRange() {
const char* test = "DirectAlignmentStaysWithinSearchRange"; const char* test = "DirectAlignmentStaysWithinSearchRange";
constexpr int64_t search_start = SceKernelPageSize * 2; constexpr int64_t search_start = SceKernelPageSize * 2;
constexpr uint64_t alignment = SceKernelPageSize * 3; constexpr uint64_t alignment = SceKernelPageSize * 3;
const auto search_end = Libs::LibKernel::Memory::KernelGetDirectMemorySize(); const auto search_end = Libs::LibKernel::Memory::KernelGetDirectMemorySize();
int64_t phys_addr = -1; int64_t phys_addr = -1;
CheckOk(test, CheckOk(test,
Libs::LibKernel::Memory::KernelAllocateDirectMemory( Libs::LibKernel::Memory::KernelAllocateDirectMemory(search_start, search_end,
search_start, search_end, SceKernelPageSize, alignment, SceKernelMtypeC, SceKernelPageSize, alignment,
&phys_addr), SceKernelMtypeC, &phys_addr),
"KernelAllocateDirectMemory(non-power-of-two alignment)"); "KernelAllocateDirectMemory(non-power-of-two alignment)");
Check(test, phys_addr >= search_start, "aligned allocation escaped below search_start"); Check(test, phys_addr >= search_start, "aligned allocation escaped below search_start");
Check(test, static_cast<uint64_t>(phys_addr) % alignment == 0, Check(test, static_cast<uint64_t>(phys_addr) % alignment == 0,
@@ -600,7 +600,7 @@ void TestDirectAlignmentStaysWithinSearchRange() {
constexpr size_t out_of_range_alignment = UINT64_MAX - (SceKernelPageSize - 1); constexpr size_t out_of_range_alignment = UINT64_MAX - (SceKernelPageSize - 1);
phys_addr = -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, search_start, search_end, SceKernelPageSize, out_of_range_alignment, SceKernelMtypeC,
&phys_addr); &phys_addr);
CheckFailed(test, result, "KernelAllocateDirectMemory(out-of-range alignment)"); CheckFailed(test, result, "KernelAllocateDirectMemory(out-of-range alignment)");
@@ -621,34 +621,32 @@ void TestDefaultDirectMapUsesSystemAddressRange() {
void* address = nullptr; void* address = nullptr;
CheckOk(test, CheckOk(test,
Libs::LibKernel::Memory::KernelMapNamedDirectMemory( Libs::LibKernel::Memory::KernelMapNamedDirectMemory(&address, SceKernelPageSize,
&address, SceKernelPageSize, SceKernelProtCpuRw, 0, phys_addr, SceKernelProtCpuRw, 0, phys_addr,
SceKernelPageSize, "system_direct"), SceKernelPageSize, "system_direct"),
"KernelMapNamedDirectMemory"); "KernelMapNamedDirectMemory");
Check(test, address != nullptr, "direct mapping returned null"); Check(test, address != nullptr, "direct mapping returned null");
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
constexpr uint64_t SystemManagedMin = 0x0000040000ull; constexpr uint64_t SystemManagedMin = 0x0000040000ull;
constexpr uint64_t SystemManagedMax = 0x07fffeffffull; constexpr uint64_t SystemManagedMax = 0x07fffeffffull;
const auto mapped = reinterpret_cast<uint64_t>(address); const auto mapped = reinterpret_cast<uint64_t>(address);
Check(test, mapped >= SystemManagedMin && Check(test, mapped >= SystemManagedMin && mapped + SceKernelPageSize - 1 <= SystemManagedMax,
mapped + SceKernelPageSize - 1 <= SystemManagedMax,
"default direct mapping fell outside the system-managed host range"); "default direct mapping fell outside the system-managed host range");
#endif #endif
CheckOk(test, CheckOk(test,
Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast<uint64_t>(address), Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast<uint64_t>(address),
SceKernelPageSize), SceKernelPageSize),
"KernelMunmap"); "KernelMunmap");
CheckOk(test, CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, SceKernelPageSize),
Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, SceKernelPageSize),
"KernelReleaseDirectMemory"); "KernelReleaseDirectMemory");
std::printf("[host] %-48s ok\n", test); std::printf("[host] %-48s ok\n", test);
} }
void TestLargeDirectMapAliasesAcrossChunks() { void TestLargeDirectMapAliasesAcrossChunks() {
const char* test = "LargeDirectMapAliasesAcrossChunks"; const char* test = "LargeDirectMapAliasesAcrossChunks";
constexpr uint64_t size = 0x400000; constexpr uint64_t size = 0x400000;
constexpr uint64_t boundary = 0x200000; constexpr uint64_t boundary = 0x200000;
int64_t phys_addr = 0; int64_t phys_addr = 0;
@@ -669,19 +667,17 @@ void TestLargeDirectMapAliasesAcrossChunks() {
&second_alias, size, SceKernelProtCpuRw, 0, phys_addr, 0x10000, "large_direct_b"), &second_alias, size, SceKernelProtCpuRw, 0, phys_addr, 0x10000, "large_direct_b"),
"KernelMapNamedDirectMemory(second alias)"); "KernelMapNamedDirectMemory(second alias)");
auto* first = static_cast<uint8_t*>(first_alias); auto* first = static_cast<uint8_t*>(first_alias);
auto* second = static_cast<uint8_t*>(second_alias); auto* second = static_cast<uint8_t*>(second_alias);
*reinterpret_cast<uint64_t*>(first) = 0x1111222233334444ull; *reinterpret_cast<uint64_t*>(first) = 0x1111222233334444ull;
*reinterpret_cast<uint64_t*>(first + boundary - 8) = 0x5555666677778888ull; *reinterpret_cast<uint64_t*>(first + boundary - 8) = 0x5555666677778888ull;
*reinterpret_cast<uint64_t*>(first + boundary) = 0x9999aaaabbbbccccull; *reinterpret_cast<uint64_t*>(first + boundary) = 0x9999aaaabbbbccccull;
*reinterpret_cast<uint64_t*>(first + size - 8) = 0xddddeeeeffff0001ull; *reinterpret_cast<uint64_t*>(first + size - 8) = 0xddddeeeeffff0001ull;
Check(test, *reinterpret_cast<const uint64_t*>(second) == 0x1111222233334444ull && Check(test,
*reinterpret_cast<const uint64_t*>(second + boundary - 8) == *reinterpret_cast<const uint64_t*>(second) == 0x1111222233334444ull &&
0x5555666677778888ull && *reinterpret_cast<const uint64_t*>(second + boundary - 8) == 0x5555666677778888ull &&
*reinterpret_cast<const uint64_t*>(second + boundary) == *reinterpret_cast<const uint64_t*>(second + boundary) == 0x9999aaaabbbbccccull &&
0x9999aaaabbbbccccull && *reinterpret_cast<const uint64_t*>(second + size - 8) == 0xddddeeeeffff0001ull,
*reinterpret_cast<const uint64_t*>(second + size - 8) ==
0xddddeeeeffff0001ull,
"large direct aliases diverged at a mapping chunk boundary"); "large direct aliases diverged at a mapping chunk boundary");
CheckOk(test, CheckOk(test,
@@ -728,31 +724,301 @@ void TestDirectMapUnmapReusesHostAddress() {
"KernelMunmap"); "KernelMunmap");
} }
CheckOk(test, CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, SceKernelPageSize),
Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys_addr, SceKernelPageSize),
"KernelReleaseDirectMemory"); "KernelReleaseDirectMemory");
std::printf("[host] %-48s ok\n", test); 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<uint64_t*>(reinterpret_cast<uint64_t>(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<uint64_t>(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<uint64_t*>(base) = 0x4b5954594b454550ull; // "KYTYKEEP"
void* replacement = reinterpret_cast<void*>(base + keep_size);
CheckOk(test,
Libs::LibKernel::Memory::KernelReserveVirtualRange(
&replacement, replace_size, SceKernelMapFixed | SceKernelMapNoCoalesce,
SceKernelPageSize),
"KernelReserveVirtualRange(partial replacement)");
Check(test, reinterpret_cast<uint64_t>(replacement) == base + keep_size,
"partial fixed reservation moved");
Check(test, *reinterpret_cast<uint64_t*>(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<uint64_t*>(remapped) == 0x4b595459414c4941ull,
"replacement remap did not preserve its direct-memory backing offset");
*reinterpret_cast<uint64_t*>(remapped) = 0x4b59545952455553ull; // "KYTYREUS"
Check(test,
*reinterpret_cast<uint64_t*>(reinterpret_cast<uint64_t>(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<uint64_t>(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<uint64_t>(reserve);
void* left = reserve;
void* right = reinterpret_cast<void*>(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<uint64_t*>(left) = 0x4b5954594c454654ull; // "KYTYLEFT"
*reinterpret_cast<uint64_t*>(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<uint64_t*>(left) == 0x4b5954594c454654ull,
"rollback did not restore the mutated first chunk");
Check(test, *reinterpret_cast<uint64_t*>(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<uint64_t>(reserve);
void* mapped = reserve;
CheckOk(test,
Libs::LibKernel::Memory::KernelMapNamedDirectMemory(
&mapped, total_size, SceKernelProtCpuRw, SceKernelMapFixed | SceKernelMapNoCoalesce,
phys_addr, SceKernelPageSize, "rollback_direct"),
"KernelMapNamedDirectMemory");
*reinterpret_cast<uint64_t*>(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<uint64_t*>(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<uint64_t>(mapped);
*reinterpret_cast<uint64_t*>(base) = 0x4b595459484f5354ull; // "KYTYHOST"
*reinterpret_cast<uint64_t*>(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<uint64_t*>(base) == 0x4b595459484f5354ull,
"rollback did not restore the first flexible page");
Check(test, *reinterpret_cast<uint64_t*>(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<uint64_t>(reserve);
void* mapped = reserve;
CheckOk(test,
Libs::LibKernel::Memory::KernelMapNamedDirectMemory(
&mapped, size, SceKernelProtCpuRw, SceKernelMapFixed | SceKernelMapNoCoalesce,
phys_addr, SceKernelPageSize, "range_add_rollback"),
"KernelMapNamedDirectMemory");
*reinterpret_cast<uint64_t*>(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<uint64_t*>(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() { void TestLargeHintedReserveHostsSmallDirectMap() {
const char* test = "LargeHintedReserveHostsSmallDirectMap"; 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 arena_size = 0x04000000ull;
constexpr uint64_t window_size = 0x00200000ull; constexpr uint64_t window_size = 0x00200000ull;
void* arena = reinterpret_cast<void*>(arena_base); void* arena = reinterpret_cast<void*>(arena_base);
CheckOk(test, Libs::LibKernel::Memory::KernelReserveVirtualRange( CheckOk(test,
&arena, arena_size, 0, 0x200000), Libs::LibKernel::Memory::KernelReserveVirtualRange(&arena, arena_size, 0, 0x200000),
"KernelReserveVirtualRange(arena)"); "KernelReserveVirtualRange(arena)");
const auto actual_arena = reinterpret_cast<uint64_t>(arena); const auto actual_arena = reinterpret_cast<uint64_t>(arena);
Check(test, actual_arena >= arena_base && (actual_arena & (0x200000 - 1u)) == 0, Check(test, actual_arena >= arena_base && (actual_arena & (0x200000 - 1u)) == 0,
"large hinted reserve violated its search start or alignment"); "large hinted reserve violated its search start or alignment");
void* window = reinterpret_cast<void*>(arena_base); void* window = reinterpret_cast<void*>(arena_base);
CheckOk(test, Libs::LibKernel::Memory::KernelReserveVirtualRange( CheckOk(test,
&window, window_size, 0, SceKernelPageSize), Libs::LibKernel::Memory::KernelReserveVirtualRange(&window, window_size, 0,
SceKernelPageSize),
"KernelReserveVirtualRange(window)"); "KernelReserveVirtualRange(window)");
Check(test, reinterpret_cast<uint64_t>(window) >= actual_arena + arena_size, Check(test, reinterpret_cast<uint64_t>(window) >= actual_arena + arena_size,
"second hinted reserve overlaps the large arena"); "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"); Check(test, mapped == window, "fixed direct mapping moved away from the reserved window");
*reinterpret_cast<uint64_t*>(mapped) = 0x4b59545952455356ull; // "KYTYRESV" *reinterpret_cast<uint64_t*>(mapped) = 0x4b59545952455356ull; // "KYTYRESV"
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap( CheckOk(test,
reinterpret_cast<uint64_t>(mapped), SceKernelPageSize * 2), Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast<uint64_t>(mapped),
SceKernelPageSize * 2),
"KernelMunmap(direct)"); "KernelMunmap(direct)");
CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory( CheckOk(test, Libs::LibKernel::Memory::KernelReleaseDirectMemory(phys, SceKernelPageSize * 2),
phys, SceKernelPageSize * 2),
"KernelReleaseDirectMemory"); "KernelReleaseDirectMemory");
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap( CheckOk(test,
reinterpret_cast<uint64_t>(window), window_size), Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast<uint64_t>(window), window_size),
"KernelMunmap(window reserve)"); "KernelMunmap(window reserve)");
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap( CheckOk(test,
reinterpret_cast<uint64_t>(arena), arena_size), Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast<uint64_t>(arena), arena_size),
"KernelMunmap(arena reserve)"); "KernelMunmap(arena reserve)");
std::printf("[host] %-48s ok\n", test); std::printf("[host] %-48s ok\n", test);
@@ -1155,17 +1421,16 @@ void TestMemoryPoolCommitDecommitQueryFlags() {
void TestProgramMemoryRegistrationAndProtection() { void TestProgramMemoryRegistrationAndProtection() {
const char* test = "ProgramMemoryRegistrationAndProtection"; const char* test = "ProgramMemoryRegistrationAndProtection";
const auto size = SceKernelPageSize * 3; 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"); Check(test, base != 0, "program host allocation failed");
Libs::LibKernel::Memory::RegisterProgramMemory(base, size, Libs::LibKernel::Memory::RegisterProgramMemory(
Common::VirtualMemory::Mode::ReadWrite, base, size, Common::VirtualMemory::Mode::ReadWrite, "program_test");
"program_test"); ExpectRange(test, Query(test, base), base, base + size,
ExpectRange(test, Query(test, base), base, base + size, SceKernelProtCpuRead | SceKernelProtCpuRw, SceKernelProtCpuRead | SceKernelProtCpuRw, 0, 0, 0, 1, "program_test");
0, 0, 0, 1, "program_test");
Libs::LibKernel::Memory::UpdateProgramMemoryProtection( Libs::LibKernel::Memory::UpdateProgramMemoryProtection(base, SceKernelPageSize,
base, SceKernelPageSize, Common::VirtualMemory::Mode::Read); Common::VirtualMemory::Mode::Read);
ExpectRange(test, Query(test, base), base, base + SceKernelPageSize, SceKernelProtCpuRead, 0, 0, ExpectRange(test, Query(test, base), base, base + SceKernelPageSize, SceKernelProtCpuRead, 0, 0,
0, 1, "program_test"); 0, 1, "program_test");
@@ -1208,6 +1473,11 @@ int main() {
RunTest(TestDefaultDirectMapUsesSystemAddressRange); RunTest(TestDefaultDirectMapUsesSystemAddressRange);
RunTest(TestLargeDirectMapAliasesAcrossChunks); RunTest(TestLargeDirectMapAliasesAcrossChunks);
RunTest(TestDirectMapUnmapReusesHostAddress); RunTest(TestDirectMapUnmapReusesHostAddress);
RunTest(TestFixedReserveReplacesPartialDirectMapping);
RunTest(TestFixedReserveRollbackConsumesRestoredPlaceholder);
RunTest(TestFixedReserveRollbackSkipsUntouchedChunks);
RunTest(TestFixedReserveRollbackRestoresDecommittedHostPages);
RunTest(TestFixedReserveRangeAddRollbackKeepsPlaceholder);
RunTest(TestLargeHintedReserveHostsSmallDirectMap); RunTest(TestLargeHintedReserveHostsSmallDirectMap);
RunTest(TestMemoryPoolAlignmentContracts); RunTest(TestMemoryPoolAlignmentContracts);
RunTest(TestProsperoSampleMemoryPoolExpandCommit); RunTest(TestProsperoSampleMemoryPoolExpandCommit);