diff --git a/Source/Core/Common/MemArena.h b/Source/Core/Common/MemArena.h index 4f414afa27..1182657182 100644 --- a/Source/Core/Common/MemArena.h +++ b/Source/Core/Common/MemArena.h @@ -22,6 +22,7 @@ struct WindowsMemoryFunctions void* m_address_UnmapViewOfFileEx = nullptr; void* m_address_VirtualAlloc2 = nullptr; void* m_address_MapViewOfFile3 = nullptr; + void* m_address_VirtualProtectEx = nullptr; }; #endif @@ -111,6 +112,14 @@ public: /// void UnmapFromMemoryRegion(void* view, size_t size); + /// + /// Write protect a section from the memory region previously mapped by CreateView. + /// + /// @param data Pointer to data to protect. + /// @param size Size of the protection. + /// + bool WriteProtectMemoryRegion(void* data, size_t size); + private: #ifdef _WIN32 WindowsMemoryRegion* EnsureSplitRegionForMapping(void* address, size_t size); diff --git a/Source/Core/Common/MemArenaWin.cpp b/Source/Core/Common/MemArenaWin.cpp index a991687d91..e50656ae5a 100644 --- a/Source/Core/Common/MemArenaWin.cpp +++ b/Source/Core/Common/MemArenaWin.cpp @@ -34,8 +34,11 @@ using PMapViewOfFile3 = PVOID(WINAPI*)(HANDLE FileMapping, HANDLE Process, PVOID using PUnmapViewOfFileEx = BOOL(WINAPI*)(PVOID BaseAddress, ULONG UnmapFlags); +using PVirtualProtectEx = BOOL(WINAPI*)(HANDLE Process, LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD* lpflOldProtect); + using PIsApiSetImplemented = BOOL(APIENTRY*)(PCSTR Contract); + namespace Common { struct WindowsMemoryRegion @@ -78,11 +81,14 @@ static bool InitWindowsMemoryFunctions(WindowsMemoryFunctions* functions) functions->m_api_ms_win_core_memory_l1_1_6_handle.GetSymbolAddress("MapViewOfFile3FromApp"); void* const address_UnmapViewOfFileEx = functions->m_kernel32_handle.GetSymbolAddress("UnmapViewOfFileEx"); + void* const address_VirtualProtectEx = + functions->m_kernel32_handle.GetSymbolAddress("VirtualProtectEx"); if (address_VirtualAlloc2 && address_MapViewOfFile3 && address_UnmapViewOfFileEx) { functions->m_address_VirtualAlloc2 = address_VirtualAlloc2; functions->m_address_MapViewOfFile3 = address_MapViewOfFile3; functions->m_address_UnmapViewOfFileEx = address_UnmapViewOfFileEx; + functions->m_address_VirtualProtectEx = address_VirtualProtectEx; return true; } @@ -209,6 +215,13 @@ void MemArena::ReleaseMemoryRegion() } } +bool MemArena::WriteProtectMemoryRegion(void* data, size_t size) +{ + PDWORD lpflOldProtect = 0; + return static_cast(m_memory_functions.m_address_VirtualProtectEx)( + nullptr, data, size, FILE_MAP_READ, &lpflOldProtect); +} + WindowsMemoryRegion* MemArena::EnsureSplitRegionForMapping(void* start_address, size_t size) { u8* const address = static_cast(start_address); diff --git a/Source/Core/Common/MemoryUtil.cpp b/Source/Core/Common/MemoryUtil.cpp index 2cd393d098..29b6626e79 100644 --- a/Source/Core/Common/MemoryUtil.cpp +++ b/Source/Core/Common/MemoryUtil.cpp @@ -289,4 +289,15 @@ size_t MemPhysical() #endif } +size_t PageSize() +{ +#ifdef _WIN32 + SYSTEM_INFO sysInfo; + GetSystemInfo(&sysInfo); + return sysInfo.dwPageSize; +#else + return sysconf(_SC_PAGESIZE); +#endif +} + } // namespace Common diff --git a/Source/Core/Common/MemoryUtil.h b/Source/Core/Common/MemoryUtil.h index 11ee1c4cbb..0db1448f2b 100644 --- a/Source/Core/Common/MemoryUtil.h +++ b/Source/Core/Common/MemoryUtil.h @@ -34,5 +34,6 @@ bool ReadProtectMemory(void* ptr, size_t size); bool WriteProtectMemory(void* ptr, size_t size, bool executable = false); bool UnWriteProtectMemory(void* ptr, size_t size, bool allowExecute = false); size_t MemPhysical(); +size_t PageSize(); } // namespace Common diff --git a/Source/Core/Core/Core.cpp b/Source/Core/Core/Core.cpp index 8399d55cba..683fa9cf6f 100644 --- a/Source/Core/Core/Core.cpp +++ b/Source/Core/Core/Core.cpp @@ -75,6 +75,7 @@ #include "Core/State.h" #include "Core/System.h" #include "Core/WiiRoot.h" +#include "Core/HW/Memmap.h" #ifdef USE_MEMORYWATCHER #include "Core/MemoryWatcher.h" @@ -380,8 +381,10 @@ static void CpuThread(Core::System& system, const std::optional& sa // The JIT need to be able to intercept faults, both for fastmem and for the BLR optimization. const bool exception_handler = EMM::IsExceptionHandlerSupported(); if (exception_handler) + { EMM::InstallExceptionHandler(); - + system.GetMemory().InitDirtyPages(); + } #ifdef USE_MEMORYWATCHER s_memory_watcher = std::make_unique(); #endif diff --git a/Source/Core/Core/HW/Memmap.cpp b/Source/Core/Core/HW/Memmap.cpp index be58fbfc69..e679df967e 100644 --- a/Source/Core/Core/HW/Memmap.cpp +++ b/Source/Core/Core/HW/Memmap.cpp @@ -47,6 +47,37 @@ MemoryManager::MemoryManager(Core::System& system) : m_system(system) MemoryManager::~MemoryManager() = default; +std::optional MemoryManager::GetDirtyPageIndexFromAddress(u64 address) +{ + size_t page_size = Common::PageSize(); + size_t page_mask = page_size - 1; + u64 page_base = address & ~page_mask; + + size_t index = 0; + bool foundPageBase = false; + + for (const PhysicalMemoryRegion& region : m_physical_regions) + { + if (!region.active) + continue; + uintptr_t region_address = reinterpret_cast(*region.out_pointer); + if (page_base >= region_address && page_base <= region_address + region.size) + { + foundPageBase = true; + index += std::floorl((page_base - region_address) / page_size); + break; + } + index += (region.size / page_size); + } + if (!foundPageBase) + { + PanicAlertFmt("Unknown PTE in Dirty Bit Lookup: {:#010x}", page_base); + return std::nullopt; + } + + return index; +} + void MemoryManager::InitMMIO(bool is_wii) { m_mmio_mapping = std::make_unique(); @@ -71,6 +102,29 @@ void MemoryManager::InitMMIO(bool is_wii) } } +void MemoryManager::InitDirtyPages() +{ + for (const PhysicalMemoryRegion& region : m_physical_regions) + { + if (!region.active) + continue; + size_t page_size = Common::PageSize(); + for (size_t i = 0; i < region.size; i += page_size) + { + DWORD lpflOldProtect = 0; + bool change_protection = + VirtualProtect((*region.out_pointer) + i, page_size, PAGE_READONLY, &lpflOldProtect); + if (!change_protection) + { + PanicAlertFmt( + "Memory::Init(): Failed to write protect for this block of memory at 0x{:08X}.", + reinterpret_cast(*region.out_pointer)); + } + m_dirty_pages.push_back(false); + } + } +} + void MemoryManager::Init() { const auto get_mem1_size = [] { @@ -327,16 +381,54 @@ void MemoryManager::DoState(PointerWrap& p) p.SetVerifyMode(); return; } - - p.DoArray(m_ram, current_ram_size); - p.DoArray(m_l1_cache, current_l1_cache_size); + u32 page_count = static_cast(Common::PageSize()); + p.Do(m_dirty_pages); + for (size_t i = 0; i < current_ram_size; i++) + { + if (IsPageDirty(reinterpret_cast(&m_ram[i]))) + { + p.DoArray(m_ram + i, page_count); + i += page_count + 1; + } + } + for (size_t i = 0; i < current_l1_cache_size; i++) + { + if (IsPageDirty(reinterpret_cast(&m_l1_cache[i]))) + { + p.DoArray(m_l1_cache + i, page_count); + i += page_count + 1; + } + } p.DoMarker("Memory RAM"); if (current_have_fake_vmem) - p.DoArray(m_fake_vmem, current_fake_vmem_size); + { + for (size_t i = 0; i < current_fake_vmem_size; i++) + { + if (IsPageDirty(reinterpret_cast(&m_fake_vmem[i]))) + { + p.DoArray(m_fake_vmem + i, page_count); + i += page_count + 1; + } + } + } p.DoMarker("Memory FakeVMEM"); if (current_have_exram) - p.DoArray(m_exram, current_exram_size); + { + for (size_t i = 0; i < current_exram_size; i++) + { + if (IsPageDirty(reinterpret_cast(&m_exram[i]))) + { + p.DoArray(m_exram + i, page_count); + i += page_count + 1; + } + } + } p.DoMarker("Memory EXRAM"); + + if (p.IsWriteMode()) + { + ResetDirtyPages(); + } } void MemoryManager::Shutdown() @@ -573,4 +665,35 @@ void MemoryManager::Write_U64_Swap(u64 value, u32 address) CopyToEmu(address, &value, sizeof(value)); } +bool MemoryManager::IsPageDirty(uintptr_t address) +{ + std::optional index = GetDirtyPageIndexFromAddress(address); + if (index.has_value()) + { + return m_dirty_pages[index.value()]; + } + else + { + return true; + } +} + +void MemoryManager::SetPageDirtyBit(uintptr_t address, size_t size, bool dirty) +{ + for (size_t i = 0; i < size; i++) + { + std::optional index = GetDirtyPageIndexFromAddress(address + i); + if (index.has_value()) + { + m_dirty_pages[index.value()] = dirty; + } + } +} + +void MemoryManager::ResetDirtyPages() +{ + for (int i = 0; i < m_dirty_pages.size(); i++) + m_dirty_pages[i] = false; +} + } // namespace Memory diff --git a/Source/Core/Core/HW/Memmap.h b/Source/Core/Core/HW/Memmap.h index e0708605db..4743068694 100644 --- a/Source/Core/Core/HW/Memmap.h +++ b/Source/Core/Core/HW/Memmap.h @@ -94,6 +94,7 @@ public: // Init and Shutdown bool IsInitialized() const { return m_is_initialized; } void Init(); + void InitDirtyPages(); void Shutdown(); bool InitFastmemArena(); void ShutdownFastmemArena(); @@ -130,6 +131,12 @@ public: void Write_U32_Swap(u32 var, u32 address); void Write_U64_Swap(u64 var, u32 address); + bool IsPageDirty(uintptr_t address); + void SetPageDirtyBit(uintptr_t address, size_t size, bool dirty); + void ResetDirtyPages(); + + std::vector& GetDirtyPages() { return m_dirty_pages; } + // Templated functions for byteswapped copies. template void CopyFromEmuSwapped(T* data, u32 address, size_t size) const @@ -254,6 +261,9 @@ private: Core::System& m_system; + std::vector m_dirty_pages; + + std::optional GetDirtyPageIndexFromAddress(u64 address); void InitMMIO(bool is_wii); }; } // namespace Memory diff --git a/Source/Core/Core/MemTools.cpp b/Source/Core/Core/MemTools.cpp index 75d20dd6a9..bbe361071f 100644 --- a/Source/Core/Core/MemTools.cpp +++ b/Source/Core/Core/MemTools.cpp @@ -17,6 +17,7 @@ #include "Core/MachineContext.h" #include "Core/PowerPC/JitInterface.h" #include "Core/System.h" +#include "Core/HW/Memmap.h" #if defined(__FreeBSD__) || defined(__NetBSD__) #include @@ -24,6 +25,7 @@ #ifndef _WIN32 #include // Needed for _POSIX_VERSION #endif +#include #if defined(__APPLE__) #ifdef _M_X86_64 @@ -60,11 +62,26 @@ static LONG NTAPI Handler(PEXCEPTION_POINTERS pPtrs) // virtual address of the inaccessible data uintptr_t fault_address = (uintptr_t)pPtrs->ExceptionRecord->ExceptionInformation[1]; SContext* ctx = pPtrs->ContextRecord; - if (Core::System::GetInstance().GetJitInterface().HandleFault(fault_address, ctx)) { return EXCEPTION_CONTINUE_EXECUTION; } + else if (!Core::System::GetInstance().GetMemory().IsPageDirty(fault_address)) + { + Core::System::GetInstance().GetMemory().SetPageDirtyBit(fault_address, 1, true); + + size_t page_size = Common::PageSize(); + size_t page_mask = page_size - 1; + u64 page_base = fault_address & ~page_mask; + DWORD lpflOldProtect = 0; + bool change_protection = VirtualProtect(reinterpret_cast(page_base), page_size, PAGE_READONLY, &lpflOldProtect); + if (!change_protection) + { + return EXCEPTION_CONTINUE_SEARCH; + } + + return EXCEPTION_CONTINUE_EXECUTION; + } else { // Let's not prevent debugging.