fix(agc): import shared guest memory through a read-write alias and never offer read-only pages
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ac70928d86
commit
8b8b710b07
|
|
@ -26,6 +26,7 @@ struct HostImport {
|
|||
VkBuffer buffer;
|
||||
VkDeviceMemory memory;
|
||||
VkDeviceAddress address;
|
||||
void* alias = nullptr;
|
||||
// Identity for the life of this import (see HostImportSerial); 0 until first asked for.
|
||||
std::uint64_t serial = 0;
|
||||
bool unwatched = false;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
|
||||
#include "prx/libSceAgcDriver/Execution/include/CaptureTrace.hpp"
|
||||
#include "prx/libc/include/CpuTopology.hpp"
|
||||
#include "prx/libc/include/GuestArena.hpp"
|
||||
#include "prx/libc/include/GuestWriteWatch.hpp"
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
|
|
@ -133,6 +134,9 @@ HostImports& Imports() {
|
|||
void destroyImport(const Context& context, const HostImport& entry) {
|
||||
context.Function<PFN_vkDestroyBuffer>("vkDestroyBuffer")(context.device, entry.buffer, nullptr);
|
||||
context.Function<PFN_vkFreeMemory>("vkFreeMemory")(context.device, entry.memory, nullptr);
|
||||
#ifdef _WIN32
|
||||
GuestArena::GuestArenaUnmapAlias_nid_postfix(entry.alias);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Frees a dropped import's Vulkan objects when it is released. Never copied: a copy would destroy the
|
||||
|
|
@ -163,8 +167,8 @@ void retireImport(const Context& context, HostImports& state, std::map<std::uint
|
|||
}
|
||||
|
||||
const char* createImport(const Context& context, HostImport& entry, VkResult& failure) {
|
||||
const auto base = entry.base;
|
||||
const auto bytes = entry.bytes;
|
||||
void* const host = entry.alias != nullptr ? entry.alias : reinterpret_cast<void*>(entry.base);
|
||||
const auto failed = [&](const char* step, VkResult result) -> const char* {
|
||||
if (entry.buffer) context.Function<PFN_vkDestroyBuffer>("vkDestroyBuffer")(context.device, entry.buffer, nullptr);
|
||||
if (entry.memory) context.Function<PFN_vkFreeMemory>("vkFreeMemory")(context.device, entry.memory, nullptr);
|
||||
|
|
@ -181,12 +185,12 @@ const char* createImport(const Context& context, HostImport& entry, VkResult& fa
|
|||
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
if (const auto result = context.Function<PFN_vkCreateBuffer>("vkCreateBuffer")(context.device, &info, nullptr, &entry.buffer); result != VK_SUCCESS) return failed("vkCreateBuffer", result);
|
||||
VkMemoryHostPointerPropertiesEXT pointer{VK_STRUCTURE_TYPE_MEMORY_HOST_POINTER_PROPERTIES_EXT};
|
||||
if (const auto result = context.Function<PFN_vkGetMemoryHostPointerPropertiesEXT>("vkGetMemoryHostPointerPropertiesEXT")(context.device, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, reinterpret_cast<const void*>(base), &pointer); result != VK_SUCCESS) return failed("vkGetMemoryHostPointerPropertiesEXT", result);
|
||||
if (const auto result = context.Function<PFN_vkGetMemoryHostPointerPropertiesEXT>("vkGetMemoryHostPointerPropertiesEXT")(context.device, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, host, &pointer); result != VK_SUCCESS) return failed("vkGetMemoryHostPointerPropertiesEXT", result);
|
||||
VkMemoryRequirements requirements{};
|
||||
context.Function<PFN_vkGetBufferMemoryRequirements>("vkGetBufferMemoryRequirements")(context.device, entry.buffer, &requirements);
|
||||
const auto types = requirements.memoryTypeBits & pointer.memoryTypeBits;
|
||||
if (types == 0) return failed("memory type selection", VK_ERROR_FORMAT_NOT_SUPPORTED);
|
||||
const VkImportMemoryHostPointerInfoEXT import{VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT, nullptr, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, reinterpret_cast<void*>(base)};
|
||||
const VkImportMemoryHostPointerInfoEXT import{VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT, nullptr, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, host};
|
||||
const VkMemoryAllocateFlagsInfo flags{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO, &import, VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT, 0};
|
||||
VkMemoryAllocateInfo allocation{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, &flags};
|
||||
allocation.allocationSize = bytes;
|
||||
|
|
@ -272,20 +276,32 @@ const HostImport* importAllocation(const Context& context, HostImports& state, s
|
|||
}
|
||||
return nullptr;
|
||||
}
|
||||
HostImport entry{base, bytes, VK_NULL_HANDLE, VK_NULL_HANDLE, 0};
|
||||
#ifdef _WIN32
|
||||
// Drivers pin imported pages, so every page must be committed and accessible.
|
||||
bool privateWritable = true;
|
||||
bool mapped = false;
|
||||
for (std::uint64_t cursor = base; cursor < base + bytes;) {
|
||||
MEMORY_BASIC_INFORMATION info{};
|
||||
if (VirtualQuery(reinterpret_cast<const void*>(cursor), &info, sizeof(info)) == 0 || info.State != MEM_COMMIT || (info.Protect & (PAGE_NOACCESS | PAGE_GUARD)) != 0) {
|
||||
state.failed.insert(base);
|
||||
return nullptr;
|
||||
}
|
||||
if (info.Type == MEM_MAPPED) mapped = true;
|
||||
const auto protection = info.Protect & 0xffu;
|
||||
if (info.Type != MEM_PRIVATE || (protection != PAGE_READWRITE && protection != PAGE_EXECUTE_READWRITE)) privateWritable = false;
|
||||
cursor = reinterpret_cast<std::uint64_t>(info.BaseAddress) + info.RegionSize;
|
||||
}
|
||||
if (!privateWritable) {
|
||||
entry.alias = mapped ? GuestArena::GuestArenaMapAlias_nid_postfix(static_cast<std::uintptr_t>(base), static_cast<std::size_t>(bytes)) : nullptr;
|
||||
if (entry.alias == nullptr) {
|
||||
state.failed.insert(base);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
decideImportWatch(context, state);
|
||||
if (state.unwatchImports) GuestMemory::Unwatch(base, bytes);
|
||||
HostImport entry{base, bytes, VK_NULL_HANDLE, VK_NULL_HANDLE, 0};
|
||||
entry.unwatched = state.unwatchImports;
|
||||
VkResult result = VK_SUCCESS;
|
||||
const char* step = nullptr;
|
||||
|
|
@ -294,6 +310,9 @@ const HostImport* importAllocation(const Context& context, HostImports& state, s
|
|||
return step == nullptr;
|
||||
});
|
||||
if (step != nullptr) {
|
||||
#ifdef _WIN32
|
||||
GuestArena::GuestArenaUnmapAlias_nid_postfix(entry.alias);
|
||||
#endif
|
||||
state.failed.insert(base);
|
||||
std::fprintf(stderr, "[gpu] host import of 0x%llx+0x%llx failed at %s (%d); falling back to copies\n", static_cast<unsigned long long>(base), static_cast<unsigned long long>(bytes), step, static_cast<int>(result));
|
||||
#ifdef _WIN32
|
||||
|
|
@ -335,6 +354,9 @@ const GuestAllocations::Range* containingRange(const GuestAllocations::Lease& le
|
|||
// guest addresses. Walks the imports only when the registry changed since the last walk.
|
||||
void refreshImports(const Context& context, HostImports& state, const GuestAllocations::Lease& lease) {
|
||||
if (state.device != context.device) {
|
||||
#ifdef _WIN32
|
||||
for (const auto& [address, entry] : state.imports) GuestArena::GuestArenaUnmapAlias_nid_postfix(entry.alias);
|
||||
#endif
|
||||
state.imports.clear();
|
||||
state.failed.clear();
|
||||
state.device = context.device;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ bool GuestArenaCollectWrites_nid_postfix(std::uintptr_t address, std::size_t byt
|
|||
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);
|
||||
void* GuestArenaMapAlias_nid_postfix(std::uintptr_t address, std::size_t bytes);
|
||||
void GuestArenaUnmapAlias_nid_postfix(void* alias);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,9 @@ public:
|
|||
std::lock_guard lock(mutex);
|
||||
auto cursor = reinterpret_cast<std::uintptr_t>(address);
|
||||
reset(cursor, bytes);
|
||||
HANDLE duplicate = nullptr;
|
||||
if (!DuplicateHandle(GetCurrentProcess(), section, GetCurrentProcess(), &duplicate, 0, FALSE, DUPLICATE_SAME_ACCESS)) fail("keep shared guest section");
|
||||
const auto owned = std::make_shared<Section>(duplicate);
|
||||
for (std::size_t done = 0; done < bytes; done += pageBytes) {
|
||||
split(cursor + done, pageBytes);
|
||||
void* page = reinterpret_cast<void*>(cursor + done);
|
||||
|
|
@ -83,7 +86,7 @@ public:
|
|||
}
|
||||
const auto base = cursor + done;
|
||||
shared->aliases.push_back(base);
|
||||
views.emplace(base, View{shared, protection, 0, false});
|
||||
views.emplace(base, View{shared, protection, 0, false, owned, offset + done});
|
||||
invalidate(*shared);
|
||||
}
|
||||
}
|
||||
|
|
@ -110,6 +113,27 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
void* MapAlias(std::uintptr_t address, std::size_t bytes) {
|
||||
std::lock_guard lock(mutex);
|
||||
if (address % pageBytes != 0 || bytes % pageBytes != 0 || bytes == 0) return nullptr;
|
||||
auto view = views.find(address);
|
||||
if (view == views.end()) return nullptr;
|
||||
const auto section = view->second.section;
|
||||
const auto offset = view->second.offset;
|
||||
SYSTEM_INFO system{};
|
||||
GetSystemInfo(&system);
|
||||
if (offset % system.dwAllocationGranularity != 0) return nullptr;
|
||||
for (std::size_t done = 0; done < bytes; done += pageBytes, ++view) {
|
||||
if (view == views.end() || view->first != address + done || view->second.offset != offset + done) return nullptr;
|
||||
if (view->second.section != section && !sameSection(view->second.section->handle, section->handle)) return nullptr;
|
||||
}
|
||||
return map(section->handle, GetCurrentProcess(), nullptr, offset, bytes, 0, PAGE_READWRITE, nullptr, 0);
|
||||
}
|
||||
|
||||
void UnmapAlias(void* alias) {
|
||||
if (alias != nullptr && !unmap(GetCurrentProcess(), alias, 0)) fail("unmap shared guest alias");
|
||||
}
|
||||
|
||||
bool Protection(std::uintptr_t address, std::uint32_t* protection) {
|
||||
std::lock_guard lock(mutex);
|
||||
const auto found = views.find(address & ~(pageBytes - 1));
|
||||
|
|
@ -181,11 +205,20 @@ private:
|
|||
std::uint64_t generation = 1;
|
||||
std::vector<std::uintptr_t> aliases;
|
||||
};
|
||||
struct Section {
|
||||
HANDLE handle;
|
||||
explicit Section(HANDLE handle) : handle(handle) {}
|
||||
Section(const Section&) = delete;
|
||||
Section& operator=(const Section&) = delete;
|
||||
~Section() { CloseHandle(handle); }
|
||||
};
|
||||
struct View {
|
||||
std::shared_ptr<SharedPage> page;
|
||||
DWORD protection;
|
||||
std::uint64_t seen;
|
||||
bool armed;
|
||||
std::shared_ptr<Section> section;
|
||||
std::uint64_t offset;
|
||||
};
|
||||
void forgetClean(std::uintptr_t start, std::uintptr_t end) {
|
||||
auto it = cleanRanges.lower_bound(start);
|
||||
|
|
@ -215,12 +248,17 @@ private:
|
|||
for (const auto alias : page.aliases) forgetClean(alias, alias + pageBytes);
|
||||
}
|
||||
|
||||
bool sameSection(HANDLE first, HANDLE second) const {
|
||||
return compare != nullptr && compare(first, second);
|
||||
}
|
||||
|
||||
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);
|
||||
using CompareFunction = BOOL (WINAPI*)(HANDLE, HANDLE);
|
||||
|
||||
WindowsMappings() {
|
||||
const auto module = GetModuleHandleW(L"KernelBase.dll");
|
||||
|
|
@ -229,6 +267,7 @@ private:
|
|||
map = reinterpret_cast<MapFunction>(GetProcAddress(module, "MapViewOfFile3"));
|
||||
unmap = reinterpret_cast<UnmapFunction>(GetProcAddress(module, "UnmapViewOfFile2"));
|
||||
if (!allocate || !map || !unmap) throw std::runtime_error("Windows placeholder memory APIs are required");
|
||||
compare = reinterpret_cast<CompareFunction>(GetProcAddress(module, "CompareObjectHandles"));
|
||||
}
|
||||
|
||||
[[noreturn]] static void fail(const char* operation) {
|
||||
|
|
@ -300,6 +339,7 @@ private:
|
|||
AllocateFunction allocate = nullptr;
|
||||
MapFunction map = nullptr;
|
||||
UnmapFunction unmap = nullptr;
|
||||
CompareFunction compare = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -177,6 +177,14 @@ void GuestArenaMap_nid_postfix(void* pointer, std::size_t bytes, void* section,
|
|||
if (!Arena::Get().Contains(pointer, bytes)) throw std::invalid_argument("shared mapping outside the guest arena");
|
||||
WindowsMappings::Get().Map(pointer, bytes, section, offset, protection);
|
||||
}
|
||||
|
||||
void* GuestArenaMapAlias_nid_postfix(std::uintptr_t address, std::size_t bytes) {
|
||||
return WindowsMappings::Get().MapAlias(address, bytes);
|
||||
}
|
||||
|
||||
void GuestArenaUnmapAlias_nid_postfix(void* alias) {
|
||||
WindowsMappings::Get().UnmapAlias(alias);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool GuestArenaWriteWatched_nid_postfix() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue