diff --git a/core/libs/prx/libSceFiber/Export.cpp b/core/libs/prx/libSceFiber/Export.cpp index 1c6ced8b7f9..83572e7d116 100644 --- a/core/libs/prx/libSceFiber/Export.cpp +++ b/core/libs/prx/libSceFiber/Export.cpp @@ -14,6 +14,7 @@ #ifdef _WIN32 #include +#include "prx/libc/include/GuestArena.hpp" #endif static constexpr int32_t SCE_OK = 0; @@ -186,6 +187,14 @@ static void SetBounds(const StackBounds& bounds) { *reinterpret_cast(teb + 0x1478) = bounds.deallocation; } +static void PinStack(const void* context, std::uint64_t bytes) { + GuestArena::GuestArenaPinWritable_nid_postfix(context, static_cast(bytes)); +} + +static void UnpinStack(const void* context, std::uint64_t bytes) { + GuestArena::GuestArenaUnpinWritable_nid_postfix(context, static_cast(bytes)); +} + #else extern "C" void Aps5FiberSwitchStack_nid_no_patch(void** save, void* load); @@ -244,6 +253,10 @@ static StackBounds CurrentBounds() { static void SetBounds(const StackBounds&) {} +static void PinStack(const void*, std::uint64_t) {} + +static void UnpinStack(const void*, std::uint64_t) {} + #endif static StackBounds FiberBounds(const Fiber* fiber) { @@ -317,6 +330,7 @@ int32_t APS5_VABI _sceFiberInitializeImpl_nid_postfix(FiberObject* object, const if (addr_context == nullptr) return SCE_FIBER_ERROR_INVALID; if (size_context < FIBER_MIN_CONTEXT_SIZE) return SCE_FIBER_ERROR_RANGE; auto* fiber = reinterpret_cast(object); + if (fiber->magic == FIBER_MAGIC) UnpinStack(fiber->context, fiber->contextSize); std::memset(object, 0, FIBER_OBJECT_SIZE); fiber->magic = FIBER_MAGIC; fiber->state.store(FiberState::Idle, std::memory_order_relaxed); @@ -330,6 +344,7 @@ int32_t APS5_VABI _sceFiberInitializeImpl_nid_postfix(FiberObject* object, const auto* words = static_cast(addr_context); std::fill(words, words + size_context / sizeof(std::uint64_t), FIBER_CONTEXT_FILL); } + PinStack(addr_context, size_context); if (TraceFibers()) std::fprintf(stderr, "[fiber] init %s object=%p context=%p+0x%llx entry=%p\n", fiber->name, static_cast(object), addr_context, static_cast(size_context), reinterpret_cast(entry)); return SCE_OK; } @@ -340,6 +355,7 @@ int32_t APS5_VABI sceFiberFinalize(FiberObject* object) { const auto state = fiber->state.load(std::memory_order_acquire); if (state == FiberState::Running || state == FiberState::Suspending) return SCE_FIBER_ERROR_STATE; fiber->magic = 0; + UnpinStack(fiber->context, fiber->contextSize); return SCE_OK; } diff --git a/core/libs/prx/libc/include/GuestArena.hpp b/core/libs/prx/libc/include/GuestArena.hpp index b929c9d3eec..1367e27e5bc 100644 --- a/core/libs/prx/libc/include/GuestArena.hpp +++ b/core/libs/prx/libc/include/GuestArena.hpp @@ -22,6 +22,8 @@ bool GuestArenaWriteWatched_nid_postfix(); #ifdef _WIN32 void GuestArenaSetProtection_nid_postfix(std::uintptr_t address, std::size_t bytes, std::uint32_t protection); bool GuestArenaHandleWrite_nid_postfix(std::uintptr_t address); +void GuestArenaPinWritable_nid_postfix(const void* pointer, std::size_t bytes); +void GuestArenaUnpinWritable_nid_postfix(const void* pointer, std::size_t bytes); bool GuestArenaProtection_nid_postfix(std::uintptr_t address, std::uint32_t* protection); bool GuestArenaCollectWrites_nid_postfix(std::uintptr_t address, std::size_t bytes, void** pages, std::size_t* count, bool clear); bool GuestArenaHostRegionOverlaps_nid_postfix(std::uintptr_t address, std::size_t bytes); diff --git a/core/libs/prx/libc/include/WindowsMappings.hpp b/core/libs/prx/libc/include/WindowsMappings.hpp index 6fb6018c7ed..200e8e3f82f 100644 --- a/core/libs/prx/libc/include/WindowsMappings.hpp +++ b/core/libs/prx/libc/include/WindowsMappings.hpp @@ -102,6 +102,31 @@ class WindowsMappings { } } + void Pin(std::uintptr_t address, std::size_t bytes) { + std::lock_guard lock(mutex); + for (auto it = views.lower_bound(address & ~(pageBytes - 1)); it != views.end() && it->first < address + bytes; ++it) { + auto& page = *it->second.page; + ++page.pins; + for (const auto alias : page.aliases) { + auto& view = views.at(alias); + if (!view.armed) continue; + DWORD previous; + if (!VirtualProtect(reinterpret_cast(alias), pageBytes, view.protection, &previous)) fail("pin shared guest page writable"); + view.armed = false; + } + invalidate(page); + } + } + + void Unpin(std::uintptr_t address, std::size_t bytes) { + std::lock_guard lock(mutex); + for (auto it = views.lower_bound(address & ~(pageBytes - 1)); it != views.end() && it->first < address + bytes; ++it) { + auto& page = *it->second.page; + if (page.pins != 0) --page.pins; + invalidate(page); + } + } + bool HandleWrite(std::uintptr_t address) { std::lock_guard lock(mutex); const auto base = address & ~(pageBytes - 1); @@ -207,7 +232,8 @@ class WindowsMappings { auto& view = found->second; const auto stop = std::min(end, base + pageBytes); if (view.protection == PAGE_NOACCESS) return false; - if (view.seen != view.page->generation) { + const bool pinned = view.page->pins != 0; + if (pinned || view.seen != view.page->generation) { const auto needed = (stop - cursor + 4095) / 4096; if (needed > capacity - *count) { for (auto at = cursor; *count < capacity; at += 4096) pages[(*count)++] = reinterpret_cast(at); @@ -215,7 +241,7 @@ class WindowsMappings { } for (auto at = cursor; at < stop; at += 4096) pages[(*count)++] = reinterpret_cast(at); } - if (clear) { + if (clear && !pinned) { for (const auto alias : view.page->aliases) { auto& other = views.at(alias); if (!writable(other.protection) || other.armed || other.hostWrites != 0) continue; @@ -249,6 +275,7 @@ class WindowsMappings { struct SharedPage { std::uint64_t generation = 1; std::vector aliases; + std::uint32_t pins = 0; }; struct Section { HANDLE handle; diff --git a/core/libs/prx/libc/src/GuestArena.cpp b/core/libs/prx/libc/src/GuestArena.cpp index 92f4877dcde..528bf354236 100644 --- a/core/libs/prx/libc/src/GuestArena.cpp +++ b/core/libs/prx/libc/src/GuestArena.cpp @@ -188,6 +188,16 @@ bool GuestArenaHandleWrite_nid_postfix(std::uintptr_t address) { return WindowsMappings::Get().HandleWrite(address); } +void GuestArenaPinWritable_nid_postfix(const void* pointer, std::size_t bytes) { + if (!Arena::Get().Contains(pointer, bytes)) return; + WindowsMappings::Get().Pin(reinterpret_cast(pointer), bytes); +} + +void GuestArenaUnpinWritable_nid_postfix(const void* pointer, std::size_t bytes) { + if (!Arena::Get().Contains(pointer, bytes)) return; + WindowsMappings::Get().Unpin(reinterpret_cast(pointer), bytes); +} + bool GuestArenaProtection_nid_postfix(std::uintptr_t address, std::uint32_t* protection) { return WindowsMappings::Get().Protection(address, protection); } diff --git a/core/libs/tests/GuestMemory.cpp b/core/libs/tests/GuestMemory.cpp index 2fe9b8b06d3..8dee5f167d6 100644 --- a/core/libs/tests/GuestMemory.cpp +++ b/core/libs/tests/GuestMemory.cpp @@ -672,6 +672,49 @@ static void CheckSharedWriteTracking() { #endif } +static void CheckPinnedSharedPages() { +#ifdef _WIN32 + constexpr std::size_t page = 0x4000; + std::int64_t phys = 0; + Require(sceKernelAllocateDirectMemory(0, 0x7fffffffffll, page * 3, 0, 0, &phys) == 0); + void* mapped = nullptr; + Require(sceKernelMapDirectMemory(&mapped, page * 3, 3, 0, phys, 0) == 0); + auto* bytes = static_cast(mapped); + const auto collect = [&] { + std::array pages{}; + std::size_t count = pages.size(); + Require(GuestArena::GuestArenaCollectWrites_nid_postfix(reinterpret_cast(mapped), page * 3, pages.data(), &count, true)); + return count; + }; + const auto protection = [&](std::size_t offset) { + MEMORY_BASIC_INFORMATION info{}; + Require(VirtualQuery(const_cast(bytes + offset), &info, sizeof(info)) == sizeof(info)); + return info.Protect; + }; + collect(); + Require(collect() == 0); + Require(protection(0) == PAGE_READONLY && protection(page) == PAGE_READONLY); + GuestArena::GuestArenaPinWritable_nid_postfix(const_cast(bytes + page), page); + Require(protection(page) == PAGE_READWRITE && protection(0) == PAGE_READONLY && protection(page * 2) == PAGE_READONLY); + Require(collect() == 4); + Require(collect() == 4); + Require(protection(page) == PAGE_READWRITE); + bytes[page + 8] = 7; + bytes[0] = 9; + Require(collect() == 8); + Require(collect() == 4); + Require(protection(0) == PAGE_READONLY && protection(page) == PAGE_READWRITE); + GuestArena::GuestArenaUnpinWritable_nid_postfix(const_cast(bytes + page), page); + collect(); + Require(collect() == 0); + Require(protection(page) == PAGE_READONLY); + bytes[page + 8] = 11; + Require(collect() == 4); + Require(sceKernelMunmap(mapped, page * 3) == 0); + Require(sceKernelReleaseDirectMemory(phys, page * 3) == 0); +#endif +} + static void CheckReadsIntoSharedWriteTracking() { #ifdef _WIN32 constexpr std::size_t page = 0x4000; @@ -937,6 +980,7 @@ int main() { #endif CheckSharedWriteTracking(); CheckReadsIntoSharedWriteTracking(); + CheckPinnedSharedPages(); #if defined(__linux__) CheckWriteWatch(); CheckDirectMemoryWriteWatch();