fix(libSceFiber): pin fiber contexts writable under write tracking on Windows
Write tracking protects shared guest pages read-only. A push on a fiber stack in such a page faults, and Windows cannot dispatch it because the exception frame lands on the same protected stack, so the process was terminated without a report. Pin fiber contexts writable for their lifetime; pinned pages are always collected as written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
219a1cf8d2
commit
57588cf2dd
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#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<void**>(teb + 0x1478) = bounds.deallocation;
|
||||
}
|
||||
|
||||
static void PinStack(const void* context, std::uint64_t bytes) {
|
||||
GuestArena::GuestArenaPinWritable_nid_postfix(context, static_cast<std::size_t>(bytes));
|
||||
}
|
||||
|
||||
static void UnpinStack(const void* context, std::uint64_t bytes) {
|
||||
GuestArena::GuestArenaUnpinWritable_nid_postfix(context, static_cast<std::size_t>(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<std::uint64_t*>(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<void*>(object), addr_context, static_cast<unsigned long long>(size_context), reinterpret_cast<void*>(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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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<void*>(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<void*>(at);
|
||||
|
|
@ -215,7 +241,7 @@ public:
|
|||
}
|
||||
for (auto at = cursor; at < stop; at += 4096) pages[(*count)++] = reinterpret_cast<void*>(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<std::uintptr_t> aliases;
|
||||
std::uint32_t pins = 0;
|
||||
};
|
||||
struct Section {
|
||||
HANDLE handle;
|
||||
|
|
|
|||
|
|
@ -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<std::uintptr_t>(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<std::uintptr_t>(pointer), bytes);
|
||||
}
|
||||
|
||||
bool GuestArenaProtection_nid_postfix(std::uintptr_t address, std::uint32_t* protection) {
|
||||
return WindowsMappings::Get().Protection(address, protection);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue