diff --git a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp index c17e16f00..354e53311 100644 --- a/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp @@ -506,7 +506,9 @@ bool describePages(std::uintptr_t address, std::size_t bytes, Emit&& emit) { const auto base = reinterpret_cast(memory.BaseAddress); if (memory.RegionSize > std::numeric_limits::max() - base || base + memory.RegionSize <= cursor) return false; const auto regionEnd = base + memory.RegionSize; - const auto protection = memory.Protect & 0xffu; + std::uint32_t logicalProtection = memory.Protect; + GuestArena::GuestArenaProtection_nid_postfix(cursor, &logicalProtection); + const auto protection = logicalProtection & 0xffu; const bool committed = memory.State == MEM_COMMIT && (memory.Protect & (PAGE_GUARD | PAGE_NOACCESS)) == 0; const bool readable = committed && readableProtection(protection); const bool writable = readable && writableProtection(protection); @@ -762,9 +764,9 @@ std::uint64_t collectWrites(std::uint64_t address, std::size_t bytes, bool memoi static const bool singlePass = std::getenv("APS5_NO_SINGLE_PASS_COLLECT") == nullptr; bool dirty = false; while (cursor < stop) { - ULONG_PTR count = tracker.pages.size(); - DWORD granularity = 0; - if (GetWriteWatch(singlePass ? WRITE_WATCH_FLAG_RESET : 0, reinterpret_cast(cursor), static_cast(stop - cursor), tracker.pages.data(), &count, &granularity) != 0) { + std::size_t count = tracker.pages.size(); + DWORD granularity = 4096; + if (!GuestArena::GuestArenaCollectWrites_nid_postfix(cursor, static_cast(stop - cursor), tracker.pages.data(), &count, singlePass)) { // Uncommitted pages inside the range make the call fail; such ranges are compared instead. return 0; } @@ -782,9 +784,9 @@ std::uint64_t collectWrites(std::uint64_t address, std::size_t bytes, bool memoi // The resetting pass reports pages again, so a write racing the first pass is stamped too. cursor = address & ~(page - 1); while (cursor < stop) { - ULONG_PTR count = tracker.pages.size(); - DWORD granularity = 0; - if (GetWriteWatch(WRITE_WATCH_FLAG_RESET, reinterpret_cast(cursor), static_cast(stop - cursor), tracker.pages.data(), &count, &granularity) != 0) return 0; + std::size_t count = tracker.pages.size(); + DWORD granularity = 4096; + if (!GuestArena::GuestArenaCollectWrites_nid_postfix(cursor, static_cast(stop - cursor), tracker.pages.data(), &count, true)) return 0; for (ULONG_PTR i = 0; i < count; ++i) { const auto block = (reinterpret_cast(tracker.pages[i]) - tracker.base) / WriteBlockBytes; tracker.blocks[block] = tracker.generation; diff --git a/core/libs/prx/libc/include/GuestArena.hpp b/core/libs/prx/libc/include/GuestArena.hpp index c7225a2f6..894dd0012 100644 --- a/core/libs/prx/libc/include/GuestArena.hpp +++ b/core/libs/prx/libc/include/GuestArena.hpp @@ -19,6 +19,10 @@ void GuestArenaRelease_nid_postfix(const void* pointer, std::size_t bytes); void GuestArenaRange_nid_postfix(std::uintptr_t* base, std::size_t* bytes); 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); +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); void GuestArenaCommit_nid_postfix(void* pointer, std::size_t bytes, std::uint32_t protection, std::size_t granule); void GuestArenaReset_nid_postfix(void* pointer, std::size_t bytes); void GuestArenaMap_nid_postfix(void* pointer, std::size_t bytes, void* section, std::uint64_t offset, std::uint32_t protection); diff --git a/core/libs/prx/libc/include/WindowsMappings.hpp b/core/libs/prx/libc/include/WindowsMappings.hpp index 8feb591cb..aff457adc 100644 --- a/core/libs/prx/libc/include/WindowsMappings.hpp +++ b/core/libs/prx/libc/include/WindowsMappings.hpp @@ -7,6 +7,9 @@ #include #include #include +#include +#include +#include #include #include @@ -44,6 +47,11 @@ public: cursor += size; } else { if (memory.State != MEM_COMMIT) throw std::runtime_error("guest memory is not committed"); + const auto mapped = views.find(cursor & ~(pageBytes - 1)); + if (mapped != views.end()) { + mapped->second.protection = protection; + mapped->second.armed = false; + } DWORD previous; if (!VirtualProtect(reinterpret_cast(cursor), stop - cursor, protection, &previous)) fail("protect guest memory"); cursor = stop; @@ -66,11 +74,110 @@ public: if (!map(section, GetCurrentProcess(), page, offset + done, pageBytes, MEM_REPLACE_PLACEHOLDER, PAGE_EXECUTE_READWRITE, nullptr, 0)) fail("map 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); + auto shared = physical[key].lock(); + if (!shared) { + shared = std::make_shared(); + physical[key] = shared; + } + const auto base = cursor + done; + shared->aliases.push_back(base); + views.emplace(base, View{shared, protection, 0, false}); } } + void SetProtection(std::uintptr_t address, std::size_t bytes, DWORD protection) { + std::lock_guard lock(mutex); + for (auto it = views.lower_bound(address); it != views.end() && it->first < address + bytes; ++it) { + it->second.protection = protection; + it->second.armed = false; + } + } + + bool HandleWrite(std::uintptr_t address) { + std::lock_guard lock(mutex); + const auto base = address & ~(pageBytes - 1); + const auto found = views.find(base); + if (found == views.end() || !writable(found->second.protection)) return false; + auto& view = found->second; + ++view.page->generation; + DWORD previous; + if (!VirtualProtect(reinterpret_cast(base), pageBytes, view.protection, &previous)) fail("resume shared memory write"); + view.armed = false; + return true; + } + + bool Protection(std::uintptr_t address, std::uint32_t* protection) { + std::lock_guard lock(mutex); + const auto found = views.find(address & ~(pageBytes - 1)); + if (found == views.end()) return false; + *protection = found->second.protection; + return true; + } + + bool Collect(std::uintptr_t address, std::size_t bytes, void** pages, std::size_t* count, bool clear) { + std::lock_guard lock(mutex); + const auto capacity = *count; + *count = 0; + const auto end = address + bytes; + for (auto cursor = address; cursor < end;) { + const auto base = cursor & ~(pageBytes - 1); + const auto found = views.find(base); + if (found != views.end()) { + 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 auto needed = (stop - cursor + 4095) / 4096; + if (needed > capacity - *count) { + for (auto at = cursor; *count < capacity; at += 4096) pages[(*count)++] = reinterpret_cast(at); + return true; + } + for (auto at = cursor; at < stop; at += 4096) pages[(*count)++] = reinterpret_cast(at); + } + if (clear) { + for (const auto alias : view.page->aliases) { + auto& other = views.at(alias); + if (!writable(other.protection) || other.armed) continue; + DWORD previous; + const DWORD protection = other.protection == PAGE_EXECUTE_READWRITE ? PAGE_EXECUTE_READ : PAGE_READONLY; + if (!VirtualProtect(reinterpret_cast(alias), pageBytes, protection, &previous)) fail("arm shared memory write tracking"); + other.armed = true; + } + view.seen = view.page->generation; + } + cursor = stop; + } else { + const auto memory = query(cursor); + if (memory.State != MEM_COMMIT || memory.Type != MEM_PRIVATE) return false; + const auto stop = std::min(end, reinterpret_cast(memory.BaseAddress) + memory.RegionSize); + ULONG_PTR available = capacity - *count; + if (available == 0) return true; + DWORD granularity = 0; + if (GetWriteWatch(clear ? WRITE_WATCH_FLAG_RESET : 0, reinterpret_cast(cursor), stop - cursor, pages + *count, &available, &granularity) != 0) fail("collect private guest writes"); + *count += available; + if (*count == capacity) return true; + cursor = stop; + } + } + return true; + } + private: static constexpr std::size_t pageBytes = 0x4000; + struct SharedPage { + std::uint64_t generation = 1; + std::vector aliases; + }; + struct View { + std::shared_ptr page; + DWORD protection; + std::uint64_t seen; + bool armed; + }; + static bool writable(DWORD protection) { + return protection == PAGE_READWRITE || protection == PAGE_EXECUTE_READWRITE; + } using AllocateFunction = PVOID (WINAPI*)(HANDLE, PVOID, SIZE_T, ULONG, ULONG, MEM_EXTENDED_PARAMETER*, ULONG); using MapFunction = PVOID (WINAPI*)(HANDLE, HANDLE, PVOID, ULONG64, SIZE_T, ULONG, ULONG, MEM_EXTENDED_PARAMETER*, ULONG); using UnmapFunction = BOOL (WINAPI*)(HANDLE, PVOID, ULONG); @@ -125,6 +232,11 @@ private: if (allocationEnd > end || query(allocationEnd).AllocationBase == memory.AllocationBase) throw std::runtime_error("guest release truncates a host allocation"); if (memory.Type == MEM_MAPPED) { if (!unmap(GetCurrentProcess(), reinterpret_cast(cursor), MEM_PRESERVE_PLACEHOLDER)) fail("unmap shared guest page"); + const auto found = views.find(cursor); + if (found != views.end()) { + std::erase(found->second.page->aliases, cursor); + views.erase(found); + } } else if (memory.Type == MEM_PRIVATE) { if (!VirtualFree(reinterpret_cast(cursor), allocationEnd - cursor, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) fail("release private guest memory"); } else { @@ -140,6 +252,8 @@ private: if (query(address).RegionSize != bytes && !VirtualFree(reinterpret_cast(address), bytes, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS)) fail("coalesce guest placeholders"); } + std::map views; + std::map, std::weak_ptr> physical; std::mutex mutex; AllocateFunction allocate = nullptr; MapFunction map = nullptr; diff --git a/core/libs/prx/libc/src/GuestArena.cpp b/core/libs/prx/libc/src/GuestArena.cpp index 6e725b2ac..68892f438 100644 --- a/core/libs/prx/libc/src/GuestArena.cpp +++ b/core/libs/prx/libc/src/GuestArena.cpp @@ -147,6 +147,22 @@ void GuestArenaRange_nid_postfix(std::uintptr_t* base, std::size_t* bytes) { } #ifdef _WIN32 +void GuestArenaSetProtection_nid_postfix(std::uintptr_t address, std::size_t bytes, std::uint32_t protection) { + WindowsMappings::Get().SetProtection(address, bytes, protection); +} + +bool GuestArenaHandleWrite_nid_postfix(std::uintptr_t address) { + return WindowsMappings::Get().HandleWrite(address); +} + +bool GuestArenaProtection_nid_postfix(std::uintptr_t address, std::uint32_t* protection) { + return WindowsMappings::Get().Protection(address, protection); +} + +bool GuestArenaCollectWrites_nid_postfix(std::uintptr_t address, std::size_t bytes, void** pages, std::size_t* count, bool clear) { + return WindowsMappings::Get().Collect(address, bytes, pages, count, clear); +} + void GuestArenaCommit_nid_postfix(void* pointer, std::size_t bytes, std::uint32_t protection, std::size_t granule) { if (!Arena::Get().Contains(pointer, bytes)) throw std::invalid_argument("commit outside the guest arena"); WindowsMappings::Get().Commit(pointer, bytes, protection, granule, Arena::Get().WriteWatched()); diff --git a/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp b/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp index 5359fcd33..a4f388e1e 100644 --- a/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp +++ b/core/libs/prx/libc/src/specifics/windows/CrashReport.cpp @@ -1,5 +1,6 @@ #ifdef _WIN32 #include +#include "prx/libc/include/GuestArena.hpp" #include #include #include @@ -235,6 +236,8 @@ bool HandleSse4a(EXCEPTION_POINTERS* info) { LONG WINAPI ReportCrash(EXCEPTION_POINTERS* info) { static std::atomic reported{false}; + const auto* fault = info->ExceptionRecord; + if (fault->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && fault->NumberParameters >= 2 && fault->ExceptionInformation[0] == 1 && GuestArena::GuestArenaHandleWrite_nid_postfix(fault->ExceptionInformation[1])) return EXCEPTION_CONTINUE_EXECUTION; if (HandleWatch(info)) return EXCEPTION_CONTINUE_EXECUTION; if (HandleSse4a(info)) return EXCEPTION_CONTINUE_EXECUTION; const auto* record = info->ExceptionRecord; diff --git a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp index 805078db4..a1440a0f5 100644 --- a/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp +++ b/core/libs/prx/libkernel/DirectMemory/DirectMemory.cpp @@ -112,6 +112,7 @@ static int munmap_release(void* addr) { } static int mprotect(void* addr, size_t len, int prot) { + GuestArena::GuestArenaSetProtection_nid_postfix(reinterpret_cast(addr), len, WinProtFromPosix(prot)); if (prot != PROT_NONE && KernelArena::Get().Contains(addr, len)) CommitArenaRange(addr, len, WinProtFromPosix(prot)); auto cursor = reinterpret_cast(addr); const auto end = cursor + len; @@ -278,6 +279,7 @@ struct DirectMapping { std::uintptr_t end; std::uint64_t phys; int memoryType; + std::shared_ptr backing; }; std::map g_directMappings; @@ -289,8 +291,8 @@ void EraseMappings(std::uintptr_t start, std::uintptr_t end) { const auto base = it->first; const auto mapping = it->second; it = g_directMappings.erase(it); - if (base < start) g_directMappings.emplace(base, DirectMapping{start, mapping.phys, mapping.memoryType}); - if (mapping.end > end) it = g_directMappings.emplace(end, DirectMapping{mapping.end, mapping.phys + end - base, mapping.memoryType}).first; + if (base < start) g_directMappings.emplace(base, DirectMapping{start, mapping.phys, mapping.memoryType, mapping.backing}); + if (mapping.end > end) it = g_directMappings.emplace(end, DirectMapping{mapping.end, mapping.phys + end - base, mapping.memoryType, mapping.backing}).first; } } @@ -317,7 +319,7 @@ void AddMapping(std::uintptr_t address, std::size_t len, std::uint64_t phys, int bytes += PS5_PAGE_SIZE; } page.backing->Map(address + offset, bytes, page.offset, nativeProt); - g_directMappings.emplace(address + offset, DirectMapping{address + offset + bytes, phys + offset, page.backing->MemoryType()}); + g_directMappings.emplace(address + offset, DirectMapping{address + offset + bytes, phys + offset, page.backing->MemoryType(), page.backing}); offset += bytes; } }