diff --git a/core/libs/prx/libSceFiber/Export.cpp b/core/libs/prx/libSceFiber/Export.cpp index 1c6ced8b7..50c808a96 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) { @@ -330,6 +343,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 +354,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 b929c9d3e..1367e27e5 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 6fb6018c7..200e8e3f8 100644 --- a/core/libs/prx/libc/include/WindowsMappings.hpp +++ b/core/libs/prx/libc/include/WindowsMappings.hpp @@ -102,6 +102,31 @@ public: } } + 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 @@ public: 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 @@ public: } 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 @@ private: 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 92f4877dc..528bf3542 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); }