fix(memory): restore write-watch tracking for shared mappings
This commit is contained in:
parent
abb9c85266
commit
f7c53a4481
|
|
@ -506,7 +506,9 @@ bool describePages(std::uintptr_t address, std::size_t bytes, Emit&& emit) {
|
|||
const auto base = reinterpret_cast<std::uintptr_t>(memory.BaseAddress);
|
||||
if (memory.RegionSize > std::numeric_limits<std::uintptr_t>::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<void*>(cursor), static_cast<std::size_t>(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<std::size_t>(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<void*>(cursor), static_cast<std::size_t>(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<std::size_t>(stop - cursor), tracker.pages.data(), &count, true)) return 0;
|
||||
for (ULONG_PTR i = 0; i < count; ++i) {
|
||||
const auto block = (reinterpret_cast<std::uintptr_t>(tracker.pages[i]) - tracker.base) / WriteBlockBytes;
|
||||
tracker.blocks[block] = tracker.generation;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@
|
|||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
|
||||
|
|
@ -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<void*>(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<std::uintptr_t>(section), offset + done);
|
||||
auto shared = physical[key].lock();
|
||||
if (!shared) {
|
||||
shared = std::make_shared<SharedPage>();
|
||||
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<void*>(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<void*>(at);
|
||||
return true;
|
||||
}
|
||||
for (auto at = cursor; at < stop; at += 4096) pages[(*count)++] = reinterpret_cast<void*>(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<void*>(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<std::uintptr_t>(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<void*>(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<std::uintptr_t> aliases;
|
||||
};
|
||||
struct View {
|
||||
std::shared_ptr<SharedPage> 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<void*>(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<void*>(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<void*>(address), bytes, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS)) fail("coalesce guest placeholders");
|
||||
}
|
||||
|
||||
std::map<std::uintptr_t, View> views;
|
||||
std::map<std::pair<std::uintptr_t, std::uint64_t>, std::weak_ptr<SharedPage>> physical;
|
||||
std::mutex mutex;
|
||||
AllocateFunction allocate = nullptr;
|
||||
MapFunction map = nullptr;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include "prx/libc/include/GuestArena.hpp"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
|
|
@ -235,6 +236,8 @@ bool HandleSse4a(EXCEPTION_POINTERS* info) {
|
|||
|
||||
LONG WINAPI ReportCrash(EXCEPTION_POINTERS* info) {
|
||||
static std::atomic<bool> 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;
|
||||
|
|
|
|||
|
|
@ -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<std::uintptr_t>(addr), len, WinProtFromPosix(prot));
|
||||
if (prot != PROT_NONE && KernelArena::Get().Contains(addr, len)) CommitArenaRange(addr, len, WinProtFromPosix(prot));
|
||||
auto cursor = reinterpret_cast<std::uintptr_t>(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<PhysicalBacking> backing;
|
||||
};
|
||||
|
||||
std::map<std::uintptr_t, DirectMapping> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue