diff --git a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp index c2e662d7d..baf70ca07 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp @@ -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; diff --git a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp index c46db689d..aaaabca33 100644 --- a/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp +++ b/core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp @@ -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 @@ -133,6 +134,9 @@ HostImports& Imports() { void destroyImport(const Context& context, const HostImport& entry) { context.Function("vkDestroyBuffer")(context.device, entry.buffer, nullptr); context.Function("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(entry.base); const auto failed = [&](const char* step, VkResult result) -> const char* { if (entry.buffer) context.Function("vkDestroyBuffer")(context.device, entry.buffer, nullptr); if (entry.memory) context.Function("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("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("vkGetMemoryHostPointerPropertiesEXT")(context.device, VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, reinterpret_cast(base), &pointer); result != VK_SUCCESS) return failed("vkGetMemoryHostPointerPropertiesEXT", result); + if (const auto result = context.Function("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("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(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(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(info.BaseAddress) + info.RegionSize; } + if (!privateWritable) { + entry.alias = mapped ? GuestArena::GuestArenaMapAlias_nid_postfix(static_cast(base), static_cast(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(base), static_cast(bytes), step, static_cast(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; diff --git a/core/libs/prx/libc/include/GuestArena.hpp b/core/libs/prx/libc/include/GuestArena.hpp index 894dd0012..8d7972549 100644 --- a/core/libs/prx/libc/include/GuestArena.hpp +++ b/core/libs/prx/libc/include/GuestArena.hpp @@ -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 } diff --git a/core/libs/prx/libc/include/WindowsMappings.hpp b/core/libs/prx/libc/include/WindowsMappings.hpp index 9f008ffad..153e6c749 100644 --- a/core/libs/prx/libc/include/WindowsMappings.hpp +++ b/core/libs/prx/libc/include/WindowsMappings.hpp @@ -69,6 +69,9 @@ public: std::lock_guard lock(mutex); auto cursor = reinterpret_cast(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
(duplicate); for (std::size_t done = 0; done < bytes; done += pageBytes) { split(cursor + done, pageBytes); void* page = reinterpret_cast(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 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 page; DWORD protection; std::uint64_t seen; bool armed; + std::shared_ptr
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(GetProcAddress(module, "MapViewOfFile3")); unmap = reinterpret_cast(GetProcAddress(module, "UnmapViewOfFile2")); if (!allocate || !map || !unmap) throw std::runtime_error("Windows placeholder memory APIs are required"); + compare = reinterpret_cast(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; }; } diff --git a/core/libs/prx/libc/src/GuestArena.cpp b/core/libs/prx/libc/src/GuestArena.cpp index 68892f438..dc87e282e 100644 --- a/core/libs/prx/libc/src/GuestArena.cpp +++ b/core/libs/prx/libc/src/GuestArena.cpp @@ -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() {