Add memory breakpoints

RPCS3 needs to be compiled with -DHAS_MEMORY_BREAKPOINTS=ON for it to be available
This commit is contained in:
RipleyTom 2025-03-01 17:08:10 +01:00
parent 783079266e
commit eb9dbfa31a
No known key found for this signature in database
GPG key ID: FC2B5DEF76BF4CC8
17 changed files with 416 additions and 74 deletions

View file

@ -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")

View file

@ -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

View file

@ -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<ppu_itype> g_ppu_itype;
@ -428,6 +451,14 @@ auto ppu_feed_data(ppu_thread& ppu, u64 addr)
auto value = vm::_ref<T>(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<u32>(ppu.gpr[op.rs]));
PPU_WRITE_32(vm::cast(addr), static_cast<u32>(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<u32>(ppu.gpr[op.rs]));
PPU_WRITE_32(vm::cast(addr), static_cast<u32>(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<u8>(ppu.gpr[op.rs]));
PPU_WRITE_8(vm::cast(addr), static_cast<u8>(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<u8>(ppu.gpr[op.rs]));
PPU_WRITE_8(vm::cast(addr), static_cast<u8>(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<u16>(ppu.gpr[op.rs]));
PPU_WRITE_16(vm::cast(addr), static_cast<u16>(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<u16>(ppu.gpr[op.rs]));
PPU_WRITE_16(vm::cast(addr), static_cast<u16>(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<u32>(ppu.gpr[op.rs]));
PPU_WRITE_32(vm::cast(addr), static_cast<u32>(ppu.gpr[op.rs]));
}
if (count)
{
@ -5354,7 +5385,7 @@ auto STSWX()
for (u32 byte = 0; byte < count; byte++)
{
u8 byte_value = static_cast<u8>(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<u32>(ppu.gpr[reg]));
PPU_WRITE_32(vm::cast(addr), static_cast<u32>(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<u32>(std::bit_cast<u64>(ppu.fpr[op.frs])));
PPU_WRITE_32(vm::cast(addr), static_cast<u32>(std::bit_cast<u64>(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<u32>(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<u32>(ppu.gpr[op.rs]));
PPU_WRITE_32(vm::cast(addr), static_cast<u32>(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<u8>(ppu.gpr[op.rs]));
PPU_WRITE_8(vm::cast(addr), static_cast<u8>(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<u8>(ppu.gpr[op.rs]));
PPU_WRITE_8(vm::cast(addr), static_cast<u8>(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<u16>(ppu.gpr[op.rs]));
PPU_WRITE_16(vm::cast(addr), static_cast<u16>(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<u16>(ppu.gpr[op.rs]));
PPU_WRITE_16(vm::cast(addr), static_cast<u16>(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<u32>(ppu.gpr[i]));
PPU_WRITE_32(vm::cast(addr), static_cast<u32>(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);

View file

@ -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<u16>(addr);
}
#ifdef RPCS3_HAS_MEMORY_BREAKPOINTS
inline void write16(u32 addr, be_t<u16> value, ppu_thread* ppu = nullptr)
#else
inline void write16(u32 addr, be_t<u16> value)
#endif
{
_ref<u16>(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<u32>& read32(u32 addr)
@ -286,9 +321,21 @@ namespace vm
return _ref<u32>(addr);
}
#ifdef RPCS3_HAS_MEMORY_BREAKPOINTS
inline void write32(u32 addr, be_t<u32> value, ppu_thread* ppu = nullptr)
#else
inline void write32(u32 addr, be_t<u32> value)
#endif
{
_ref<u32>(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<u64>& read64(u32 addr)
@ -296,9 +343,21 @@ namespace vm
return _ref<u64>(addr);
}
#ifdef RPCS3_HAS_MEMORY_BREAKPOINTS
inline void write64(u32 addr, be_t<u64> value, ppu_thread* ppu = nullptr)
#else
inline void write64(u32 addr, be_t<u64> value)
#endif
{
_ref<u64>(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();

View file

@ -232,6 +232,9 @@
<ClCompile Include="QTGeneratedFiles\Debug\moc_custom_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_debugger_add_bp_window.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_debugger_frame.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
@ -511,6 +514,9 @@
<ClCompile Include="QTGeneratedFiles\Release\moc_custom_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_debugger_add_bp_window.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_debugger_frame.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
@ -845,6 +851,7 @@
<ClCompile Include="headless_application.cpp" />
<ClCompile Include="rpcs3qt\auto_pause_settings_dialog.cpp" />
<ClCompile Include="rpcs3qt\cg_disasm_window.cpp" />
<ClCompile Include="rpcs3qt\debugger_add_bp_window.cpp" />
<ClCompile Include="rpcs3qt\debugger_frame.cpp" />
<ClCompile Include="rpcs3qt\emu_settings.cpp" />
<ClCompile Include="rpcs3qt\game_list_frame.cpp" />
@ -960,6 +967,16 @@
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
</CustomBuild>
<CustomBuild Include="rpcs3qt\debugger_add_bp_window.h">
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing debugger_add_bp_window.h...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(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"</Command>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Moc%27ing debugger_add_bp_window.h...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(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"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
</CustomBuild>
<CustomBuild Include="rpcs3qt\debugger_frame.h">
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing debugger_frame.h...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>

View file

@ -213,6 +213,12 @@
<ClCompile Include="QTGeneratedFiles\Release\moc_cg_disasm_window.cpp">
<Filter>Generated Files\Release</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_debugger_add_bp_window.cpp">
<Filter>Generated Files\Debug</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_debugger_add_bp_window.cpp">
<Filter>Generated Files\Release</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_debugger_frame.cpp">
<Filter>Generated Files\Debug</Filter>
</ClCompile>
@ -396,6 +402,9 @@
<ClCompile Include="rpcs3qt\cg_disasm_window.cpp">
<Filter>Gui\dev tools</Filter>
</ClCompile>
<ClCompile Include="rpcs3qt\debugger_add_bp_window.cpp">
<Filter>Gui\debugger</Filter>
</ClCompile>
<ClCompile Include="rpcs3qt\debugger_frame.cpp">
<Filter>Gui\debugger</Filter>
</ClCompile>

View file

@ -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)

View file

@ -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<breakpoint_types> 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<breakpoint_types> 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<breakpoint_types> 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;
}

View file

@ -1,13 +1,16 @@
#pragma once
#include "util/types.hpp"
#include <set>
#include "Utilities/bit_set.h"
#include <map>
#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<breakpoint_types> type);
/**
* Returns true if added successfully. TODO: flags
*/
bool AddBreakpoint(u32 loc);
bool AddBreakpoint(u32 loc, bs_t<breakpoint_types> 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<std::string (gameid), std::set<breakpoint>>.
// Although, externally, they'll only be accessed by loc (I think) so a map of maps may also do?
std::set<u32> m_breakpoints; //! Holds all breakpoints.
shared_mutex mutex_breakpoints;
std::map<u32, bs_t<breakpoint_types>> m_breakpoints; //! Holds all breakpoints.
bool m_break_on_bpm = false;
};
extern breakpoint_handler g_breakpoint_handler;

View file

@ -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 <QMenu>
#include <QMessageBox>
@ -72,19 +73,35 @@ void breakpoint_list::RemoveBreakpoint(u32 addr)
}
}
bool breakpoint_list::AddBreakpoint(u32 pc)
bool breakpoint_list::AddBreakpoint(u32 pc, bs_t<breakpoint_types> 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;

View file

@ -4,9 +4,10 @@
#include <QListWidget>
#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<CPUDisAsm> disasm);
void ClearBreakpoints();
bool AddBreakpoint(u32 pc);
void RemoveBreakpoint(u32 addr);
bool AddBreakpoint(u32 pc, bs_t<breakpoint_types> 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<CPUDisAsm> m_disasm = nullptr;

View file

@ -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 <QVBoxLayout>
#include <QLineEdit>
#include <QLabel>
#include <QComboBox>
#include <QPushButton>
#include <QDialogButtonBox>
#include <QMessageBox>
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<u32>(parsed_address);
bs_t<breakpoint_types> 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());
}

View file

@ -0,0 +1,13 @@
#pragma once
#include "breakpoint_list.h"
#include <QDialog>
class debugger_add_bp_window : public QDialog
{
Q_OBJECT
public:
explicit debugger_add_bp_window(breakpoint_list* bp_list, QWidget* parent = nullptr);
};

View file

@ -34,6 +34,7 @@
#include <algorithm>
#include <functional>
#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<bool> g_debugger_pause_all_threads_on_bp;
extern const ppu_decoder<ppu_itype> 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> 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> 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> 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> 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);

View file

@ -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;

View file

@ -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:
{

View file

@ -56,7 +56,7 @@ private:
std::shared_ptr<gui_settings> m_gui_settings;
breakpoint_handler* m_ppu_breakpoint_handler;
breakpoint_handler* m_ppu_breakpoint_handler = nullptr;
cpu_thread* m_cpu = nullptr;
std::shared_ptr<CPUDisAsm> m_disasm;
QDialog* m_cmd_detail = nullptr;