diff --git a/src/graphics/host_gpu/renderer/cache/bufferCache.cpp b/src/graphics/host_gpu/renderer/cache/bufferCache.cpp index 44b5503..ffed036 100644 --- a/src/graphics/host_gpu/renderer/cache/bufferCache.cpp +++ b/src/graphics/host_gpu/renderer/cache/bufferCache.cpp @@ -621,7 +621,8 @@ ImageBufferSource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t siz } auto [staging, stage_offset] = m_staging_buffer.Map(size, 16); - if (staging == nullptr || !Libs::LibKernel::Memory::TryReadBacking(vaddr, staging, size)) { + if (staging == nullptr || (!Libs::LibKernel::Memory::TryReadBacking(vaddr, staging, size) && + !Libs::LibKernel::Memory::TryReadPrtBacking(vaddr, staging, size))) { EXIT("BufferCache: failed to read mapped guest image backing\n"); } m_staging_buffer.Commit(); diff --git a/src/kernel/memory.cpp b/src/kernel/memory.cpp index ce84cd4..c71a9b8 100644 --- a/src/kernel/memory.cpp +++ b/src/kernel/memory.cpp @@ -921,18 +921,39 @@ constexpr uint64_t PRT_APERTURE_END = 0xfc00000000ull; static std::array g_prt_apertures {}; static Common::Mutex g_prt_aperture_mutex; -static bool IsInPrtAperture(uint64_t address) { +static bool IsInPrtAperture(uint64_t address, uint64_t size = 1) { + if (size == 0 || UINT64_MAX - address < size) { + return false; + } Common::LockGuard lock(g_prt_aperture_mutex); for (const auto& aperture: g_prt_apertures) { - if (address >= aperture.address && address < aperture.address + aperture.size) { - return true; + if (address >= aperture.address) { + const auto offset = address - aperture.address; + if (offset < aperture.size && size <= aperture.size - offset) { + return true; + } } } return false; } +bool TryReadPrtBacking(uint64_t vaddr, void* data, uint64_t size) { + std::vector ranges; + if (g_guest_address_space == nullptr || g_virtual_ranges == nullptr || + !IsInPrtAperture(vaddr, size) || !g_virtual_ranges->QuerySpan(vaddr, size, &ranges)) { + return false; + } + if (std::any_of(ranges.begin(), ranges.end(), [](const auto& range) { + return !IsReservedRangeType(range.type) && + !g_guest_address_space->BackingContains(range.start, range.size); + })) { + return false; + } + return g_guest_address_space->TryReadSparseBacking(vaddr, data, size); +} + static bool SelfTestSub64SharedPlaceholderAlias() { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS constexpr uint64_t PageSize = 0x4000; diff --git a/src/kernel/memory.h b/src/kernel/memory.h index 45017ba..21cffef 100644 --- a/src/kernel/memory.h +++ b/src/kernel/memory.h @@ -109,6 +109,7 @@ void RegisterCallbacks(callback_func_t alloc_func, callback_fu void SetFlexibleMemorySize(uint64_t size); bool TryWriteBacking(uint64_t vaddr, const void* data, uint64_t size); bool TryReadBacking(uint64_t vaddr, void* data, uint64_t size); +bool TryReadPrtBacking(uint64_t vaddr, void* data, uint64_t size); [[nodiscard]] uint64_t ClampRangeSize(uint64_t vaddr, uint64_t size); void WriteBacking(uint64_t vaddr, const void* data, uint64_t size) noexcept; void InvalidateMemory(uint64_t vaddr, uint64_t size); diff --git a/src/kernel/memoryAddressSpace.inc b/src/kernel/memoryAddressSpace.inc index 9ea25b0..8b0cb13 100644 --- a/src/kernel/memoryAddressSpace.inc +++ b/src/kernel/memoryAddressSpace.inc @@ -188,6 +188,43 @@ public: }); } + bool TryReadSparseBacking(uint64_t vaddr, void* data, uint64_t size) { + if (!IsAvailable() || data == nullptr || size == 0 || UINT64_MAX - vaddr < size) { + return false; + } + + std::lock_guard lock(m_mutex); + const auto end = vaddr + size; + auto first = m_maps.lower_bound(vaddr); + if (first != m_maps.begin()) { + auto previous = std::prev(first); + if (previous->second.vaddr + previous->second.size > vaddr) { + first = previous; + } + } + for (auto mapping = first; mapping != m_maps.end() && mapping->second.vaddr < end; + ++mapping) { + const auto& entry = mapping->second; + const auto start = std::max(vaddr, entry.vaddr); + const auto bytes = std::min(end, entry.vaddr + entry.size) - start; + if (!RangeOk(entry.backing_offset + start - entry.vaddr, bytes)) { + return false; + } + } + + auto* destination = static_cast(data); + std::memset(destination, 0, size); + for (auto mapping = first; mapping != m_maps.end() && mapping->second.vaddr < end; + ++mapping) { + const auto& entry = mapping->second; + const auto start = std::max(vaddr, entry.vaddr); + const auto bytes = std::min(end, entry.vaddr + entry.size) - start; + const auto offset = entry.backing_offset + start - entry.vaddr; + std::memcpy(destination + start - vaddr, m_backing_base + offset, bytes); + } + return true; + } + bool MapExistingPlaceholderFixed(uint64_t vaddr, uint64_t size, uint64_t backing_offset, VirtualMemory::Mode mode, FailureReason* failure_reason = nullptr) { @@ -694,6 +731,10 @@ public: return m_backing->TryReadBacking(vaddr, data, size); } + bool TryReadSparseBacking(uint64_t vaddr, void* data, uint64_t size) { + return m_backing->TryReadSparseBacking(vaddr, data, size); + } + bool BackingContains(uint64_t vaddr, uint64_t size) { return m_backing->Contains(vaddr, size); } bool Protect(uint64_t vaddr, uint64_t size, VirtualMemory::Mode mode) { diff --git a/tests/VirtualMemoryAllocationTests.cpp b/tests/VirtualMemoryAllocationTests.cpp index 259d762..d624cfc 100644 --- a/tests/VirtualMemoryAllocationTests.cpp +++ b/tests/VirtualMemoryAllocationTests.cpp @@ -354,6 +354,84 @@ void TestGuestAddressSpaceOwnsReservationsBeforeBacking() { std::printf("[host] %-48s ok\n", test); } +void TestPrtBackingReadPreservesSparseResidency() { + const char* test = "PrtBackingReadPreservesSparseResidency"; + constexpr uint64_t commit_size = SceKernelMemoryPoolCommitLen; + constexpr uint64_t aperture_len = commit_size * 3; + int64_t pool_offset = -1; + CheckOk(test, + Libs::LibKernel::Memory::KernelMemoryPoolExpand( + 0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), commit_size * 2, + SceKernelMemoryPoolAlignment, &pool_offset), + "KernelMemoryPoolExpand"); + + void* arena = nullptr; + CheckOk(test, + Libs::LibKernel::Memory::KernelMemoryPoolReserve( + reinterpret_cast(0x1000000000ull), SceKernelMemoryPoolReserveLen, 0, 0, + &arena), + "KernelMemoryPoolReserve"); + const auto base = reinterpret_cast(arena); + CheckOk(test, + Libs::LibKernel::Memory::KernelMemoryPoolCommit( + arena, commit_size, SceKernelMtypeC, SceKernelProtCpuRw, 0), + "KernelMemoryPoolCommit(first)"); + CheckOk(test, + Libs::LibKernel::Memory::KernelMemoryPoolCommit( + reinterpret_cast(base + commit_size * 2), commit_size, SceKernelMtypeC, + SceKernelProtCpuRw, 0), + "KernelMemoryPoolCommit(third)"); + std::memset(reinterpret_cast(base), 0x3c, commit_size); + std::memset(reinterpret_cast(base + commit_size * 2), 0xa7, commit_size); + + CheckOk(test, Libs::LibKernel::Memory::KernelSetPrtAperture(2, arena, aperture_len), + "KernelSetPrtAperture"); + std::vector bytes(aperture_len, 0x5a); + Check(test, !Libs::LibKernel::Memory::TryReadBacking(base, bytes.data(), bytes.size()), + "dense backing read accepted a nonresident span"); + Check(test, std::all_of(bytes.begin(), bytes.end(), [](uint8_t value) { return value == 0x5a; }), + "failed dense backing read modified its destination"); + Check(test, Libs::LibKernel::Memory::TryReadPrtBacking(base, bytes.data(), bytes.size()), + "PRT backing read rejected a valid sparse aperture range"); + Check(test, + std::all_of(bytes.begin(), bytes.begin() + commit_size, + [](uint8_t value) { return value == 0x3c; }) && + std::all_of(bytes.begin() + commit_size, bytes.begin() + commit_size * 2, + [](uint8_t value) { return value == 0; }) && + std::all_of(bytes.begin() + commit_size * 2, bytes.end(), + [](uint8_t value) { return value == 0xa7; }), + "PRT backing read did not copy resident pages and zero nonresident pages"); + Check(test, + !Libs::LibKernel::Memory::TryReadPrtBacking(base + commit_size * 2, bytes.data(), + commit_size * 2), + "PRT backing read crossed the registered aperture"); + + constexpr uint64_t unowned_prt = 0x5000000000ull; + CheckOk(test, + Libs::LibKernel::Memory::KernelSetPrtAperture( + 2, reinterpret_cast(unowned_prt), commit_size), + "KernelSetPrtAperture(unowned)"); + Check(test, + !Libs::LibKernel::Memory::TryReadPrtBacking(unowned_prt, bytes.data(), commit_size), + "PRT backing read accepted an unowned virtual range"); + CheckOk(test, Libs::LibKernel::Memory::KernelSetPrtAperture(2, nullptr, 0), + "KernelSetPrtAperture(clear)"); + + CheckOk(test, Libs::LibKernel::Memory::KernelMemoryPoolDecommit(arena, commit_size, 0), + "KernelMemoryPoolDecommit(first)"); + CheckOk(test, + Libs::LibKernel::Memory::KernelMemoryPoolDecommit( + reinterpret_cast(base + commit_size * 2), commit_size, 0), + "KernelMemoryPoolDecommit(third)"); + CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(base, SceKernelMemoryPoolReserveLen), + "KernelMunmap(pool reserve)"); + CheckOk(test, + Libs::LibKernel::Memory::KernelReleaseDirectMemory(pool_offset, commit_size * 2), + "KernelReleaseDirectMemory(pool expansion)"); + + std::printf("[host] %-48s ok\n", test); +} + void TestGuestAddressSpaceHasNoFixedFallback() { const char* test = "GuestAddressSpaceHasNoFixedFallback"; const auto unowned_address = reinterpret_cast(0x10000); @@ -2440,6 +2518,7 @@ int main(int argc, char** argv) { RunTest(TestWindowsGuestRedZoneStaticPatcher); RunTest(TestProsperoArgumentAndInfoSizeContracts); RunTest(TestGuestAddressSpaceOwnsReservationsBeforeBacking); + RunTest(TestPrtBackingReadPreservesSparseResidency); RunTest(TestGuestAddressSpaceHasNoFixedFallback); RunTest(TestGuestFreeRangeSearchDoesNotUnderflow); RunTest(TestFlexibleMemoryCapacityIsBootFixed);