From f546e5a8eff8a5aa54c1c93dafc25fb9535a40eb Mon Sep 17 00:00:00 2001 From: Eladash Date: Wed, 6 Jul 2022 15:53:48 +0300 Subject: [PATCH] VM/Savestates: Replace bugged read-only block optimization --- rpcs3/Emu/Cell/PPUModule.cpp | 66 ++++++++++++++++++++++++++---------- rpcs3/Emu/Memory/vm.cpp | 11 +++--- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/rpcs3/Emu/Cell/PPUModule.cpp b/rpcs3/Emu/Cell/PPUModule.cpp index 684a25022b..90d460b26c 100644 --- a/rpcs3/Emu/Cell/PPUModule.cpp +++ b/rpcs3/Emu/Cell/PPUModule.cpp @@ -869,23 +869,60 @@ void ppu_manual_load_imports_exports(u32 imports_start, u32 imports_size, u32 ex } // For savestates -extern bool is_memory_read_only_of_executable(u32 addr) +extern bool is_memory_compatible_for_copy_from_executable_optimization(u32 addr, u32 size) { if (g_cfg.savestate.state_inspection_mode) { return false; } - const auto _main = g_fxo->try_get(); - ensure(_main); + static ppu_exec_object s_ppu_exec; + static std::vector zeroes; - for (const auto& seg : _main->segs) + if (!addr) { - if (!seg.addr || (seg.flags & 0x2) /* W */) - continue; + // A call for cleanup + s_ppu_exec.clear(); + zeroes = {}; + return false; + } - if (addr >= seg.addr && addr < (seg.addr + seg.size)) - return true; + if (s_ppu_exec != elf_error::ok) + { + if (s_ppu_exec != elf_error::stream) + { + // Failed before + return false; + } + + s_ppu_exec.open(decrypt_self(fs::file(Emu.GetBoot()))); + + if (s_ppu_exec != elf_error::ok) + { + return false; + } + } + + for (const auto& prog : s_ppu_exec.progs) + { + const u32 vaddr = static_cast(prog.p_vaddr); + const u32 seg_size = static_cast(prog.p_filesz); + const u32 aligned_vaddr = vaddr & -0x10000; + const u32 vaddr_offs = vaddr & 0xffff; + + // Check if the address is a start of segment within the executable + if (prog.p_type == 0x1u /* LOAD */ && seg_size && aligned_vaddr == addr && prog.p_vaddr == prog.p_paddr && vaddr_offs + seg_size <= size) + { + zeroes.resize(std::max({zeroes.size(), usz{addr + size - (vaddr + seg_size)}, usz{vaddr_offs}})); + + // Check if gaps between segment and allocation bounds are still zeroes-only + if (!std::memcmp(vm::_ptr(aligned_vaddr), zeroes.data(), vaddr_offs) && + !std::memcmp(vm::_ptr(vaddr + seg_size), zeroes.data(), (addr + size - (vaddr + seg_size)))) + { + // Test memory equality + return !std::memcmp(prog.bin.data(), vm::base(vaddr), seg_size); + } + } } return false; @@ -1516,15 +1553,10 @@ bool ppu_load_exec(const ppu_exec_object& elf, utils::serial* ar) return false; } - const bool already_loaded = ar && (_seg.flags & 0x2); + const bool already_loaded = ar && vm::check_addr(addr, vm::page_readable, size); if (already_loaded) { - if (!vm::check_addr(addr, vm::page_readable, size)) - { - ppu_loader.fatal("ppu_load_exec(): Archived PPU executable memory has not been found! (addr=0x%x, memsz=0x%x)", addr, size); - return false; - } } else if (!vm::falloc(addr, size, vm::main)) { @@ -1605,9 +1637,9 @@ bool ppu_load_exec(const ppu_exec_object& elf, utils::serial* ar) Emu.SetExecutableHash(hash); // Apply the patch - auto applied = g_fxo->get().apply(hash, vm::g_base_addr); + auto applied = g_fxo->get().apply(!ar ? hash : std::string{}, vm::g_base_addr); - if (!Emu.GetTitleID().empty()) + if (!ar && !Emu.GetTitleID().empty()) { // Alternative patch applied += g_fxo->get().apply(Emu.GetTitleID() + '-' + hash, vm::g_base_addr); @@ -2132,7 +2164,7 @@ std::pair, CellError> ppu_load_overlay(const ppu_ex if (prog.bin.size() > size || prog.bin.size() != prog.p_filesz) fmt::throw_exception("Invalid binary size (0x%llx, memsz=0x%x)", prog.bin.size(), size); - const bool already_loaded = ar /*&& !!(_seg.flags & 0x2)*/; + const bool already_loaded = true; // Unimplemented optimization for savestates if (already_loaded) { diff --git a/rpcs3/Emu/Memory/vm.cpp b/rpcs3/Emu/Memory/vm.cpp index 5892e8df1d..6aa7e62063 100644 --- a/rpcs3/Emu/Memory/vm.cpp +++ b/rpcs3/Emu/Memory/vm.cpp @@ -23,7 +23,7 @@ LOG_CHANNEL(vm_log, "VM"); void ppu_remove_hle_instructions(u32 addr, u32 size); -extern bool is_memory_read_only_of_executable(u32 addr); +extern bool is_memory_compatible_for_copy_from_executable_optimization(u32 addr, u32 size); namespace vm { @@ -1564,13 +1564,12 @@ namespace vm if (flags & preallocated) { - // Do not save read-only memory which comes from the executable - // Because it couldn't have changed - if (!(ar.data.back() & page_writable) && is_memory_read_only_of_executable(addr)) + // Do not save memory which matches the memory found in the executable (we can use it instead) + if (is_memory_compatible_for_copy_from_executable_optimization(addr, shm.first)) { // Revert changes ar.data.resize(ar.seek_end(sizeof(u32) * 2 + sizeof(memory_page))); - vm_log.success("Removed read-only memory block of the executable from savestate. (addr=0x%x, size=0x%x)", addr, shm.first); + vm_log.success("Removed memory block matching the memory of the executable from savestate. (addr=0x%x, size=0x%x)", addr, shm.first); continue; } @@ -2044,6 +2043,8 @@ namespace vm loc->save(ar, shared_map); } } + + is_memory_compatible_for_copy_from_executable_optimization(0, 0); // Cleanup internal data } void load(utils::serial& ar)