diff --git a/core/libs/prx/libc/include/WindowsMappings.hpp b/core/libs/prx/libc/include/WindowsMappings.hpp index 9d33b9b0b..acb82476a 100644 --- a/core/libs/prx/libc/include/WindowsMappings.hpp +++ b/core/libs/prx/libc/include/WindowsMappings.hpp @@ -82,6 +82,9 @@ public: split(cursor + done, pageBytes); void* page = reinterpret_cast(cursor + done); if (!map(section, GetCurrentProcess(), page, offset + done, pageBytes, MEM_REPLACE_PLACEHOLDER, PAGE_EXECUTE_READWRITE, nullptr, 0)) fail("map shared guest page"); + MEMORY_BASIC_INFORMATION memory{}; + if (VirtualQuery(page, &memory, sizeof(memory)) != sizeof(memory)) fail("query shared guest page"); + if (memory.State == MEM_RESERVE && !VirtualAlloc(page, pageBytes, MEM_COMMIT, PAGE_EXECUTE_READWRITE)) fail("commit shared guest page"); DWORD previous; if (!VirtualProtect(page, pageBytes, protection, &previous)) fail("protect shared guest page"); const auto key = std::make_pair(reinterpret_cast(section), offset + done); diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 9eeb24861..8f314928a 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -270,7 +270,7 @@ public: #ifdef _WIN32 static_cast(start); const auto size = static_cast(bytes); - section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE, static_cast(size >> 32), static_cast(size), nullptr); + section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE | SEC_RESERVE, static_cast(size >> 32), static_cast(size), nullptr); if (!section) { const auto error = static_cast(GetLastError()); char message[96]; diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index 74beb7554..574fa0106 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -289,6 +289,34 @@ static void CheckDirectMemoryFollowsPhysicalPages() { Require(sceKernelReleaseDirectMemory(again, page * 2) == 0); } +static void CheckSparseDirectMemoryBacking() { + constexpr std::size_t page = 0x4000; + constexpr std::size_t size = 256u * 1024u * 1024u; + std::int64_t physical = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, size, 0, 0, &physical) == 0); + void* first = nullptr; + void* last = nullptr; + Require(sceKernelMapDirectMemory(&first, page, 3, 0, physical, 0) == 0); + Require(sceKernelMapDirectMemory(&last, page, 3, 0, physical + size - page, 0) == 0); + auto* firstBytes = static_cast(first); + auto* lastBytes = static_cast(last); + Require(firstBytes[0] == 0 && firstBytes[page - 1] == 0 && lastBytes[0] == 0 && lastBytes[page - 1] == 0); + firstBytes[0] = 17; + lastBytes[page - 1] = 29; + void* alias = nullptr; + Require(sceKernelMapDirectMemory(&alias, page, 3, 0, physical + size - page, 0) == 0); + auto* aliasBytes = static_cast(alias); + Require(aliasBytes[page - 1] == 29 && aliasBytes[0] == 0); + aliasBytes[page - 1] = 41; + Require(lastBytes[page - 1] == 41 && firstBytes[0] == 17); + Require(sceKernelMunmap(last, page) == 0 && sceKernelMunmap(alias, page) == 0); + last = nullptr; + Require(sceKernelMapDirectMemory(&last, page, 3, 0, physical + size - page, 0) == 0); + Require(static_cast(last)[page - 1] == 41); + Require(sceKernelMunmap(first, page) == 0 && sceKernelMunmap(last, page) == 0); + Require(sceKernelReleaseDirectMemory(physical, size) == 0); +} + static void CheckReleaseDirectMemoryClearsMappings() { constexpr std::size_t page = 0x4000; std::int64_t phys = 0; @@ -1715,6 +1743,7 @@ int main() { CheckCheckedReleaseDirectMemory(); CheckAudioCoprocessorProtection(); CheckDirectMemoryFollowsPhysicalPages(); + CheckSparseDirectMemoryBacking(); CheckReleaseDirectMemoryClearsMappings(); CheckReleaseDirectMemoryRejectsInvalidRanges(); CheckFixedMappingReplacesPartialOverlap(); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 51f08cc9e..81ebe8628 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -9,6 +9,10 @@ - libwinpthread-1.dll - winpthreads initializes a statically initialized rwlock on its first lock and fails a concurrent first lock with `EINVAL`, which `std::shared_mutex` and `std::shared_timed_mutex` ignore, so that lock does not exclude. Guest rwlocks ([Pthread.hpp](../../core/libs/prx/libkernel/Pthread/include/Pthread.hpp)) are locked once when created. The host `std::shared_mutex` objects in [Recompiler.cpp](../../core/shader/recompiler/Recompiler.cpp) and [GuestWriteWatch.cpp](../../core/libs/prx/libc/src/GuestWriteWatch.cpp) are not, and can still race on their first contended use. +### Direct memory + +- Windows direct-memory backing reserves a paging-file section and commits each mapped guest page before setting its protection. Committed physical contents remain in the section across unmap/remap and are released when the backing and its remaining views are destroyed; commitment is not recovered merely by unmapping a page. Linux retains its existing backing. Peak gameplay memory and Windows versions other than the tested host remain unverified. + ### Silent stubs Throughout the project, every function at every stage either **does exactly what it's supposed to or throws an exception**. Everywhere... except: