Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions core/libs/prx/libSceFiber/Export.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#ifdef _WIN32
#include <windows.h>
#include "prx/libc/include/GuestArena.hpp"
#endif

static constexpr int32_t SCE_OK = 0;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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<Fiber*>(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);
Expand All @@ -330,6 +344,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;
}
Expand All @@ -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;
}

Expand Down
2 changes: 2 additions & 0 deletions core/libs/prx/libc/include/GuestArena.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
31 changes: 29 additions & 2 deletions core/libs/prx/libc/include/WindowsMappings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<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);
Expand Down Expand Up @@ -207,15 +232,16 @@ 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<void*>(at);
return true;
}
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;
Expand Down Expand Up @@ -249,6 +275,7 @@ class WindowsMappings {
struct SharedPage {
std::uint64_t generation = 1;
std::vector<std::uintptr_t> aliases;
std::uint32_t pins = 0;
};
struct Section {
HANDLE handle;
Expand Down
10 changes: 10 additions & 0 deletions core/libs/prx/libc/src/GuestArena.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
44 changes: 44 additions & 0 deletions core/libs/tests/GuestMemory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<volatile unsigned char*>(mapped);
const auto collect = [&] {
std::array<void*, 32> pages{};
std::size_t count = pages.size();
Require(GuestArena::GuestArenaCollectWrites_nid_postfix(reinterpret_cast<std::uintptr_t>(mapped), page * 3, pages.data(), &count, true));
return count;
};
const auto protection = [&](std::size_t offset) {
MEMORY_BASIC_INFORMATION info{};
Require(VirtualQuery(const_cast<unsigned char*>(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<unsigned char*>(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<unsigned char*>(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;
Expand Down Expand Up @@ -937,6 +980,7 @@ int main() {
#endif
CheckSharedWriteTracking();
CheckReadsIntoSharedWriteTracking();
CheckPinnedSharedPages();
#if defined(__linux__)
CheckWriteWatch();
CheckDirectMemoryWriteWatch();
Expand Down
Loading