diff --git a/CMakeLists.txt b/CMakeLists.txt index 28ed1bd5dc..2208f2335e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ option(USE_SYSTEM_FFMPEG "Prefer system ffmpeg instead of the prebuild one" OFF) option(USE_SYSTEM_OPENAL "Prefer system OpenAL instead of the prebuild one" ON) option(USE_SYSTEM_CURL "Prefer system Curl instead of the prebuild one" ON) option(USE_SYSTEM_OPENCV "Prefer system OpenCV instead of the builtin one" ON) +option(HAS_MEMORY_BREAKPOINTS "Add support for memory breakpoints to the interpreter" OFF) option(USE_LTO "Use LTO for building" ON) set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/buildfiles/cmake") diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 562dd29476..e135e53ede 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -28,6 +28,10 @@ if(USE_ASAN) set_source_files_properties(../../Utilities/Thread.cpp PROPERTIES COMPILE_DEFINITIONS USE_ASAN) endif() +if(HAS_MEMORY_BREAKPOINTS) + target_compile_definitions(rpcs3_emu PRIVATE RPCS3_HAS_MEMORY_BREAKPOINTS) +endif() + target_link_libraries(rpcs3_emu PRIVATE 3rdparty::zlib 3rdparty::yaml-cpp 3rdparty::zstd diff --git a/rpcs3/Emu/Cell/PPUInterpreter.cpp b/rpcs3/Emu/Cell/PPUInterpreter.cpp index 9b7ad2ecba..690581acaa 100644 --- a/rpcs3/Emu/Cell/PPUInterpreter.cpp +++ b/rpcs3/Emu/Cell/PPUInterpreter.cpp @@ -26,6 +26,29 @@ #pragma GCC diagnostic ignored "-Wuninitialized" #endif +#ifdef RPCS3_HAS_MEMORY_BREAKPOINTS +void ppubreak(ppu_thread& ppu) +{ + if (!g_breakpoint_handler.IsBreakOnBPM()) + return; + + if (!ppu.state.test_and_set(cpu_flag::dbg_pause)) + { + ppu.check_state(); + } +} + +#define PPU_WRITE_8(addr, value) vm::write8(addr, value, &ppu); +#define PPU_WRITE_16(addr, value) vm::write16(addr, value, &ppu); +#define PPU_WRITE_32(addr, value) vm::write32(addr, value, &ppu); +#define PPU_WRITE_64(addr, value) vm::write64(addr, value, &ppu); +#else +#define PPU_WRITE_8(addr, value) vm::write8(addr, value); +#define PPU_WRITE_16(addr, value) vm::write16(addr, value); +#define PPU_WRITE_32(addr, value) vm::write32(addr, value); +#define PPU_WRITE_64(addr, value) vm::write64(addr, value); +#endif + extern bool is_debugger_present(); extern const ppu_decoder g_ppu_itype; @@ -428,6 +451,14 @@ auto ppu_feed_data(ppu_thread& ppu, u64 addr) auto value = vm::_ref(vm::cast(addr)); +#ifdef RPCS3_HAS_MEMORY_BREAKPOINTS + if (g_breakpoint_handler.HasBreakpoint(addr, breakpoint_types::bp_read)) + { + debugbp_log.success("BPMR: breakpoint reading 0x%x at 0x%x", value, addr); + ppubreak(ppu); + } +#endif + if constexpr (!((Flags == use_feed_data) || ...)) { return value; @@ -4274,7 +4305,7 @@ auto STVEBX() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = op.ra ? ppu.gpr[op.ra] + ppu.gpr[op.rb] : ppu.gpr[op.rb]; const u8 eb = addr & 0xf; - vm::write8(vm::cast(addr), ppu.vr[op.vs]._u8[15 - eb]); + PPU_WRITE_8(vm::cast(addr), ppu.vr[op.vs]._u8[15 - eb]); }; RETURN_(ppu, op); } @@ -4381,7 +4412,7 @@ auto STDX() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = op.ra ? ppu.gpr[op.ra] + ppu.gpr[op.rb] : ppu.gpr[op.rb]; - vm::write64(vm::cast(addr), ppu.gpr[op.rs]); + PPU_WRITE_64(vm::cast(addr), ppu.gpr[op.rs]); }; RETURN_(ppu, op); } @@ -4407,7 +4438,7 @@ auto STWX() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = op.ra ? ppu.gpr[op.ra] + ppu.gpr[op.rb] : ppu.gpr[op.rb]; - vm::write32(vm::cast(addr), static_cast(ppu.gpr[op.rs])); + PPU_WRITE_32(vm::cast(addr), static_cast(ppu.gpr[op.rs])); }; RETURN_(ppu, op); } @@ -4421,7 +4452,7 @@ auto STVEHX() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = (op.ra ? ppu.gpr[op.ra] + ppu.gpr[op.rb] : ppu.gpr[op.rb]) & ~1ULL; const u8 eb = (addr & 0xf) >> 1; - vm::write16(vm::cast(addr), ppu.vr[op.vs]._u16[7 - eb]); + PPU_WRITE_16(vm::cast(addr), ppu.vr[op.vs]._u16[7 - eb]); }; RETURN_(ppu, op); } @@ -4434,7 +4465,7 @@ auto STDUX() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = ppu.gpr[op.ra] + ppu.gpr[op.rb]; - vm::write64(vm::cast(addr), ppu.gpr[op.rs]); + PPU_WRITE_64(vm::cast(addr), ppu.gpr[op.rs]); ppu.gpr[op.ra] = addr; }; RETURN_(ppu, op); @@ -4448,7 +4479,7 @@ auto STWUX() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = ppu.gpr[op.ra] + ppu.gpr[op.rb]; - vm::write32(vm::cast(addr), static_cast(ppu.gpr[op.rs])); + PPU_WRITE_32(vm::cast(addr), static_cast(ppu.gpr[op.rs])); ppu.gpr[op.ra] = addr; }; RETURN_(ppu, op); @@ -4463,7 +4494,7 @@ auto STVEWX() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = (op.ra ? ppu.gpr[op.ra] + ppu.gpr[op.rb] : ppu.gpr[op.rb]) & ~3ULL; const u8 eb = (addr & 0xf) >> 2; - vm::write32(vm::cast(addr), ppu.vr[op.vs]._u32[3 - eb]); + PPU_WRITE_32(vm::cast(addr), ppu.vr[op.vs]._u32[3 - eb]); }; RETURN_(ppu, op); } @@ -4527,7 +4558,7 @@ auto STBX() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = op.ra ? ppu.gpr[op.ra] + ppu.gpr[op.rb] : ppu.gpr[op.rb]; - vm::write8(vm::cast(addr), static_cast(ppu.gpr[op.rs])); + PPU_WRITE_8(vm::cast(addr), static_cast(ppu.gpr[op.rs])); }; RETURN_(ppu, op); } @@ -4639,7 +4670,7 @@ auto STBUX() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = ppu.gpr[op.ra] + ppu.gpr[op.rb]; - vm::write8(vm::cast(addr), static_cast(ppu.gpr[op.rs])); + PPU_WRITE_8(vm::cast(addr), static_cast(ppu.gpr[op.rs])); ppu.gpr[op.ra] = addr; }; RETURN_(ppu, op); @@ -4883,7 +4914,7 @@ auto STHX() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = op.ra ? ppu.gpr[op.ra] + ppu.gpr[op.rb] : ppu.gpr[op.rb]; - vm::write16(vm::cast(addr), static_cast(ppu.gpr[op.rs])); + PPU_WRITE_16(vm::cast(addr), static_cast(ppu.gpr[op.rs])); }; RETURN_(ppu, op); } @@ -4922,7 +4953,7 @@ auto STHUX() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = ppu.gpr[op.ra] + ppu.gpr[op.rb]; - vm::write16(vm::cast(addr), static_cast(ppu.gpr[op.rs])); + PPU_WRITE_16(vm::cast(addr), static_cast(ppu.gpr[op.rs])); ppu.gpr[op.ra] = addr; }; RETURN_(ppu, op); @@ -5346,7 +5377,7 @@ auto STSWX() u32 count = ppu.xer.cnt & 0x7F; for (; count >= 4; count -= 4, addr += 4, op.rs = (op.rs + 1) & 31) { - vm::write32(vm::cast(addr), static_cast(ppu.gpr[op.rs])); + PPU_WRITE_32(vm::cast(addr), static_cast(ppu.gpr[op.rs])); } if (count) { @@ -5354,7 +5385,7 @@ auto STSWX() for (u32 byte = 0; byte < count; byte++) { u8 byte_value = static_cast(value >> ((3 ^ byte) * 8)); - vm::write8(vm::cast(addr + byte), byte_value); + PPU_WRITE_8(vm::cast(addr + byte), byte_value); } } }; @@ -5434,7 +5465,7 @@ auto STSWI() { if (N > 3) { - vm::write32(vm::cast(addr), static_cast(ppu.gpr[reg])); + PPU_WRITE_32(vm::cast(addr), static_cast(ppu.gpr[reg])); addr += 4; N -= 4; } @@ -5444,7 +5475,7 @@ auto STSWI() while (N > 0) { N = N - 1; - vm::write8(vm::cast(addr), (0xFF000000 & buf) >> 24); + PPU_WRITE_8(vm::cast(addr), (0xFF000000 & buf) >> 24); buf <<= 8; addr++; } @@ -5678,7 +5709,7 @@ auto STFIWX() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = op.ra ? ppu.gpr[op.ra] + ppu.gpr[op.rb] : ppu.gpr[op.rb]; - vm::write32(vm::cast(addr), static_cast(std::bit_cast(ppu.fpr[op.frs]))); + PPU_WRITE_32(vm::cast(addr), static_cast(std::bit_cast(ppu.fpr[op.frs]))); }; RETURN_(ppu, op); } @@ -5793,7 +5824,7 @@ auto STW() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = op.ra || 1 ? ppu.gpr[op.ra] + op.simm16 : op.simm16; const u32 value = static_cast(ppu.gpr[op.rs]); - vm::write32(vm::cast(addr), value); + PPU_WRITE_32(vm::cast(addr), value); //Insomniac engine v3 & v4 (newer R&C, Fuse, Resitance 3) if (value == 0xAAAAAAAA) [[unlikely]] @@ -5813,7 +5844,7 @@ auto STWU() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = ppu.gpr[op.ra] + op.simm16; - vm::write32(vm::cast(addr), static_cast(ppu.gpr[op.rs])); + PPU_WRITE_32(vm::cast(addr), static_cast(ppu.gpr[op.rs])); ppu.gpr[op.ra] = addr; }; RETURN_(ppu, op); @@ -5827,7 +5858,7 @@ auto STB() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = op.ra || 1 ? ppu.gpr[op.ra] + op.simm16 : op.simm16; - vm::write8(vm::cast(addr), static_cast(ppu.gpr[op.rs])); + PPU_WRITE_8(vm::cast(addr), static_cast(ppu.gpr[op.rs])); }; RETURN_(ppu, op); } @@ -5840,7 +5871,7 @@ auto STBU() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = ppu.gpr[op.ra] + op.simm16; - vm::write8(vm::cast(addr), static_cast(ppu.gpr[op.rs])); + PPU_WRITE_8(vm::cast(addr), static_cast(ppu.gpr[op.rs])); ppu.gpr[op.ra] = addr; }; RETURN_(ppu, op); @@ -5908,7 +5939,7 @@ auto STH() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = op.ra || 1 ? ppu.gpr[op.ra] + op.simm16 : op.simm16; - vm::write16(vm::cast(addr), static_cast(ppu.gpr[op.rs])); + PPU_WRITE_16(vm::cast(addr), static_cast(ppu.gpr[op.rs])); }; RETURN_(ppu, op); } @@ -5921,7 +5952,7 @@ auto STHU() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = ppu.gpr[op.ra] + op.simm16; - vm::write16(vm::cast(addr), static_cast(ppu.gpr[op.rs])); + PPU_WRITE_16(vm::cast(addr), static_cast(ppu.gpr[op.rs])); ppu.gpr[op.ra] = addr; }; RETURN_(ppu, op); @@ -5953,7 +5984,7 @@ auto STMW() u64 addr = op.ra ? ppu.gpr[op.ra] + op.simm16 : op.simm16; for (u32 i = op.rs; i<32; ++i, addr += 4) { - vm::write32(vm::cast(addr), static_cast(ppu.gpr[i])); + PPU_WRITE_32(vm::cast(addr), static_cast(ppu.gpr[i])); } }; RETURN_(ppu, op); @@ -6115,7 +6146,7 @@ auto STD() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = (op.simm16 & ~3) + (op.ra ? ppu.gpr[op.ra] : 0); - vm::write64(vm::cast(addr), ppu.gpr[op.rs]); + PPU_WRITE_64(vm::cast(addr), ppu.gpr[op.rs]); }; RETURN_(ppu, op); } @@ -6128,7 +6159,7 @@ auto STDU() static const auto exec = [](ppu_thread& ppu, ppu_opcode_t op) { const u64 addr = ppu.gpr[op.ra] + (op.simm16 & ~3); - vm::write64(vm::cast(addr), ppu.gpr[op.rs]); + PPU_WRITE_64(vm::cast(addr), ppu.gpr[op.rs]); ppu.gpr[op.ra] = addr; }; RETURN_(ppu, op); diff --git a/rpcs3/Emu/Memory/vm.h b/rpcs3/Emu/Memory/vm.h index 9680187cd4..915791e2d6 100644 --- a/rpcs3/Emu/Memory/vm.h +++ b/rpcs3/Emu/Memory/vm.h @@ -8,6 +8,17 @@ #include "util/to_endian.hpp" +#ifdef RPCS3_HAS_MEMORY_BREAKPOINTS +#include "rpcs3qt/breakpoint_handler.h" +#include "util/logs.hpp" + +LOG_CHANNEL(debugbp_log, "DebugBP"); + +class ppu_thread; + +void ppubreak(ppu_thread& ppu); +#endif + namespace utils { class shm; @@ -242,9 +253,21 @@ namespace vm return g_base_addr[addr]; } +#ifdef RPCS3_HAS_MEMORY_BREAKPOINTS + inline void write8(u32 addr, u8 value, ppu_thread* ppu = nullptr) +#else inline void write8(u32 addr, u8 value) +#endif { g_base_addr[addr] = value; + +#ifdef RPCS3_HAS_MEMORY_BREAKPOINTS + if (ppu && g_breakpoint_handler.HasBreakpoint(addr, breakpoint_types::bp_write)) + { + debugbp_log.success("BPMW: breakpoint writing(8) 0x%x at 0x%x", value, addr); + ppubreak(*ppu); + } +#endif } // Read or write virtual memory in a safe manner, returns false on failure @@ -276,9 +299,21 @@ namespace vm return _ref(addr); } +#ifdef RPCS3_HAS_MEMORY_BREAKPOINTS + inline void write16(u32 addr, be_t value, ppu_thread* ppu = nullptr) +#else inline void write16(u32 addr, be_t value) +#endif { _ref(addr) = value; + +#ifdef RPCS3_HAS_MEMORY_BREAKPOINTS + if (ppu && g_breakpoint_handler.HasBreakpoint(addr, breakpoint_types::bp_write)) + { + debugbp_log.success("BPMW: breakpoint writing(16) 0x%x at 0x%x", value, addr); + ppubreak(*ppu); + } +#endif } inline const be_t& read32(u32 addr) @@ -286,9 +321,21 @@ namespace vm return _ref(addr); } +#ifdef RPCS3_HAS_MEMORY_BREAKPOINTS + inline void write32(u32 addr, be_t value, ppu_thread* ppu = nullptr) +#else inline void write32(u32 addr, be_t value) +#endif { _ref(addr) = value; + +#ifdef RPCS3_HAS_MEMORY_BREAKPOINTS + if (ppu && g_breakpoint_handler.HasBreakpoint(addr, breakpoint_types::bp_write)) + { + debugbp_log.success("BPMW: breakpoint writing(32) 0x%x at 0x%x", value, addr); + ppubreak(*ppu); + } +#endif } inline const be_t& read64(u32 addr) @@ -296,9 +343,21 @@ namespace vm return _ref(addr); } +#ifdef RPCS3_HAS_MEMORY_BREAKPOINTS + inline void write64(u32 addr, be_t value, ppu_thread* ppu = nullptr) +#else inline void write64(u32 addr, be_t value) +#endif { _ref(addr) = value; + +#ifdef RPCS3_HAS_MEMORY_BREAKPOINTS + if (ppu && g_breakpoint_handler.HasBreakpoint(addr, breakpoint_types::bp_write)) + { + debugbp_log.success("BPMW: breakpoint writing(64) 0x%x at 0x%x", value, addr); + ppubreak(*ppu); + } +#endif } void init(); diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index cbc0111029..ae17a264c2 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -232,6 +232,9 @@ true + + true + true @@ -511,6 +514,9 @@ true + + true + true @@ -845,6 +851,7 @@ + @@ -960,6 +967,16 @@ $(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath) $(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath) + + Moc%27ing debugger_add_bp_window.h... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" + Moc%27ing debugger_add_bp_window.h... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" + $(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath) + $(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing debugger_frame.h... .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index bc2110067b..3b0890fe49 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -213,6 +213,12 @@ Generated Files\Release + + Generated Files\Debug + + + Generated Files\Release + Generated Files\Debug @@ -396,6 +402,9 @@ Gui\dev tools + + Gui\debugger + Gui\debugger diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index 29531499a7..d4442d91cd 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -13,6 +13,7 @@ add_library(rpcs3_ui STATIC curl_handle.cpp custom_dialog.cpp custom_table_widget_item.cpp + debugger_add_bp_window.cpp debugger_frame.cpp debugger_list.cpp downloader.cpp @@ -128,6 +129,10 @@ add_library(rpcs3_ui STATIC "../resources.qrc" ) +if(HAS_MEMORY_BREAKPOINTS) + target_compile_definitions(rpcs3_ui PRIVATE RPCS3_HAS_MEMORY_BREAKPOINTS) +endif() + if(WIN32) target_sources(rpcs3_ui PUBLIC "../windows.qrc") target_compile_definitions(rpcs3_ui PRIVATE UNICODE _UNICODE) diff --git a/rpcs3/rpcs3qt/breakpoint_handler.cpp b/rpcs3/rpcs3qt/breakpoint_handler.cpp index b65525a72e..d2c4c44bae 100644 --- a/rpcs3/rpcs3qt/breakpoint_handler.cpp +++ b/rpcs3/rpcs3qt/breakpoint_handler.cpp @@ -2,29 +2,53 @@ extern bool ppu_breakpoint(u32 loc, bool is_adding); -bool breakpoint_handler::HasBreakpoint(u32 loc) const +bool breakpoint_handler::IsBreakOnBPM() const { - return m_breakpoints.contains(loc); + return m_break_on_bpm; } -bool breakpoint_handler::AddBreakpoint(u32 loc) +void breakpoint_handler::SetBreakOnBPM(bool break_on_bpm) { - if (!ppu_breakpoint(loc, true)) + m_break_on_bpm = break_on_bpm; +} + +bool breakpoint_handler::HasBreakpoint(u32 loc, bs_t type) +{ + std::lock_guard lock(mutex_breakpoints); + + return m_breakpoints.contains(loc) && ((m_breakpoints.at(loc) & type) == type); +} + +bool breakpoint_handler::AddBreakpoint(u32 loc, bs_t type) +{ + std::lock_guard lock(mutex_breakpoints); + + if ((type & breakpoint_types::bp_exec) && !ppu_breakpoint(loc, true)) { return false; } - ensure(m_breakpoints.insert(loc).second); - return true; + return m_breakpoints.insert({loc, type}).second; } bool breakpoint_handler::RemoveBreakpoint(u32 loc) { + std::lock_guard lock(mutex_breakpoints); + + bs_t bp_type{}; + if (m_breakpoints.contains(loc)) + { + bp_type = m_breakpoints.at(loc); + } + if (m_breakpoints.erase(loc) == 0) { return false; } - ensure(ppu_breakpoint(loc, false)); + if (bp_type & breakpoint_types::bp_exec) + { + ensure(ppu_breakpoint(loc, false)); + } return true; } diff --git a/rpcs3/rpcs3qt/breakpoint_handler.h b/rpcs3/rpcs3qt/breakpoint_handler.h index 4794fd1d83..f27f8031c2 100644 --- a/rpcs3/rpcs3qt/breakpoint_handler.h +++ b/rpcs3/rpcs3qt/breakpoint_handler.h @@ -1,13 +1,16 @@ #pragma once #include "util/types.hpp" -#include +#include "Utilities/bit_set.h" +#include +#include "Utilities/mutex.h" enum class breakpoint_types { bp_read = 0x1, bp_write = 0x2, bp_exec = 0x4, + __bitset_enum_max }; /* @@ -20,16 +23,19 @@ public: breakpoint_handler() = default; ~breakpoint_handler() = default; + bool IsBreakOnBPM() const; + void SetBreakOnBPM(bool break_on_bpm); + /** * Returns true iff breakpoint exists at loc. * TODO: Add arg for flags, gameid, and maybe even thread if it should be thread local breakpoint.... breakpoint struct is probably what'll happen */ - bool HasBreakpoint(u32 loc) const; + bool HasBreakpoint(u32 loc, bs_t type); /** * Returns true if added successfully. TODO: flags */ - bool AddBreakpoint(u32 loc); + bool AddBreakpoint(u32 loc, bs_t type); /** * Returns true if removed breakpoint at loc successfully. @@ -39,5 +45,9 @@ public: private: // TODO : generalize to hold multiple games and handle flags.Probably do : std::map>. // Although, externally, they'll only be accessed by loc (I think) so a map of maps may also do? - std::set m_breakpoints; //! Holds all breakpoints. + shared_mutex mutex_breakpoints; + std::map> m_breakpoints; //! Holds all breakpoints. + bool m_break_on_bpm = false; }; + +extern breakpoint_handler g_breakpoint_handler; diff --git a/rpcs3/rpcs3qt/breakpoint_list.cpp b/rpcs3/rpcs3qt/breakpoint_list.cpp index 03fc4105ea..63e7756c9a 100644 --- a/rpcs3/rpcs3qt/breakpoint_list.cpp +++ b/rpcs3/rpcs3qt/breakpoint_list.cpp @@ -4,6 +4,7 @@ #include "Emu/CPU/CPUDisAsm.h" #include "Emu/Cell/PPUThread.h" #include "Emu/Cell/SPUThread.h" +#include "rpcs3qt/debugger_add_bp_window.h" #include #include @@ -72,19 +73,35 @@ void breakpoint_list::RemoveBreakpoint(u32 addr) } } -bool breakpoint_list::AddBreakpoint(u32 pc) +bool breakpoint_list::AddBreakpoint(u32 pc, bs_t type) { - if (!m_ppu_breakpoint_handler->AddBreakpoint(pc)) + if (!m_ppu_breakpoint_handler->AddBreakpoint(pc, type)) { return false; } - m_disasm->disasm(pc); + QString breakpoint_item_text; - QString text = QString::fromStdString(m_disasm->last_opcode); - text.remove(10, 13); + if (type == breakpoint_types::bp_exec) + { + m_disasm->disasm(m_disasm->dump_pc = pc); + breakpoint_item_text = QString::fromStdString(m_disasm->last_opcode); + breakpoint_item_text.remove(10, 13); + } + else if (type == breakpoint_types::bp_read) + { + breakpoint_item_text = QString("BPMR: 0x%1").arg(pc, 8, 16, QChar('0')); + } + else if (type == breakpoint_types::bp_write) + { + breakpoint_item_text = QString("BPMW: 0x%1").arg(pc, 8, 16, QChar('0')); + } + else if (type == (breakpoint_types::bp_read + breakpoint_types::bp_write)) + { + breakpoint_item_text = QString("BPMRW: 0x%1").arg(pc, 8, 16, QChar('0')); + } - QListWidgetItem* breakpoint_item = new QListWidgetItem(text); + QListWidgetItem* breakpoint_item = new QListWidgetItem(breakpoint_item_text); breakpoint_item->setForeground(m_text_color_bp); breakpoint_item->setBackground(m_color_bp); breakpoint_item->setData(Qt::UserRole, pc); @@ -96,8 +113,8 @@ bool breakpoint_list::AddBreakpoint(u32 pc) } /** -* If breakpoint exists, we remove it, else add new one. Yeah, it'd be nicer from a code logic to have it be set/reset. But, that logic has to happen somewhere anyhow. -*/ + * If breakpoint exists, we remove it, else add new one. Yeah, it'd be nicer from a code logic to have it be set/reset. But, that logic has to happen somewhere anyhow. + */ void breakpoint_list::HandleBreakpointRequest(u32 loc, bool only_add) { const auto cpu = m_disasm ? m_disasm->get_cpu() : nullptr; @@ -168,7 +185,7 @@ void breakpoint_list::HandleBreakpointRequest(u32 loc, bool only_add) return; } - if (m_ppu_breakpoint_handler->HasBreakpoint(loc)) + if (m_ppu_breakpoint_handler->HasBreakpoint(loc, breakpoint_types::bp_exec)) { if (!only_add) { @@ -177,7 +194,7 @@ void breakpoint_list::HandleBreakpointRequest(u32 loc, bool only_add) } else { - if (!AddBreakpoint(loc)) + if (!AddBreakpoint(loc, breakpoint_types::bp_exec)) { QMessageBox::warning(this, tr("Unknown error while setting breakpoint!"), tr("Failed to set breakpoints.")); return; @@ -194,13 +211,8 @@ void breakpoint_list::OnBreakpointListDoubleClicked() } } -void breakpoint_list::OnBreakpointListRightClicked(const QPoint &pos) +void breakpoint_list::OnBreakpointListRightClicked(const QPoint& pos) { - if (!itemAt(pos)) - { - return; - } - m_context_menu = new QMenu(); if (selectedItems().count() == 1) @@ -215,7 +227,28 @@ void breakpoint_list::OnBreakpointListRightClicked(const QPoint &pos) m_context_menu->addSeparator(); } - m_context_menu->addAction(m_delete_action); + if (selectedItems().count() >= 1) + { + m_context_menu->addAction(m_delete_action); + } + + QAction* m_addbp = new QAction(tr("Add Breakpoint"), this); + connect(m_addbp, &QAction::triggered, this, [this] + { + debugger_add_bp_window dlg(this, this); + dlg.exec(); + }); + m_context_menu->addAction(m_addbp); + +#ifdef RPCS3_HAS_MEMORY_BREAKPOINTS + QAction* m_tglbpmbreak = new QAction(m_ppu_breakpoint_handler->IsBreakOnBPM() ? tr("Disable BPM") : tr("Enable BPM"), this); + connect(m_tglbpmbreak, &QAction::triggered, [this] + { + m_ppu_breakpoint_handler->SetBreakOnBPM(!m_ppu_breakpoint_handler->IsBreakOnBPM()); + }); + m_context_menu->addAction(m_tglbpmbreak); +#endif + m_context_menu->exec(viewport()->mapToGlobal(pos)); m_context_menu->deleteLater(); m_context_menu = nullptr; diff --git a/rpcs3/rpcs3qt/breakpoint_list.h b/rpcs3/rpcs3qt/breakpoint_list.h index 5481e06efc..15d87857eb 100644 --- a/rpcs3/rpcs3qt/breakpoint_list.h +++ b/rpcs3/rpcs3qt/breakpoint_list.h @@ -4,9 +4,10 @@ #include +#include "breakpoint_handler.h" + class CPUDisAsm; class cpu_thread; -class breakpoint_handler; class breakpoint_list : public QListWidget { @@ -16,8 +17,8 @@ public: breakpoint_list(QWidget* parent, breakpoint_handler* handler); void UpdateCPUData(std::shared_ptr disasm); void ClearBreakpoints(); - bool AddBreakpoint(u32 pc); void RemoveBreakpoint(u32 addr); + bool AddBreakpoint(u32 pc, bs_t type); QColor m_text_color_bp; QColor m_color_bp; @@ -36,7 +37,7 @@ private Q_SLOTS: void OnBreakpointListDelete(); private: - breakpoint_handler* m_ppu_breakpoint_handler; + breakpoint_handler* m_ppu_breakpoint_handler = nullptr; QMenu* m_context_menu = nullptr; QAction* m_delete_action; std::shared_ptr m_disasm = nullptr; diff --git a/rpcs3/rpcs3qt/debugger_add_bp_window.cpp b/rpcs3/rpcs3qt/debugger_add_bp_window.cpp new file mode 100644 index 0000000000..64255ecc50 --- /dev/null +++ b/rpcs3/rpcs3qt/debugger_add_bp_window.cpp @@ -0,0 +1,116 @@ +#include "debugger_add_bp_window.h" +#include "Utilities/StrFmt.h" +#include "Utilities/StrUtil.h" +#include "breakpoint_handler.h" +#include "util/types.hpp" + +#include +#include +#include +#include +#include +#include +#include + +debugger_add_bp_window::debugger_add_bp_window(breakpoint_list* bp_list, QWidget* parent) + : QDialog(parent) +{ + ensure(bp_list); + + setWindowTitle(tr("Add a breakpoint")); + setModal(true); + + QVBoxLayout* vbox_panel = new QVBoxLayout(); + + QHBoxLayout* hbox_top = new QHBoxLayout(); + QLabel* l_address = new QLabel(tr("Address")); + QLineEdit* t_address = new QLineEdit(); + t_address->setPlaceholderText(tr("Address here")); + t_address->setFocus(); + + hbox_top->addWidget(l_address); + hbox_top->addWidget(t_address); + vbox_panel->addLayout(hbox_top); + + QHBoxLayout* hbox_bot = new QHBoxLayout(); + QComboBox* co_bptype = new QComboBox(this); + QStringList qstr_breakpoint_types; + + qstr_breakpoint_types +#ifdef RPCS3_HAS_MEMORY_BREAKPOINTS + << tr("Memory Read") + << tr("Memory Write") + << tr("Memory Read&Write") +#endif + << tr("Execution"); + + co_bptype->addItems(qstr_breakpoint_types); + + hbox_bot->addWidget(co_bptype); + vbox_panel->addLayout(hbox_bot); + + QHBoxLayout* hbox_buttons = new QHBoxLayout(); + QDialogButtonBox* button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + button_box->button(QDialogButtonBox::Ok)->setText(tr("Add")); + + hbox_buttons->addWidget(button_box); + vbox_panel->addLayout(hbox_buttons); + + setLayout(vbox_panel); + + connect(button_box, &QDialogButtonBox::accepted, this, [=, this] + { + const std::string str_address = t_address->text().toStdString(); + + if (str_address.empty()) + { + QMessageBox::warning(this, tr("Add BP error"), tr("Address is empty!")); + return; + } + + // We always want hex + const std::string parsed_string = (!str_address.starts_with("0x") && !str_address.starts_with("0X")) ? fmt::format("0x%s", str_address) : str_address; + u64 parsed_address = 0; + + // We don't accept 0 + if (!try_to_uint64(&parsed_address, parsed_string, 1, 0xFF'FF'FF'FF)) + { + QMessageBox::warning(this, tr("Add BP error"), tr("Address is invalid!")); + return; + } + + const u32 address = static_cast(parsed_address); + bs_t bp_t{}; + +#ifdef RPCS3_HAS_MEMORY_BREAKPOINTS + switch (co_bptype->currentIndex()) + { + case 0: + bp_t = breakpoint_types::bp_read; + break; + case 1: + bp_t = breakpoint_types::bp_write; + break; + case 2: + bp_t = breakpoint_types::bp_read + breakpoint_types::bp_write; + break; + case 3: + bp_t = breakpoint_types::bp_exec; + break; + default: + break; + } +#else + bp_t = breakpoint_types::bp_exec; +#endif + + if (bp_t) + bp_list->AddBreakpoint(address, bp_t); + + QDialog::accept(); + }); + + connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + + move(QCursor::pos()); +} diff --git a/rpcs3/rpcs3qt/debugger_add_bp_window.h b/rpcs3/rpcs3qt/debugger_add_bp_window.h new file mode 100644 index 0000000000..c57c6873d8 --- /dev/null +++ b/rpcs3/rpcs3qt/debugger_add_bp_window.h @@ -0,0 +1,13 @@ +#pragma once + +#include "breakpoint_list.h" + +#include + +class debugger_add_bp_window : public QDialog +{ + Q_OBJECT + +public: + explicit debugger_add_bp_window(breakpoint_list* bp_list, QWidget* parent = nullptr); +}; diff --git a/rpcs3/rpcs3qt/debugger_frame.cpp b/rpcs3/rpcs3qt/debugger_frame.cpp index b62efcc716..606b491bf8 100644 --- a/rpcs3/rpcs3qt/debugger_frame.cpp +++ b/rpcs3/rpcs3qt/debugger_frame.cpp @@ -34,6 +34,7 @@ #include #include +#include "rpcs3qt/debugger_add_bp_window.h" #include "util/asm.hpp" constexpr auto qstr = QString::fromStdString; @@ -43,6 +44,9 @@ constexpr auto s_pause_flags = cpu_flag::dbg_pause + cpu_flag::dbg_global_pause; extern atomic_t g_debugger_pause_all_threads_on_bp; extern const ppu_decoder g_ppu_itype; +#ifdef RPCS3_HAS_MEMORY_BREAKPOINTS +breakpoint_handler g_breakpoint_handler = breakpoint_handler(); +#endif extern bool is_using_interpreter(thread_class t_class); @@ -66,7 +70,12 @@ debugger_frame::debugger_frame(std::shared_ptr gui_settings, QWidg QHBoxLayout* hbox_b_main = new QHBoxLayout(); hbox_b_main->setContentsMargins(0, 0, 0, 0); +#ifdef RPCS3_HAS_MEMORY_BREAKPOINTS + m_ppu_breakpoint_handler = &g_breakpoint_handler; +#else m_ppu_breakpoint_handler = new breakpoint_handler(); +#endif + m_breakpoint_list = new breakpoint_list(this, m_ppu_breakpoint_handler); m_debugger_list = new debugger_list(this, m_gui_settings, m_ppu_breakpoint_handler); @@ -90,6 +99,7 @@ debugger_frame::debugger_frame(std::shared_ptr gui_settings, QWidg m_go_to_pc = new QPushButton(tr("Go To PC"), this); m_btn_step = new QPushButton(tr("Step"), this); m_btn_step_over = new QPushButton(tr("Step Over"), this); + m_btn_add_bp = new QPushButton(tr("Add BP"), this); m_btn_run = new QPushButton(RunString, this); EnableButtons(false); @@ -100,6 +110,7 @@ debugger_frame::debugger_frame(std::shared_ptr gui_settings, QWidg hbox_b_main->addWidget(m_go_to_pc); hbox_b_main->addWidget(m_btn_step); hbox_b_main->addWidget(m_btn_step_over); + hbox_b_main->addWidget(m_btn_add_bp); hbox_b_main->addWidget(m_btn_run); hbox_b_main->addWidget(m_choice_units); hbox_b_main->addStretch(); @@ -152,6 +163,12 @@ debugger_frame::debugger_frame(std::shared_ptr gui_settings, QWidg connect(m_btn_step, &QAbstractButton::clicked, this, &debugger_frame::DoStep); connect(m_btn_step_over, &QAbstractButton::clicked, [this]() { DoStep(true); }); + connect(m_btn_add_bp, &QAbstractButton::clicked, this, [this] + { + debugger_add_bp_window dlg(m_breakpoint_list, this); + dlg.exec(); + }); + connect(m_btn_run, &QAbstractButton::clicked, this, &debugger_frame::RunBtnPress); connect(m_choice_units->lineEdit(), &QLineEdit::editingFinished, [&] @@ -1588,7 +1605,7 @@ void debugger_frame::DoStep(bool step_over) // Set breakpoint on next instruction const u32 next_instruction_pc = current_instruction_pc + 4; - m_ppu_breakpoint_handler->AddBreakpoint(next_instruction_pc); + m_ppu_breakpoint_handler->AddBreakpoint(next_instruction_pc, breakpoint_types::bp_exec); // Undefine previous step over breakpoint if it hasn't been already // This can happen when the user steps over a branch that doesn't return to itself @@ -1680,6 +1697,7 @@ void debugger_frame::EnableButtons(bool enable) m_go_to_addr->setEnabled(enable); m_go_to_pc->setEnabled(enable); + m_btn_add_bp->setEnabled(enable); m_btn_step->setEnabled(step); m_btn_step_over->setEnabled(step); m_btn_run->setEnabled(enable); diff --git a/rpcs3/rpcs3qt/debugger_frame.h b/rpcs3/rpcs3qt/debugger_frame.h index ee685231c5..46df22e3c8 100644 --- a/rpcs3/rpcs3qt/debugger_frame.h +++ b/rpcs3/rpcs3qt/debugger_frame.h @@ -46,20 +46,21 @@ class debugger_frame : public custom_dock_widget const QString RunString = tr("Run"); const QString PauseString = tr("Pause"); - debugger_list* m_debugger_list; - QSplitter* m_right_splitter; + debugger_list* m_debugger_list = nullptr; + QSplitter* m_right_splitter = nullptr; QFont m_mono; - QPlainTextEdit* m_misc_state; - QPlainTextEdit* m_regs; - QPushButton* m_go_to_addr; - QPushButton* m_go_to_pc; - QPushButton* m_btn_step; - QPushButton* m_btn_step_over; - QPushButton* m_btn_run; + QPlainTextEdit* m_misc_state = nullptr; + QPlainTextEdit* m_regs = nullptr; + QPushButton* m_go_to_addr = nullptr; + QPushButton* m_go_to_pc = nullptr; + QPushButton* m_btn_step = nullptr; + QPushButton* m_btn_step_over = nullptr; + QPushButton* m_btn_add_bp = nullptr; + QPushButton* m_btn_run = nullptr; - QComboBox* m_choice_units; - QTimer* m_update; - QSplitter* m_splitter; + QComboBox* m_choice_units = nullptr; + QTimer* m_update = nullptr; + QSplitter* m_splitter = nullptr; u64 m_threads_created = -1; u64 m_threads_deleted = -1; @@ -83,9 +84,9 @@ class debugger_frame : public custom_dock_widget u32 m_spu_disasm_pc = 0; bool m_is_spu_disasm_mode = false; - breakpoint_list* m_breakpoint_list; - breakpoint_handler* m_ppu_breakpoint_handler; - call_stack_list* m_call_stack_list; + breakpoint_list* m_breakpoint_list = nullptr; + breakpoint_handler* m_ppu_breakpoint_handler = nullptr; + call_stack_list* m_call_stack_list = nullptr; instruction_editor_dialog* m_inst_editor = nullptr; register_editor_dialog* m_reg_editor = nullptr; QDialog* m_goto_dialog = nullptr; diff --git a/rpcs3/rpcs3qt/debugger_list.cpp b/rpcs3/rpcs3qt/debugger_list.cpp index d1e0f97677..e398921aaf 100644 --- a/rpcs3/rpcs3qt/debugger_list.cpp +++ b/rpcs3/rpcs3qt/debugger_list.cpp @@ -157,7 +157,7 @@ void debugger_list::ShowAddress(u32 addr, bool select_addr, bool direct) { case thread_class::ppu: { - return m_ppu_breakpoint_handler->HasBreakpoint(pc); + return m_ppu_breakpoint_handler->HasBreakpoint(pc, breakpoint_types::bp_exec); } case thread_class::spu: { diff --git a/rpcs3/rpcs3qt/debugger_list.h b/rpcs3/rpcs3qt/debugger_list.h index ebae5bcc13..e0429db884 100644 --- a/rpcs3/rpcs3qt/debugger_list.h +++ b/rpcs3/rpcs3qt/debugger_list.h @@ -56,7 +56,7 @@ private: std::shared_ptr m_gui_settings; - breakpoint_handler* m_ppu_breakpoint_handler; + breakpoint_handler* m_ppu_breakpoint_handler = nullptr; cpu_thread* m_cpu = nullptr; std::shared_ptr m_disasm; QDialog* m_cmd_detail = nullptr;