diff --git a/Source/Core/Common/MemArena.h b/Source/Core/Common/MemArena.h index 4f414afa27..baaba5c556 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_VirtualProtect = nullptr; }; #endif @@ -111,6 +112,15 @@ public: /// void UnmapFromMemoryRegion(void* view, size_t size); + /// + /// Virtual protect a section from the memory region previously mapped by CreateView. + /// + /// @param data Pointer to data to protect. + /// @param size Size of the protection. + /// @param flag What new permission to protect with. + /// + bool VirtualProtectMemoryRegion(u8* data, size_t size, u64 flag); + 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..7eb10d9877 100644 --- a/Source/Core/Common/MemArenaWin.cpp +++ b/Source/Core/Common/MemArenaWin.cpp @@ -34,6 +34,9 @@ using PMapViewOfFile3 = PVOID(WINAPI*)(HANDLE FileMapping, HANDLE Process, PVOID using PUnmapViewOfFileEx = BOOL(WINAPI*)(PVOID BaseAddress, ULONG UnmapFlags); +using PVirtualProtect = BOOL(WINAPI*)(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, + PDWORD lpflOldProtect); + using PIsApiSetImplemented = BOOL(APIENTRY*)(PCSTR Contract); namespace Common @@ -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_VirtualProtect = + functions->m_kernel32_handle.GetSymbolAddress("VirtualProtect"); 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_VirtualProtect = address_VirtualProtect; return true; } @@ -209,6 +215,13 @@ void MemArena::ReleaseMemoryRegion() } } +bool MemArena::VirtualProtectMemoryRegion(u8* data, size_t size, u64 flag) +{ + DWORD lpflOldProtect = 0; + return static_cast(m_memory_functions.m_address_VirtualProtect)( + data, size, flag, &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 84a398b7f7..d1fff961b4 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" @@ -385,8 +386,9 @@ 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(); - + } #ifdef USE_MEMORYWATCHER s_memory_watcher = std::make_unique(); #endif @@ -689,6 +691,9 @@ static void EmuThread(Core::System& system, std::unique_ptr boot cpuThreadFunc(system, savestate_path, delete_savestate); } + if (cpuThreadFunc == CpuThread) + system.GetMemory().InitDirtyPages(); + INFO_LOG_FMT(CONSOLE, "{}", StopMessage(true, "Stopping GDB ...")); GDBStub::Deinit(); INFO_LOG_FMT(CONSOLE, "{}", StopMessage(true, "GDB stopped.")); diff --git a/Source/Core/Core/HW/HW.cpp b/Source/Core/Core/HW/HW.cpp index 524feab884..4bb05345c9 100644 --- a/Source/Core/Core/HW/HW.cpp +++ b/Source/Core/Core/HW/HW.cpp @@ -82,9 +82,9 @@ void Shutdown(Core::System& system) system.GetCoreTiming().Shutdown(); } -void DoState(Core::System& system, PointerWrap& p) +void DoState(Core::System& system, PointerWrap& p, bool delta) { - system.GetMemory().DoState(p); + system.GetMemory().DoState(p, delta); p.DoMarker("Memory"); system.GetMemoryInterface().DoState(p); p.DoMarker("MemoryInterface"); diff --git a/Source/Core/Core/HW/HW.h b/Source/Core/Core/HW/HW.h index d70ad72fa6..a3dabfa339 100644 --- a/Source/Core/Core/HW/HW.h +++ b/Source/Core/Core/HW/HW.h @@ -14,5 +14,5 @@ namespace HW { void Init(Core::System& system, const Sram* override_sram); void Shutdown(Core::System& system); -void DoState(Core::System& system, PointerWrap& p); +void DoState(Core::System& system, PointerWrap& p, bool delta); } // namespace HW diff --git a/Source/Core/Core/HW/Memmap.cpp b/Source/Core/Core/HW/Memmap.cpp index be58fbfc69..1e87f414b0 100644 --- a/Source/Core/Core/HW/Memmap.cpp +++ b/Source/Core/Core/HW/Memmap.cpp @@ -47,6 +47,57 @@ MemoryManager::MemoryManager(Core::System& system) : m_system(system) MemoryManager::~MemoryManager() = default; +u64 MemoryManager::GetDirtyPageIndexFromAddress(u64 address) +{ + const size_t page_size = Common::PageSize(); + const size_t page_mask = page_size - 1; + return address & ~page_mask; +} + +bool MemoryManager::HandleFault(uintptr_t fault_address) +{ + u8* fault_address_bytes = reinterpret_cast(fault_address); + if (!IsAddressInEmulatedMemory(fault_address_bytes) || IsPageDirty(fault_address)) + { + return false; + } + SetPageDirtyBit(fault_address, 0x1, true); + bool change_protection = + m_arena.VirtualProtectMemoryRegion(fault_address_bytes, 0x1, PAGE_READWRITE); + + if (!change_protection) + { + return false; + } + + return true; +} + +void MemoryManager::WriteProtectPhysicalMemoryRegions() +{ + for (const PhysicalMemoryRegion& region : m_physical_regions) + { + if (!region.active || !region.track) + continue; + + bool change_protection = + m_arena.VirtualProtectMemoryRegion((*region.out_pointer), region.size, PAGE_READONLY); + + if (!change_protection) + { + PanicAlertFmt("Memory::WriteProtectPhysicalMemoryRegions(): Failed to write protect for " + "this block of memory at 0x{:08X}.", + reinterpret_cast(*region.out_pointer)); + } + const size_t page_size = Common::PageSize(); + const intptr_t out_pointer = reinterpret_cast(*region.out_pointer); + for (size_t i = out_pointer; i < region.size; i += page_size) + { + m_dirty_pages[i] = false; + } + } +} + void MemoryManager::InitMMIO(bool is_wii) { m_mmio_mapping = std::make_unique(); @@ -71,6 +122,11 @@ void MemoryManager::InitMMIO(bool is_wii) } } +void MemoryManager::InitDirtyPages() +{ + WriteProtectPhysicalMemoryRegions(); +} + void MemoryManager::Init() { const auto get_mem1_size = [] { @@ -95,13 +151,14 @@ void MemoryManager::Init() m_exram_mask = GetExRamSize() - 1; m_physical_regions[0] = PhysicalMemoryRegion{ - &m_ram, 0x00000000, GetRamSize(), PhysicalMemoryRegion::ALWAYS, 0, false}; + &m_ram, 0x00000000, GetRamSize(), PhysicalMemoryRegion::ALWAYS, 0, false, true}; m_physical_regions[1] = PhysicalMemoryRegion{ - &m_l1_cache, 0xE0000000, GetL1CacheSize(), PhysicalMemoryRegion::ALWAYS, 0, false}; + &m_l1_cache, 0xE0000000, GetL1CacheSize(), PhysicalMemoryRegion::ALWAYS, 0, false, false}; m_physical_regions[2] = PhysicalMemoryRegion{ - &m_fake_vmem, 0x7E000000, GetFakeVMemSize(), PhysicalMemoryRegion::FAKE_VMEM, 0, false}; + &m_fake_vmem, 0x7E000000, GetFakeVMemSize(), PhysicalMemoryRegion::FAKE_VMEM, 0, + false, false}; m_physical_regions[3] = PhysicalMemoryRegion{ - &m_exram, 0x10000000, GetExRamSize(), PhysicalMemoryRegion::WII_ONLY, 0, false}; + &m_exram, 0x10000000, GetExRamSize(), PhysicalMemoryRegion::WII_ONLY, 0, false, true}; const bool wii = m_system.IsWii(); const bool mmu = m_system.IsMMUMode(); @@ -164,6 +221,21 @@ bool MemoryManager::IsAddressInFastmemArea(const u8* address) const return address >= m_fastmem_arena && address < m_fastmem_arena + m_fastmem_arena_size; } +bool MemoryManager::IsAddressInEmulatedMemory(const u8* address) const +{ + for (const PhysicalMemoryRegion& region : m_physical_regions) + { + if (!region.active || !region.track) + continue; + + if (address >= *region.out_pointer && address < *region.out_pointer + region.size) + { + return true; + } + } + return false; +} + bool MemoryManager::InitFastmemArena() { // Here we set up memory mappings for fastmem. The basic idea of fastmem is that we reserve 4 GiB @@ -290,7 +362,7 @@ void MemoryManager::UpdateLogicalMemory(const PowerPC::BatTable& dbat_table) } } -void MemoryManager::DoState(PointerWrap& p) +void MemoryManager::DoState(PointerWrap& p, bool delta) { const u32 current_ram_size = GetRamSize(); const u32 current_l1_cache_size = GetL1CacheSize(); @@ -327,28 +399,60 @@ void MemoryManager::DoState(PointerWrap& p) p.SetVerifyMode(); return; } - - p.DoArray(m_ram, current_ram_size); - p.DoArray(m_l1_cache, current_l1_cache_size); - p.DoMarker("Memory RAM"); - if (current_have_fake_vmem) - p.DoArray(m_fake_vmem, current_fake_vmem_size); - p.DoMarker("Memory FakeVMEM"); - if (current_have_exram) - p.DoArray(m_exram, current_exram_size); - p.DoMarker("Memory EXRAM"); + if (delta) + { + const u32 page_size = static_cast(Common::PageSize()); + p.Do(m_dirty_pages); + for (size_t i = 0; i < current_ram_size; i += page_size) + { + if (IsPageDirty(reinterpret_cast(&m_ram[i]))) + { + p.DoArray(m_ram + i, page_size); + } + } + p.DoArray(m_l1_cache, current_l1_cache_size); + p.DoMarker("Memory RAM"); + if (current_have_fake_vmem) + { + p.DoArray(m_fake_vmem, current_fake_vmem_size); + } + p.DoMarker("Memory FakeVMEM"); + if (current_have_exram) + { + for (size_t i = 0; i < current_exram_size; i += page_size) + { + if (IsPageDirty(reinterpret_cast(&m_exram[i]))) + { + p.DoArray(m_exram + i, page_size); + } + } + } + p.DoMarker("Memory EXRAM"); + } + else + { + p.DoArray(m_ram, current_ram_size); + p.DoArray(m_l1_cache, current_l1_cache_size); + p.DoMarker("Memory RAM"); + if (current_have_fake_vmem) + p.DoArray(m_fake_vmem, current_fake_vmem_size); + p.DoMarker("Memory FakeVMEM"); + if (current_have_exram) + p.DoArray(m_exram, current_exram_size); + p.DoMarker("Memory EXRAM"); + } } void MemoryManager::Shutdown() { ShutdownFastmemArena(); + m_dirty_pages.clear(); m_is_initialized = false; for (const PhysicalMemoryRegion& region : m_physical_regions) { if (!region.active) continue; - m_arena.ReleaseView(*region.out_pointer, region.size); *region.out_pointer = nullptr; } @@ -573,4 +677,22 @@ void MemoryManager::Write_U64_Swap(u64 value, u32 address) CopyToEmu(address, &value, sizeof(value)); } +bool MemoryManager::IsPageDirty(uintptr_t address) +{ + return m_dirty_pages[GetDirtyPageIndexFromAddress(address)]; +} + +void MemoryManager::SetPageDirtyBit(uintptr_t address, size_t size, bool dirty) +{ + for (size_t i = 0; i < size; i++) + { + m_dirty_pages[GetDirtyPageIndexFromAddress(address + i)] = dirty; + } +} + +void MemoryManager::ResetDirtyPages() +{ + WriteProtectPhysicalMemoryRegions(); +} + } // namespace Memory diff --git a/Source/Core/Core/HW/Memmap.h b/Source/Core/Core/HW/Memmap.h index e0708605db..a9b6e52531 100644 --- a/Source/Core/Core/HW/Memmap.h +++ b/Source/Core/Core/HW/Memmap.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "Common/CommonTypes.h" #include "Common/MathUtil.h" @@ -48,6 +49,7 @@ struct PhysicalMemoryRegion } flags; u32 shm_position; bool active; + bool track; }; struct LogicalMemoryView @@ -78,6 +80,7 @@ public: u32 GetExRamMask() const { return m_exram_mask; } bool IsAddressInFastmemArea(const u8* address) const; + bool IsAddressInEmulatedMemory(const u8* address) const; u8* GetPhysicalBase() const { return m_physical_base; } u8* GetLogicalBase() const { return m_logical_base; } u8* GetPhysicalPageMappingsBase() const { return m_physical_page_mappings_base; } @@ -94,10 +97,11 @@ public: // Init and Shutdown bool IsInitialized() const { return m_is_initialized; } void Init(); + void InitDirtyPages(); void Shutdown(); bool InitFastmemArena(); void ShutdownFastmemArena(); - void DoState(PointerWrap& p); + void DoState(PointerWrap& p, bool delta); void UpdateLogicalMemory(const PowerPC::BatTable& dbat_table); @@ -130,6 +134,13 @@ 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(); + bool HandleFault(uintptr_t fault_address); + + std::map& GetDirtyPages() { return m_dirty_pages; } + // Templated functions for byteswapped copies. template void CopyFromEmuSwapped(T* data, u32 address, size_t size) const @@ -254,6 +265,11 @@ private: Core::System& m_system; + std::map m_dirty_pages; + + u64 GetDirtyPageIndexFromAddress(u64 address); + void WriteProtectPhysicalMemoryRegions(); + void InitMMIO(bool is_wii); }; } // namespace Memory diff --git a/Source/Core/Core/MemTools.cpp b/Source/Core/Core/MemTools.cpp index 75d20dd6a9..d1dd689642 100644 --- a/Source/Core/Core/MemTools.cpp +++ b/Source/Core/Core/MemTools.cpp @@ -14,6 +14,7 @@ #include "Common/MsgHandler.h" #include "Common/Thread.h" +#include "Core/HW/Memmap.h" #include "Core/MachineContext.h" #include "Core/PowerPC/JitInterface.h" #include "Core/System.h" @@ -24,6 +25,7 @@ #ifndef _WIN32 #include // Needed for _POSIX_VERSION #endif +#include #if defined(__APPLE__) #ifdef _M_X86_64 @@ -60,8 +62,12 @@ 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)) + Core::System& system = Core::System::GetInstance(); + if (system.GetMemory().HandleFault(fault_address)) + { + return EXCEPTION_CONTINUE_EXECUTION; + } + else if (system.GetJitInterface().HandleFault(fault_address, ctx)) { return EXCEPTION_CONTINUE_EXECUTION; } diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index aac9d05221..b26d9869ed 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -139,7 +139,7 @@ void EnableCompression(bool compression) s_use_compression = compression; } -static void DoState(Core::System& system, PointerWrap& p) +static void DoState(Core::System& system, PointerWrap& p, bool delta) { bool is_wii = system.IsWii() || system.IsMIOS(); const bool is_wii_currently = is_wii; @@ -188,7 +188,7 @@ static void DoState(Core::System& system, PointerWrap& p) p.DoMarker("CoreTiming"); // HW needs to be restored before PowerPC because the data cache might need to be flushed. - HW::DoState(system, p); + HW::DoState(system, p, delta); p.DoMarker("HW"); system.GetPowerPC().DoState(p); @@ -224,7 +224,7 @@ void LoadFromBuffer(Core::System& system, std::vector& buffer) [&] { u8* ptr = buffer.data(); PointerWrap p(&ptr, buffer.size(), PointerWrap::Mode::Read); - DoState(system, p); + DoState(system, p, false); }, true); } @@ -237,13 +237,13 @@ void SaveToBuffer(Core::System& system, std::vector& buffer) u8* ptr = nullptr; PointerWrap p_measure(&ptr, 0, PointerWrap::Mode::Measure); - DoState(system, p_measure); + DoState(system, p_measure, false); const size_t buffer_size = reinterpret_cast(ptr); buffer.resize(buffer_size); ptr = buffer.data(); PointerWrap p(&ptr, buffer_size, PointerWrap::Mode::Write); - DoState(system, p); + DoState(system, p, false); }, true); } @@ -486,7 +486,7 @@ void SaveAs(Core::System& system, const std::string& filename, bool wait) // Measure the size of the buffer. u8* ptr = nullptr; PointerWrap p_measure(&ptr, 0, PointerWrap::Mode::Measure); - DoState(system, p_measure); + DoState(system, p_measure, false); const size_t buffer_size = reinterpret_cast(ptr); // Then actually do the write. @@ -494,7 +494,7 @@ void SaveAs(Core::System& system, const std::string& filename, bool wait) current_buffer.resize(buffer_size); ptr = current_buffer.data(); PointerWrap p(&ptr, buffer_size, PointerWrap::Mode::Write); - DoState(system, p); + DoState(system, p, true); if (p.IsWriteMode()) { @@ -900,7 +900,7 @@ void LoadAs(Core::System& system, const std::string& filename) { u8* ptr = buffer.data(); PointerWrap p(&ptr, buffer.size(), PointerWrap::Mode::Read); - DoState(system, p); + DoState(system, p, true); loaded = true; loadedSuccessfully = p.IsReadMode(); }