diff --git a/dep/msvc/qt b/dep/msvc/qt index fb90181218..4d453164df 160000 --- a/dep/msvc/qt +++ b/dep/msvc/qt @@ -1 +1 @@ -Subproject commit fb9018121818293b72e9f5064b2cb202e713c501 +Subproject commit 4d453164dfd7b0793653e59ba70ed64a2f5ca2a4 diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index a4fd3b4edd..e7d5752738 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -444,6 +444,12 @@ u8 CDROM::ReadRegister(u32 offset) case 2: // always data FIFO { + if (m_data_fifo.IsEmpty()) + { + Log_DevPrint("Data FIFO empty on read"); + return 0x00; + } + const u8 value = m_data_fifo.Pop(); UpdateStatusRegister(); Log_DebugPrintf("CDROM read data FIFO -> 0x%08X", ZeroExtend32(value)); diff --git a/src/core/cpu_core.cpp b/src/core/cpu_core.cpp index ce0867e629..3b3cadbf86 100644 --- a/src/core/cpu_core.cpp +++ b/src/core/cpu_core.cpp @@ -22,7 +22,6 @@ namespace CPU { static void SetPC(u32 new_pc); static void UpdateLoadDelay(); static void Branch(u32 target); -static void FlushPipeline(); State g_state; bool g_using_interpreter = false; @@ -35,7 +34,8 @@ static bool s_trace_to_log = false; static constexpr u32 INVALID_BREAKPOINT_PC = UINT32_C(0xFFFFFFFF); static std::vector s_breakpoints; static u32 s_breakpoint_counter = 1; -static u32 s_last_breakpoint_check_pc = INVALID_BREAKPOINT_PC; +static u32 s_last_execution_breakpoint_check_pc = INVALID_BREAKPOINT_PC; +static u32 s_last_data_breakpoint_check_pc = INVALID_BREAKPOINT_PC; static bool s_single_step = false; bool IsTraceEnabled() @@ -95,7 +95,8 @@ void Initialize() g_state.use_debug_dispatcher = false; s_breakpoints.clear(); s_breakpoint_counter = 1; - s_last_breakpoint_check_pc = INVALID_BREAKPOINT_PC; + s_last_execution_breakpoint_check_pc = INVALID_BREAKPOINT_PC; + s_last_data_breakpoint_check_pc = INVALID_BREAKPOINT_PC; s_single_step = false; UpdateFastmemBase(); @@ -322,30 +323,6 @@ ALWAYS_INLINE_RELEASE static void UpdateLoadDelay() g_state.next_load_delay_reg = Reg::count; } -ALWAYS_INLINE_RELEASE static void FlushPipeline() -{ - // loads are flushed - g_state.next_load_delay_reg = Reg::count; - if (g_state.load_delay_reg != Reg::count) - { - g_state.regs.r[static_cast(g_state.load_delay_reg)] = g_state.load_delay_value; - g_state.load_delay_reg = Reg::count; - } - - // not in a branch delay slot - g_state.branch_was_taken = false; - g_state.next_instruction_is_branch_delay_slot = false; - g_state.current_instruction_pc = g_state.regs.pc; - - // prefetch the next instruction - FetchInstruction(); - - // and set it as the next one to execute - g_state.current_instruction.bits = g_state.next_instruction.bits; - g_state.current_instruction_in_branch_delay_slot = false; - g_state.current_instruction_was_branch_taken = false; -} - ALWAYS_INLINE static u32 ReadReg(Reg rs) { return g_state.regs.r[static_cast(rs)]; @@ -539,6 +516,104 @@ ALWAYS_INLINE_RELEASE void Cop0DataBreakpointCheck(VirtualMemoryAddress address) DispatchCop0Breakpoint(); } +// range1 = [a, b]: represents any range of addresses that can have a breakpoint +// range2 = [c, d]: represents the current address being checked, and it's at most a word +// return: a mask of data intersection +ALWAYS_INLINE_RELEASE u32 AddressRangeIntersection(u32 a, u32 b, u32 c, u32 d) +{ + // test impossible intersections + if (d < a) + return 0; + if (c > b) + return 0; + + u32 min = std::max(a, c); + u32 max = std::min(b, d); + if (a <= min && b >= max) + { + u32 mask = 0; + while (max > min) + { + mask = mask << 4; + mask |= 0xFF; + max--; + } + while (min > c) + { + mask = mask << 4; + min--; + } + + return mask; + } + return 0; +} + +template +ALWAYS_INLINE_RELEASE void DataBreakpointCheck(VirtualMemoryAddress address, u32 size, u32 old_val=0, u32 new_val=0) +{ + if (System::IsPaused()) + return; + + if constexpr (type == MemoryAccessType::Read) + { + u32 count = static_cast(s_breakpoints.size()); + for (u32 i = 0; i < count; i++) + { + Breakpoint& bp = s_breakpoints[i]; + if (!bp.enabled || !(bp.dbg.debug_type & DebugType::Read)) + continue; + + if (AddressRangeIntersection(bp.dbg.address, bp.dbg.address + bp.dbg.size, address, address + size)) + { + bp.hit_count++; + g_host_interface->ReportFormattedDebuggerMessage("Hit read breakpoint at PC=0x%08X, reading address 0x%08X", + g_state.regs.pc, bp.dbg.address); + g_host_interface->PauseSystem(true); + return; + } + } + } + else + { + if constexpr (type == MemoryAccessType::Write) + { + u32 count = static_cast(s_breakpoints.size()); + for (u32 i = 0; i < count; i++) + { + Breakpoint& bp = s_breakpoints[i]; + if (!bp.enabled) + continue; + + if (bp.dbg.debug_type & DebugType::Written) + { + if (AddressRangeIntersection(bp.dbg.address, bp.dbg.address + bp.dbg.size, address, address + size)) + { + bp.hit_count++; + g_host_interface->ReportFormattedDebuggerMessage("Hit write breakpoint at PC=0x%08X, writing to address 0x%08X", + g_state.regs.pc, bp.dbg.address); + g_host_interface->PauseSystem(true); + return; + } + return; + } + if (bp.dbg.debug_type & DebugType::Changed) + { + u32 mask = AddressRangeIntersection(bp.dbg.address, bp.dbg.address + bp.dbg.size, address, address + size); + if (mask && ((old_val & mask) != (new_val & mask))) + { + bp.hit_count++; + g_host_interface->ReportFormattedDebuggerMessage("Hit data changed breakpoint at PC=0x%08X, writing to address 0x%08X", + g_state.regs.pc, bp.dbg.address); + g_host_interface->PauseSystem(true); + return; + } + } + } + } + } +} + static void TracePrintInstruction() { const u32 pc = g_state.current_instruction_pc; @@ -627,6 +702,111 @@ void DisassembleAndPrint(u32 addr, u32 instructions_before /* = 0 */, u32 instru } } +ALWAYS_INLINE_RELEASE static bool ConditionalBreakpointLookAhead() +{ + const u32 pc = g_state.regs.pc; + Instruction inst = g_state.next_instruction; + + if (System::IsPaused() || pc == s_last_data_breakpoint_check_pc) + return false; + + // Skip nops. Makes PGXP-CPU quicker, but also the regular interpreter. + if (inst.bits == 0) + return false; + + switch (inst.op) + { + case InstructionOp::lb: + case InstructionOp::lbu: + { + const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32(); + DataBreakpointCheck(addr, sizeof(u8)); + } + break; + case InstructionOp::lh: + case InstructionOp::lhu: + { + const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32(); + DataBreakpointCheck(addr, sizeof(u16)); + } + break; + case InstructionOp::lw: + { + const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32(); + DataBreakpointCheck(addr, sizeof(u32)); + } + break; + case InstructionOp::lwl: + case InstructionOp::lwr: + { + const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32(); + const VirtualMemoryAddress aligned_addr = addr & ~UINT32_C(3); + DataBreakpointCheck(aligned_addr, sizeof(u32)); + } + break; + case InstructionOp::sb: + { + const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32(); + u8 old_value; + if (!SafeReadMemoryByte(addr, &old_value)) + return false; + u8 new_value = Truncate8(ReadReg(inst.i.rt)); + DataBreakpointCheck(addr, sizeof(u8), old_value, new_value); + } + break; + case InstructionOp::sh: + { + const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32(); + u16 old_value; + if (!SafeReadMemoryHalfWord(addr, &old_value)) + return false; + u16 new_value = Truncate16(ReadReg(inst.i.rt)); + DataBreakpointCheck(addr, sizeof(u16), old_value, new_value); + } + break; + case InstructionOp::sw: + { + const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32(); + u32 old_value; + if (!SafeReadMemoryWord(addr, &old_value)) + return false; + u32 new_value = ReadReg(inst.i.rt); + DataBreakpointCheck(addr, sizeof(u32), old_value, new_value); + } + break; + case InstructionOp::swl: + case InstructionOp::swr: + { + const VirtualMemoryAddress addr = ReadReg(inst.i.rs) + inst.i.imm_sext32(); + const VirtualMemoryAddress aligned_addr = addr & ~UINT32_C(3); + u32 old_value; + if (!SafeReadMemoryWord(aligned_addr, &old_value)) + return false; + + const u32 reg_value = ReadReg(inst.i.rt); + const u8 shift = (Truncate8(addr) & u8(3)) * u8(8); + + u32 new_value; + if (inst.op == InstructionOp::swl) + { + const u32 mem_mask = UINT32_C(0xFFFFFF00) << shift; + new_value = (old_value & mem_mask) | (reg_value >> (24 - shift)); + } + else + { + const u32 mem_mask = UINT32_C(0x00FFFFFF) >> (24 - shift); + new_value = (old_value & mem_mask) | (reg_value << shift); + } + + DataBreakpointCheck(aligned_addr, sizeof(u32), old_value, new_value); + } + break; + } + + s_last_data_breakpoint_check_pc = pc; + return System::IsPaused(); +} + template ALWAYS_INLINE_RELEASE static void ExecuteInstruction() { @@ -1650,7 +1830,7 @@ bool HasBreakpointAtAddress(VirtualMemoryAddress address) { for (const Breakpoint& bp : s_breakpoints) { - if (bp.address == address) + if (address >= bp.dbg.address && bp.dbg.address + bp.dbg.size > address) return true; } @@ -1675,54 +1855,85 @@ BreakpointList GetBreakpointList(bool include_auto_clear, bool include_callbacks return bps; } -bool AddBreakpoint(VirtualMemoryAddress address, bool auto_clear, bool enabled) +void SetBreakpointEnable(int index, bool is_enable) { - if (HasBreakpointAtAddress(address)) + s_breakpoints[index].enabled = is_enable; +} + +DebugAddress GetBreakpointDebugAddress(int index) +{ + return s_breakpoints[index].dbg; +} + +void SetBreakpointDebugAddress(int index, DebugAddress dbg) +{ + s_breakpoints[index].dbg = dbg; +} + +bool AddBreakpoint(DebugAddress dbg, bool auto_clear, bool enabled) +{ + if (HasBreakpointAtAddress(dbg.address)) return false; - Log_InfoPrintf("Adding breakpoint at %08X, auto clear = %u", address, static_cast(auto_clear)); + Log_InfoPrintf("Adding breakpoint at %08X, auto clear = %u", dbg.address, static_cast(auto_clear)); - Breakpoint bp{address, nullptr, auto_clear ? 0 : s_breakpoint_counter++, 0, auto_clear, enabled}; + Breakpoint bp{dbg, nullptr, auto_clear ? 0 : s_breakpoint_counter++, 0, auto_clear, enabled}; s_breakpoints.push_back(std::move(bp)); UpdateDebugDispatcherFlag(); if (!auto_clear) { g_host_interface->ReportFormattedDebuggerMessage( - g_host_interface->TranslateString("DebuggerMessage", "Added breakpoint at 0x%08X."), address); + g_host_interface->TranslateString("DebuggerMessage", "Added breakpoint at 0x%08X."), dbg.address); } return true; } -bool AddBreakpointWithCallback(VirtualMemoryAddress address, BreakpointCallback callback) +bool AddBreakpointWithCallback(DebugAddress dbg, BreakpointCallback callback) { - if (HasBreakpointAtAddress(address)) + if (HasBreakpointAtAddress(dbg.address)) return false; - Log_InfoPrintf("Adding breakpoint with callback at %08X", address); + Log_InfoPrintf("Adding breakpoint with callback at %08X", dbg.address); - Breakpoint bp{address, callback, 0, 0, false, true}; + Breakpoint bp{dbg, callback, 0, 0, false, true}; s_breakpoints.push_back(std::move(bp)); UpdateDebugDispatcherFlag(); return true; } -bool RemoveBreakpoint(VirtualMemoryAddress address) +bool RemoveBreakpoint(DebugAddress dbg) { - auto it = std::find_if(s_breakpoints.begin(), s_breakpoints.end(), - [address](const Breakpoint& bp) { return bp.address == address; }); - if (it == s_breakpoints.end()) + u32 count = static_cast(s_breakpoints.size()); + u32 index = count; + for (u32 i = 0; i < count; i++) + { + if (s_breakpoints[i].dbg.address == dbg.address) + { + index = i; + break; + } + } + + if (index == count) return false; g_host_interface->ReportFormattedDebuggerMessage( - g_host_interface->TranslateString("DebuggerMessage", "Removed breakpoint at 0x%08X."), address); + g_host_interface->TranslateString("DebuggerMessage", "Removed breakpoint at 0x%08X."), dbg.address); + + for (u32 i = index + 1; i < count; i++) + s_breakpoints[i].number--; - s_breakpoints.erase(it); + s_breakpoints.erase(s_breakpoints.begin() + index); + s_breakpoint_counter--; UpdateDebugDispatcherFlag(); - if (address == s_last_breakpoint_check_pc) - s_last_breakpoint_check_pc = INVALID_BREAKPOINT_PC; + if (dbg.address == s_last_execution_breakpoint_check_pc) + s_last_execution_breakpoint_check_pc = INVALID_BREAKPOINT_PC; + + if (dbg.address == s_last_data_breakpoint_check_pc) + s_last_data_breakpoint_check_pc = INVALID_BREAKPOINT_PC; return true; } @@ -1730,20 +1941,24 @@ bool RemoveBreakpoint(VirtualMemoryAddress address) void ClearBreakpoints() { s_breakpoints.clear(); - s_breakpoint_counter = 0; - s_last_breakpoint_check_pc = INVALID_BREAKPOINT_PC; + s_breakpoint_counter = 1; + s_last_execution_breakpoint_check_pc = INVALID_BREAKPOINT_PC; + s_last_data_breakpoint_check_pc = INVALID_BREAKPOINT_PC; UpdateDebugDispatcherFlag(); } bool AddStepOverBreakpoint() { - u32 bp_pc = g_state.regs.pc; + DebugAddress bp_pc; + bp_pc.address = g_state.regs.pc; + bp_pc.debug_type = DebugType::Executed; + bp_pc.size = 4; Instruction inst; - if (!SafeReadInstruction(bp_pc, &inst.bits)) + if (!SafeReadInstruction(bp_pc.address, &inst.bits)) return false; - bp_pc += sizeof(Instruction); + bp_pc.address += sizeof(Instruction); if (!IsCallInstruction(inst)) { @@ -1752,7 +1967,7 @@ bool AddStepOverBreakpoint() return false; } - if (!SafeReadInstruction(bp_pc, &inst.bits)) + if (!SafeReadInstruction(bp_pc.address, &inst.bits)) return false; if (IsBranchInstruction(inst)) @@ -1763,10 +1978,10 @@ bool AddStepOverBreakpoint() } // skip the delay slot - bp_pc += sizeof(Instruction); + bp_pc.address += sizeof(Instruction); g_host_interface->ReportFormattedDebuggerMessage( - g_host_interface->TranslateString("DebuggerMessage", "Stepping over to 0x%08X."), bp_pc); + g_host_interface->TranslateString("DebuggerMessage", "Stepping over to 0x%08X."), bp_pc.address); return AddBreakpoint(bp_pc, true); } @@ -1774,13 +1989,17 @@ bool AddStepOverBreakpoint() bool AddStepOutBreakpoint(u32 max_instructions_to_search) { // find the branch-to-ra instruction. - u32 ret_pc = g_state.regs.pc; + DebugAddress ret_pc; + ret_pc.address = g_state.regs.pc; + ret_pc.debug_type = DebugType::Executed; + ret_pc.size = 4; + for (u32 i = 0; i < max_instructions_to_search; i++) { - ret_pc += sizeof(Instruction); + ret_pc.address += sizeof(Instruction); Instruction inst; - if (!SafeReadInstruction(ret_pc, &inst.bits)) + if (!SafeReadInstruction(ret_pc.address, &inst.bits)) { g_host_interface->ReportFormattedDebuggerMessage( g_host_interface->TranslateString("DebuggerMessage", @@ -1792,7 +2011,7 @@ bool AddStepOutBreakpoint(u32 max_instructions_to_search) if (IsReturnInstruction(inst)) { g_host_interface->ReportFormattedDebuggerMessage( - g_host_interface->TranslateString("DebuggerMessage", "Stepping out to 0x%08X."), ret_pc); + g_host_interface->TranslateString("DebuggerMessage", "Stepping out to 0x%08X."), ret_pc.address); return AddBreakpoint(ret_pc, true); } @@ -1806,7 +2025,7 @@ bool AddStepOutBreakpoint(u32 max_instructions_to_search) return false; } -ALWAYS_INLINE_RELEASE static bool BreakpointCheck() +ALWAYS_INLINE_RELEASE static bool ExecutionBreakpointCheck() { const u32 pc = g_state.regs.pc; @@ -1816,11 +2035,11 @@ ALWAYS_INLINE_RELEASE static bool BreakpointCheck() { ForceDispatcherExit(); s_single_step = false; - s_last_breakpoint_check_pc = pc; + s_last_execution_breakpoint_check_pc = pc; return false; } - if (pc == s_last_breakpoint_check_pc) + if (pc == s_last_execution_breakpoint_check_pc) { // we don't want to trigger the same breakpoint which just paused us repeatedly. return false; @@ -1830,7 +2049,7 @@ ALWAYS_INLINE_RELEASE static bool BreakpointCheck() for (u32 i = 0; i < count;) { Breakpoint& bp = s_breakpoints[i]; - if (!bp.enabled || bp.address != pc) + if (!bp.enabled || !(bp.dbg.debug_type & DebugType::Executed) || bp.dbg.address != pc) { i++; continue; @@ -1865,13 +2084,13 @@ ALWAYS_INLINE_RELEASE static bool BreakpointCheck() } else { - g_host_interface->ReportFormattedDebuggerMessage("Hit breakpoint %u at 0x%08X.", bp.number, pc); + g_host_interface->ReportFormattedDebuggerMessage("Hit execution breakpoint at PC=0x%08X.", pc); i++; } } } - s_last_breakpoint_check_pc = pc; + s_last_execution_breakpoint_check_pc = pc; return System::IsPaused(); } @@ -1893,11 +2112,8 @@ static void ExecuteImpl() { Cop0ExecutionBreakpointCheck(); - if (BreakpointCheck()) - { - // continue is measurably faster than break on msvc for some reason - continue; - } + if (ExecutionBreakpointCheck() || ConditionalBreakpointLookAhead()) + continue; // continue is measurably faster than break on msvc for some reason } g_state.interrupt_delay = false; diff --git a/src/core/cpu_core.h b/src/core/cpu_core.h index 2be8482b56..966de73227 100644 --- a/src/core/cpu_core.h +++ b/src/core/cpu_core.h @@ -169,7 +169,7 @@ using BreakpointCallback = bool (*)(VirtualMemoryAddress address); struct Breakpoint { - VirtualMemoryAddress address; + DebugAddress dbg; BreakpointCallback callback; u32 number; u32 hit_count; @@ -183,12 +183,15 @@ using BreakpointList = std::vector; bool HasAnyBreakpoints(); bool HasBreakpointAtAddress(VirtualMemoryAddress address); BreakpointList GetBreakpointList(bool include_auto_clear = false, bool include_callbacks = false); -bool AddBreakpoint(VirtualMemoryAddress address, bool auto_clear = false, bool enabled = true); -bool AddBreakpointWithCallback(VirtualMemoryAddress address, BreakpointCallback callback); -bool RemoveBreakpoint(VirtualMemoryAddress address); +bool AddBreakpoint(DebugAddress dbg, bool auto_clear = false, bool enabled = true); +bool AddBreakpointWithCallback(DebugAddress dbg, BreakpointCallback callback); +bool RemoveBreakpoint(DebugAddress dbg); void ClearBreakpoints(); bool AddStepOverBreakpoint(); bool AddStepOutBreakpoint(u32 max_instructions_to_search = 1000); +void SetBreakpointEnable(int index, bool is_enable); +DebugAddress GetBreakpointDebugAddress(int index); +void SetBreakpointDebugAddress(int index, DebugAddress dbg); extern bool TRACE_EXECUTION; diff --git a/src/core/cpu_core_private.h b/src/core/cpu_core_private.h index 1be1b13a7a..7d44cda7c9 100644 --- a/src/core/cpu_core_private.h +++ b/src/core/cpu_core_private.h @@ -111,6 +111,30 @@ bool WriteMemoryWord(VirtualMemoryAddress addr, u32 value); void* GetDirectReadMemoryPointer(VirtualMemoryAddress address, MemoryAccessSize size, TickCount* read_ticks); void* GetDirectWriteMemoryPointer(VirtualMemoryAddress address, MemoryAccessSize size); +ALWAYS_INLINE_RELEASE void FlushPipeline() +{ + // loads are flushed + g_state.next_load_delay_reg = Reg::count; + if (g_state.load_delay_reg != Reg::count) + { + g_state.regs.r[static_cast(g_state.load_delay_reg)] = g_state.load_delay_value; + g_state.load_delay_reg = Reg::count; + } + + // not in a branch delay slot + g_state.branch_was_taken = false; + g_state.next_instruction_is_branch_delay_slot = false; + g_state.current_instruction_pc = g_state.regs.pc; + + // prefetch the next instruction + FetchInstruction(); + + // and set it as the next one to execute + g_state.current_instruction.bits = g_state.next_instruction.bits; + g_state.current_instruction_in_branch_delay_slot = false; + g_state.current_instruction_was_branch_taken = false; +} + ALWAYS_INLINE void AddGTETicks(TickCount ticks) { g_state.gte_completion_tick = g_state.pending_ticks + ticks + 1; diff --git a/src/core/cpu_disasm.cpp b/src/core/cpu_disasm.cpp index 6caacc409a..c117d05384 100644 --- a/src/core/cpu_disasm.cpp +++ b/src/core/cpu_disasm.cpp @@ -170,6 +170,12 @@ static void FormatInstruction(String* dest, const Instruction inst, u32 pc, cons { dest->Clear(); + if (inst.bits == 0) + { + dest->AppendString("nop"); + return; + } + const char* str = format; while (*str != '\0') { @@ -197,36 +203,43 @@ static void FormatInstruction(String* dest, const Instruction inst, u32 pc, cons } else if (std::strncmp(str, "shamt", 5) == 0) { - dest->AppendFormattedString("%d", ZeroExtend32(inst.r.shamt.GetValue())); + const s32 shamt = static_cast(ZeroExtend32(inst.r.shamt.GetValue())); + if (shamt < 0) + dest->AppendFormattedString("-%x", shamt * -1); + else + dest->AppendFormattedString("%x", shamt); str += 5; } else if (std::strncmp(str, "immu", 4) == 0) { - dest->AppendFormattedString("%u", inst.i.imm_zext32()); + dest->AppendFormattedString("0x%04x", inst.i.imm_zext32()); str += 4; } else if (std::strncmp(str, "imm", 3) == 0) { // dest->AppendFormattedString("%d", static_cast(inst.i.imm_sext32())); - dest->AppendFormattedString("%04x", inst.i.imm_zext32()); + dest->AppendFormattedString("0x%04x", inst.i.imm_zext32()); str += 3; } else if (std::strncmp(str, "rel", 3) == 0) { const u32 target = (pc + UINT32_C(4)) + (inst.i.imm_sext32() << 2); - dest->AppendFormattedString("%08x", target); + dest->AppendFormattedString("0x%08x", target); str += 3; } else if (std::strncmp(str, "offsetrs", 8) == 0) { const s32 offset = static_cast(inst.i.imm_sext32()); - dest->AppendFormattedString("%d(%s)", offset, GetRegName(inst.i.rs)); + if (offset < 0) + dest->AppendFormattedString("-0x%x(%s)", offset * -1, GetRegName(inst.i.rs)); + else + dest->AppendFormattedString("0x%x(%s)", offset, GetRegName(inst.i.rs)); str += 8; } else if (std::strncmp(str, "jt", 2) == 0) { const u32 target = ((pc + UINT32_C(4)) & UINT32_C(0xF0000000)) | (inst.j.target << 2); - dest->AppendFormattedString("%08x", target); + dest->AppendFormattedString("0x%08x", target); str += 2; } else if (std::strncmp(str, "copcc", 5) == 0) diff --git a/src/core/gdb_protocol.cpp b/src/core/gdb_protocol.cpp index 309981ee4d..2d54fc321e 100644 --- a/src/core/gdb_protocol.cpp +++ b/src/core/gdb_protocol.cpp @@ -213,7 +213,11 @@ static std::optional Cmd$z1(const std::string_view& data) { auto address = StringUtil::FromChars(data, 16); if (address) { - CPU::RemoveBreakpoint(*address); + DebugAddress dbg; + dbg.address = *address; + dbg.debug_type = DebugType::Executed; + dbg.size = 4; + CPU::RemoveBreakpoint(dbg); return { "OK" }; } else { @@ -226,7 +230,11 @@ static std::optional Cmd$Z1(const std::string_view& data) { auto address = StringUtil::FromChars(data, 16); if (address) { - CPU::AddBreakpoint(*address, false); + DebugAddress dbg; + dbg.address = *address; + dbg.debug_type = DebugType::Executed; + dbg.size = 4; + CPU::AddBreakpoint(dbg, false); return { "OK" }; } else { diff --git a/src/core/types.h b/src/core/types.h index d197ad62bf..f4228ed026 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -17,6 +17,21 @@ enum class MemoryAccessSize : u32 Word }; +struct DebugAddress +{ + VirtualMemoryAddress address; + u32 size; + u8 debug_type = 0; +}; + +enum DebugType : u8 +{ + Read = 1, + Written = 2, + Changed = 4, + Executed = 8 +}; + using TickCount = s32; enum class ConsoleRegion diff --git a/src/duckstation-qt/cheatmanagerdialog.cpp b/src/duckstation-qt/cheatmanagerdialog.cpp index 735adacd46..a8aa145a88 100644 --- a/src/duckstation-qt/cheatmanagerdialog.cpp +++ b/src/duckstation-qt/cheatmanagerdialog.cpp @@ -746,7 +746,7 @@ void CheatManagerDialog::addToWatchClicked() void CheatManagerDialog::addManualWatchAddressClicked() { - std::optional address = QtUtils::PromptForAddress(this, windowTitle(), tr("Enter manual address:"), false); + std::optional address = QtUtils::PromptForAddress(this, windowTitle(), false); if (!address.has_value()) return; diff --git a/src/duckstation-qt/debuggermodels.cpp b/src/duckstation-qt/debuggermodels.cpp index b842c69521..be06f64528 100644 --- a/src/duckstation-qt/debuggermodels.cpp +++ b/src/duckstation-qt/debuggermodels.cpp @@ -2,10 +2,12 @@ #include "core/cpu_core.h" #include "core/cpu_core_private.h" #include "core/cpu_disasm.h" +#include "core/system.h" #include #include #include #include +#include static constexpr int NUM_COLUMNS = 5; static constexpr int STACK_RANGE = 128; @@ -55,7 +57,7 @@ QVariant DebuggerCodeModel::data(const QModelIndex& index, int role /*= Qt::Disp if (index.column() < 0 || index.column() >= NUM_COLUMNS) return QVariant(); - if (role == Qt::DisplayRole) + if (role == Qt::DisplayRole || role == Qt::EditRole) { const VirtualMemoryAddress address = getAddressForRow(index.row()); switch (index.column()) @@ -95,7 +97,7 @@ QVariant DebuggerCodeModel::data(const QModelIndex& index, int role /*= Qt::Disp case 4: { // Comment - if (address != m_last_pc) + if (address != m_last_pc && address != selected_address) return QVariant(); u32 instruction_bits; @@ -133,6 +135,8 @@ QVariant DebuggerCodeModel::data(const QModelIndex& index, int role /*= Qt::Disp if (hasBreakpointAtAddress(address)) return QVariant(QColor(171, 97, 107)); + if (address == selected_address) + return QColor(76, 76, 76); // if (address == m_last_pc) // return QApplication::palette().toolTipBase(); if (address == m_last_pc) @@ -181,6 +185,34 @@ QVariant DebuggerCodeModel::headerData(int section, Qt::Orientation orientation, } } +Qt::ItemFlags DebuggerCodeModel::flags(const QModelIndex& index) const +{ + if (index.column() == 2) + return Qt::ItemIsEditable | QAbstractItemModel::flags(index); + return QAbstractItemModel::flags(index); +} + +bool DebuggerCodeModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (System::IsPaused() && index.isValid() && role == Qt::EditRole) + { + bool ok; + QString value_str = value.toString(); + u32 instruction; + if (value_str.startsWith("0x")) + instruction = value_str.mid(2).toUInt(&ok, 16); + else + instruction = value_str.toUInt(&ok, 16); + if (ok) + { + const VirtualMemoryAddress address = static_cast(getAddressForRow(index.row())); + std::memcpy(&Bus::g_ram[address & Bus::g_ram_mask], &instruction, sizeof(instruction)); + return true; + } + } + return false; +} + bool DebuggerCodeModel::updateRegion(VirtualMemoryAddress address) { CPU::Segment segment = CPU::GetSegmentForAddress(address); @@ -241,6 +273,13 @@ void DebuggerCodeModel::setPC(VirtualMemoryAddress pc) } } +void DebuggerCodeModel::setCurrentSelectedAddress(VirtualMemoryAddress address) +{ + VirtualMemoryAddress temp = selected_address; + selected_address = address; + emitDataChangedForAddress(temp); +} + void DebuggerCodeModel::ensureAddressVisible(VirtualMemoryAddress address) { updateRegion(address); @@ -288,6 +327,16 @@ DebuggerRegistersModel::DebuggerRegistersModel(QObject* parent /*= nullptr*/) : DebuggerRegistersModel::~DebuggerRegistersModel() {} +void DebuggerRegistersModel::setCodeModel(DebuggerCodeModel* model) +{ + code_model = model; +} + +void DebuggerRegistersModel::setCodeView(QTreeView* view) +{ + code_view = view; +} + int DebuggerRegistersModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const { return static_cast(CPU::Reg::count); @@ -327,6 +376,8 @@ QVariant DebuggerRegistersModel::data(const QModelIndex& index, int role /*= Qt: if (CPU::g_state.regs.r[reg_index] != m_old_reg_values[reg_index]) return QColor(255, 50, 50); } + else if (role == Qt::EditRole) + return QString::asprintf("%08X", CPU::g_state.regs.r[reg_index]); } break; @@ -357,6 +408,55 @@ QVariant DebuggerRegistersModel::headerData(int section, Qt::Orientation orienta } } +Qt::ItemFlags DebuggerRegistersModel::flags(const QModelIndex& index) const +{ + if (index.column() == 1) + return Qt::ItemIsEditable | QAbstractItemModel::flags(index); + return QAbstractItemModel::flags(index); +} + +bool DebuggerRegistersModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (System::IsPaused() && index.isValid() && role == Qt::EditRole) + { + bool ok; + QString value_str = value.toString(); + u32 reg_value; + if (value_str.startsWith("0x")) + reg_value = value_str.mid(2).toUInt(&ok, 16); + else + reg_value = value_str.toUInt(&ok, 16); + if (ok) + { + u32 reg_index = static_cast(index.row()); + if (reg_index == 34) // special case for PC + { + CPU::g_state.regs.npc = reg_value & 0xFFFFFFFC; // making sure it's divisible by 4 + CPU::FlushPipeline(); + } + else if (reg_index == 35) // special case for NPC + CPU::g_state.regs.npc = reg_value & 0xFFFFFFFC; // making sure it's divisible by 4 + else + CPU::g_state.regs.r[reg_index] = reg_value; + + // Update code view comments with the new register value + code_model->setPC(CPU::g_state.regs.pc); + code_model->ensureAddressVisible(CPU::g_state.regs.pc); + int row = code_model->getRowForAddress(CPU::g_state.regs.pc); + if (row >= 0) + { + qApp->processEvents(QEventLoop::ExcludeUserInputEvents); + code_view->scrollTo(code_model->index(row, 0)); + } + + emit dataChanged(index, index, {role}); + + return true; + } + } + return false; +} + void DebuggerRegistersModel::invalidateView() { beginResetModel(); @@ -388,7 +488,7 @@ QVariant DebuggerStackModel::data(const QModelIndex& index, int role /*= Qt::Dis if (index.column() < 0 || index.column() > 1) return QVariant(); - if (role != Qt::DisplayRole) + if (role != Qt::DisplayRole && role != Qt::EditRole) return QVariant(); const u32 sp = CPU::g_state.regs.sp; @@ -402,7 +502,33 @@ QVariant DebuggerStackModel::data(const QModelIndex& index, int role /*= Qt::Dis if (!CPU::SafeReadMemoryWord(address, &value)) return tr(""); - return QString::asprintf("0x%08X", ZeroExtend32(value)); + if (role == Qt::DisplayRole) + return QString::asprintf("0x%08X", ZeroExtend32(value)); + + return QString::asprintf("%08X", ZeroExtend32(value)); +} + +bool DebuggerStackModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (System::IsPaused() && index.isValid() && role == Qt::EditRole) + { + bool ok; + QString value_str = value.toString(); + u32 stack_value; + if (value_str.startsWith("0x")) + stack_value = value_str.mid(2).toUInt(&ok, 16); + else + stack_value = value_str.toUInt(&ok, 16); + if (ok) + { + const u32 sp = CPU::g_state.regs.sp; + const VirtualMemoryAddress address = + (sp - static_cast(STACK_RANGE * STACK_VALUE_SIZE)) + static_cast(index.row()) * STACK_VALUE_SIZE; + std::memcpy(&Bus::g_ram[address & Bus::g_ram_mask], &stack_value, sizeof(stack_value)); + return true; + } + } + return false; } QVariant DebuggerStackModel::headerData(int section, Qt::Orientation orientation, int role /*= Qt::DisplayRole*/) const @@ -424,6 +550,13 @@ QVariant DebuggerStackModel::headerData(int section, Qt::Orientation orientation } } +Qt::ItemFlags DebuggerStackModel::flags(const QModelIndex& index) const +{ + if (index.column() == 1) + return Qt::ItemIsEditable | QAbstractItemModel::flags(index); + return QAbstractItemModel::flags(index); +} + void DebuggerStackModel::invalidateView() { beginResetModel(); diff --git a/src/duckstation-qt/debuggermodels.h b/src/duckstation-qt/debuggermodels.h index fcbbb5f7d0..c919db36d5 100644 --- a/src/duckstation-qt/debuggermodels.h +++ b/src/duckstation-qt/debuggermodels.h @@ -4,6 +4,7 @@ #include #include #include +#include #include class DebuggerCodeModel : public QAbstractTableModel @@ -18,6 +19,8 @@ class DebuggerCodeModel : public QAbstractTableModel virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + virtual Qt::ItemFlags flags(const QModelIndex& index) const override; + virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; // Returns the row for this instruction pointer void resetCodeView(VirtualMemoryAddress start_address); @@ -27,6 +30,7 @@ class DebuggerCodeModel : public QAbstractTableModel VirtualMemoryAddress getAddressForIndex(QModelIndex index) const; void setPC(VirtualMemoryAddress pc); void ensureAddressVisible(VirtualMemoryAddress address); + void setCurrentSelectedAddress(VirtualMemoryAddress address); void setBreakpointList(std::vector bps); void setBreakpointState(VirtualMemoryAddress address, bool enabled); void clearBreakpoints(); @@ -41,6 +45,7 @@ class DebuggerCodeModel : public QAbstractTableModel VirtualMemoryAddress m_code_region_start = 0; VirtualMemoryAddress m_code_region_end = 0; VirtualMemoryAddress m_last_pc = 0; + VirtualMemoryAddress selected_address = 0xFFFFFFFF; std::vector m_breakpoints; QPixmap m_pc_pixmap; @@ -54,17 +59,23 @@ class DebuggerRegistersModel : public QAbstractListModel public: DebuggerRegistersModel(QObject* parent = nullptr); virtual ~DebuggerRegistersModel(); + void setCodeModel(DebuggerCodeModel* model); + void setCodeView(QTreeView* view); virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + virtual Qt::ItemFlags flags(const QModelIndex& index) const override; + virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; void invalidateView(); void saveCurrentValues(); private: u32 m_old_reg_values[static_cast(CPU::Reg::count)] = {}; + DebuggerCodeModel* code_model; + QTreeView* code_view; }; class DebuggerStackModel : public QAbstractListModel @@ -78,7 +89,9 @@ class DebuggerStackModel : public QAbstractListModel virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + virtual Qt::ItemFlags flags(const QModelIndex& index) const override; void invalidateView(); }; diff --git a/src/duckstation-qt/debuggerwindow.cpp b/src/duckstation-qt/debuggerwindow.cpp index 8a0d708460..dc9044d943 100644 --- a/src/duckstation-qt/debuggerwindow.cpp +++ b/src/duckstation-qt/debuggerwindow.cpp @@ -13,16 +13,24 @@ DebuggerWindow::DebuggerWindow(QWidget* parent /* = nullptr */) { m_ui.setupUi(this); setupAdditionalUi(); - connectSignals(); createModels(); + connectSignals(); setMemoryViewRegion(Bus::MemoryRegion::RAM); setUIEnabled(false); } DebuggerWindow::~DebuggerWindow() = default; +void DebuggerWindow::SetActive(bool status) +{ + DebuggerWindow::is_active = status; +} + void DebuggerWindow::onEmulationPaused(bool paused) { + if (!DebuggerWindow::is_active) + return; + if (paused) { setUIEnabled(true); @@ -92,7 +100,11 @@ void DebuggerWindow::onRunToCursorTriggered() return; } - CPU::AddBreakpoint(addr.value(), true, true); + DebugAddress dbg; + dbg.address = addr.value(); + dbg.debug_type = DebugType::Executed; + dbg.size = 4; + CPU::AddBreakpoint(dbg, true, true); QtHostInterface::GetInstance()->pauseSystem(false); } @@ -104,17 +116,18 @@ void DebuggerWindow::onGoToPCTriggered() void DebuggerWindow::onGoToAddressTriggered() { std::optional address = - QtUtils::PromptForAddress(this, windowTitle(), tr("Enter code address:"), true); + QtUtils::PromptForAddress(this, windowTitle(), true); if (!address.has_value()) return; + m_code_model->setCurrentSelectedAddress(address.value()); scrollToCodeAddress(address.value()); } void DebuggerWindow::onDumpAddressTriggered() { std::optional address = - QtUtils::PromptForAddress(this, windowTitle(), tr("Enter memory address:"), false); + QtUtils::PromptForAddress(this, windowTitle(), false); if (!address.has_value()) return; @@ -144,18 +157,18 @@ void DebuggerWindow::onFollowAddressTriggered() void DebuggerWindow::onAddBreakpointTriggered() { - std::optional address = - QtUtils::PromptForAddress(this, windowTitle(), tr("Enter code address:"), true); - if (!address.has_value()) + DebugAddress dbg = QtUtils::PromptForDebugAddress(this, windowTitle(), "", ""); + + if (!dbg.debug_type) return; - if (CPU::HasBreakpointAtAddress(address.value())) + if (CPU::HasBreakpointAtAddress(dbg.address)) { QMessageBox::critical(this, windowTitle(), tr("A breakpoint already exists at this address.")); return; } - toggleBreakpoint(address.value()); + toggleBreakpoint(dbg); } void DebuggerWindow::onToggleBreakpointTriggered() @@ -164,12 +177,17 @@ void DebuggerWindow::onToggleBreakpointTriggered() if (!address.has_value()) return; - toggleBreakpoint(address.value()); + DebugAddress dbg; + dbg.address = address.value(); + dbg.debug_type = DebugType::Executed; + dbg.size = 4; + toggleBreakpoint(dbg); } void DebuggerWindow::onClearBreakpointsTriggered() { clearBreakpoints(); + refreshBreakpointList(); } void DebuggerWindow::onStepIntoActionTriggered() @@ -208,17 +226,38 @@ void DebuggerWindow::onStepOutActionTriggered() QtHostInterface::GetInstance()->pauseSystem(false); } +void DebuggerWindow::refreshCodeViewSelectedAddress() +{ + std::optional address = getSelectedCodeAddress(); + if (address.has_value()) + m_code_model->setCurrentSelectedAddress(address.value()); +} + +void DebuggerWindow::onCodeViewPressed(QModelIndex index) +{ + refreshCodeViewSelectedAddress(); +} + +void DebuggerWindow::onCodeViewCurrentChanged(QModelIndex current, QModelIndex previous) +{ + refreshCodeViewSelectedAddress(); +} + void DebuggerWindow::onCodeViewItemActivated(QModelIndex index) { if (!index.isValid()) return; const VirtualMemoryAddress address = m_code_model->getAddressForIndex(index); + DebugAddress dbg; + dbg.address = address; + dbg.debug_type = DebugType::Executed; + dbg.size = 4; switch (index.column()) { case 0: // breakpoint case 3: // disassembly - toggleBreakpoint(address); + toggleBreakpoint(dbg); break; case 1: // address @@ -232,11 +271,94 @@ void DebuggerWindow::onCodeViewItemActivated(QModelIndex index) } } +void DebuggerWindow::onBreakpointsWidgetItemChanged(QTreeWidgetItem* item, int column) +{ + if (column == 0) + { + bool ok; + int index = item->text(0).toInt(&ok); + if (ok) + { + if (item->checkState(column) == Qt::Unchecked) + CPU::SetBreakpointEnable(index - 1, false); + else + CPU::SetBreakpointEnable(index - 1, true); + } + } +} + +void DebuggerWindow::editBreakpoint(QTreeWidgetItem* item) +{ + bool ok; + int index = item->text(0).toInt(&ok); + if (ok) + { + DebugAddress curr_dbg = CPU::GetBreakpointDebugAddress(index - 1); + DebugAddress new_dbg = QtUtils::PromptForDebugAddress(this, windowTitle(), item->text(1).mid(2), QString::number(curr_dbg.size), + (curr_dbg.debug_type & DebugType::Read) ? Qt::Checked : Qt::Unchecked, + (curr_dbg.debug_type & DebugType::Written) ? Qt::Checked : Qt::Unchecked, + (curr_dbg.debug_type & DebugType::Changed) ? Qt::Checked : Qt::Unchecked, + (curr_dbg.debug_type & DebugType::Executed) ? Qt::Checked : Qt::Unchecked); + if (!new_dbg.debug_type) + return; + if (curr_dbg.address == new_dbg.address) + CPU::SetBreakpointDebugAddress(index - 1, new_dbg); + else + { + if (CPU::HasBreakpointAtAddress(new_dbg.address)) + QMessageBox::critical(this, windowTitle(), tr("A breakpoint already exists at this address.")); + else + { + CPU::RemoveBreakpoint(curr_dbg); + CPU::AddBreakpoint(new_dbg); + refreshBreakpointList(); + } + } + } +} + +void DebuggerWindow::deleteBreakpoint(QTreeWidgetItem* item) +{ + bool ok; + int index = item->text(0).toInt(&ok); + if (ok) + { + DebugAddress dbg = CPU::GetBreakpointDebugAddress(index - 1); + CPU::RemoveBreakpoint(dbg); + refreshBreakpointList(); + } +} + +void DebuggerWindow::onBreakpointWidgetItemDoubleClicked(QTreeWidgetItem* item, int column) +{ + editBreakpoint(item); +} + +void DebuggerWindow::onBreakpointsWidgetMenuRequested(const QPoint& pos) +{ + QTreeWidgetItem* item = m_ui.breakpointsWidget->itemAt(pos); + if (!item) + return; + + QMenu menu(tr("Breakpoint menu"), this); + menu.addAction("Edit"); + menu.addAction("Delete"); + QAction* action = menu.exec(m_ui.breakpointsWidget->viewport()->mapToGlobal(pos)); + + if (action == nullptr) + return; + if (action->text() == "Edit") + editBreakpoint(item); + else if (action->text() == "Delete") + deleteBreakpoint(item); +} + void DebuggerWindow::onMemorySearchTriggered() { m_ui.memoryView->clearHighlightRange(); const QString pattern_str = m_ui.memorySearchString->text(); + qsizetype str_size = pattern_str.length(); if (pattern_str.isEmpty()) return; @@ -245,55 +367,72 @@ void DebuggerWindow::onMemorySearchTriggered() u8 spattern = 0; u8 smask = 0; bool msb = false; + bool is_string = false; - pattern.reserve(static_cast(pattern_str.length()) / 2); - mask.reserve(static_cast(pattern_str.length()) / 2); + pattern.reserve(static_cast(str_size) / 2); + mask.reserve(static_cast(str_size) / 2); - for (int i = 0; i < pattern_str.length(); i++) + if (pattern_str[0].unicode() == '"' && pattern_str[str_size - 1].unicode() == '"') { - const QChar ch = pattern_str[i]; - if (ch == ' ') - continue; - - if (ch == '?') - { - spattern = (spattern << 4); - smask = (smask << 4); - } - else if (ch.isDigit()) - { - spattern = (spattern << 4) | static_cast(ch.digitValue()); - smask = (smask << 4) | 0xF; - } - else if (ch.unicode() >= 'a' && ch.unicode() <= 'f') - { - spattern = (spattern << 4) | (0xA + static_cast(ch.unicode() - 'a')); - smask = (smask << 4) | 0xF; - } - else if (ch.unicode() >= 'A' && ch.unicode() <= 'F') - { - spattern = (spattern << 4) | (0xA + static_cast(ch.unicode() - 'A')); - smask = (smask << 4) | 0xF; - } - else - { - QMessageBox::critical(this, windowTitle(), - tr("Invalid search pattern. It should contain hex digits or question marks.")); - return; - } - - if (msb) + is_string = true; + smask = 0xFF; + for (int i = 1; i < str_size - 1; i++) { + spattern = static_cast(pattern_str[i].toLatin1()); pattern.push_back(spattern); mask.push_back(smask); spattern = 0; - smask = 0; } + } - msb = !msb; + if (!is_string) + { + for (int i = 0; i < str_size; i++) + { + const QChar ch = pattern_str[i]; + if (ch == ' ') + continue; + + if (ch == '?') + { + spattern = (spattern << 4); + smask = (smask << 4); + } + else if (ch.isDigit()) + { + spattern = (spattern << 4) | static_cast(ch.digitValue()); + smask = (smask << 4) | 0xF; + } + else if (ch.unicode() >= 'a' && ch.unicode() <= 'f') + { + spattern = (spattern << 4) | (0xA + static_cast(ch.unicode() - 'a')); + smask = (smask << 4) | 0xF; + } + else if (ch.unicode() >= 'A' && ch.unicode() <= 'F') + { + spattern = (spattern << 4) | (0xA + static_cast(ch.unicode() - 'A')); + smask = (smask << 4) | 0xF; + } + else + { + QMessageBox::critical(this, windowTitle(), + tr("Invalid search pattern or string. Patterns should contain hex digits or question marks; strings should start and end with double quotes.")); + return; + } + + if (msb) + { + pattern.push_back(spattern); + mask.push_back(smask); + spattern = 0; + smask = 0; + } + + msb = !msb; + } } - if (msb) + if (msb && !is_string) { // partial byte on the end spattern = (spattern << 4); @@ -305,7 +444,7 @@ void DebuggerWindow::onMemorySearchTriggered() if (pattern.empty()) { QMessageBox::critical(this, windowTitle(), - tr("Invalid search pattern. It should contain hex digits or question marks.")); + tr("Invalid search pattern or string. Patterns should contain hex digits or question marks; strings should start and end with double quotes.")); return; } @@ -396,13 +535,25 @@ void DebuggerWindow::connectSignals() connect(m_ui.actionToggleBreakpoint, &QAction::triggered, this, &DebuggerWindow::onToggleBreakpointTriggered); connect(m_ui.actionClearBreakpoints, &QAction::triggered, this, &DebuggerWindow::onClearBreakpointsTriggered); connect(m_ui.actionClose, &QAction::triggered, this, &DebuggerWindow::close); + connect(m_ui.codeView, &QTreeView::pressed, this, &DebuggerWindow::onCodeViewPressed); connect(m_ui.codeView, &QTreeView::activated, this, &DebuggerWindow::onCodeViewItemActivated); + connect(m_ui.codeView->selectionModel(), &QItemSelectionModel::currentChanged, this, + &DebuggerWindow::onCodeViewCurrentChanged); + connect(m_ui.breakpointsWidget, &QTreeWidget::itemChanged, this, &DebuggerWindow::onBreakpointsWidgetItemChanged); + connect(m_ui.breakpointsWidget, &QWidget::customContextMenuRequested, this, + &DebuggerWindow::onBreakpointsWidgetMenuRequested); + connect(m_ui.breakpointsWidget, &QTreeWidget::itemDoubleClicked, this, + &DebuggerWindow::onBreakpointWidgetItemDoubleClicked); connect(m_ui.memoryRegionRAM, &QRadioButton::clicked, [this]() { setMemoryViewRegion(Bus::MemoryRegion::RAM); }); connect(m_ui.memoryRegionEXP1, &QRadioButton::clicked, [this]() { setMemoryViewRegion(Bus::MemoryRegion::EXP1); }); connect(m_ui.memoryRegionScratchpad, &QRadioButton::clicked, [this]() { setMemoryViewRegion(Bus::MemoryRegion::Scratchpad); }); connect(m_ui.memoryRegionBIOS, &QRadioButton::clicked, [this]() { setMemoryViewRegion(Bus::MemoryRegion::BIOS); }); + + connect(m_ui.memoryDisplayByte, &QRadioButton::clicked, [this]() { m_ui.memoryView->setDisplaySize(sizeof(u8)); }); + connect(m_ui.memoryDisplayHalfword, &QRadioButton::clicked, [this]() { m_ui.memoryView->setDisplaySize(sizeof(u16)); }); + connect(m_ui.memoryDisplayWord, &QRadioButton::clicked, [this]() { m_ui.memoryView->setDisplaySize(sizeof(u32)); }); connect(m_ui.memorySearch, &QPushButton::clicked, this, &DebuggerWindow::onMemorySearchTriggered); connect(m_ui.memorySearchString, &QLineEdit::textChanged, this, &DebuggerWindow::onMemorySearchStringChanged); @@ -427,6 +578,8 @@ void DebuggerWindow::createModels() m_ui.codeView->setColumnWidth(4, m_ui.codeView->width() - (40 + 80 + 80 + 250)); m_registers_model = std::make_unique(); + m_registers_model.get()->setCodeModel(m_code_model.get()); + m_registers_model.get()->setCodeView(m_ui.codeView); m_ui.registerView->setModel(m_registers_model.get()); // m_ui->registerView->resizeRowsToContents(); @@ -437,6 +590,7 @@ void DebuggerWindow::createModels() m_ui.breakpointsWidget->setColumnWidth(1, 80); m_ui.breakpointsWidget->setColumnWidth(2, 40); m_ui.breakpointsWidget->setRootIsDecorated(false); + m_ui.breakpointsWidget->setContextMenuPolicy(Qt::CustomContextMenu); } void DebuggerWindow::setUIEnabled(bool enabled) @@ -491,21 +645,21 @@ void DebuggerWindow::setMemoryViewRegion(Bus::MemoryRegion region) m_ui.memoryView->repaint(); } -void DebuggerWindow::toggleBreakpoint(VirtualMemoryAddress address) +void DebuggerWindow::toggleBreakpoint(DebugAddress dbg) { - const bool new_bp_state = !CPU::HasBreakpointAtAddress(address); + const bool new_bp_state = !CPU::HasBreakpointAtAddress(dbg.address); if (new_bp_state) { - if (!CPU::AddBreakpoint(address, false)) + if (!CPU::AddBreakpoint(dbg, false)) return; } else { - if (!CPU::RemoveBreakpoint(address)) + if (!CPU::RemoveBreakpoint(dbg)) return; } - m_code_model->setBreakpointState(address, new_bp_state); + m_code_model->setBreakpointState(dbg.address, new_bp_state); refreshBreakpointList(); } @@ -562,10 +716,10 @@ void DebuggerWindow::refreshBreakpointList() for (const CPU::Breakpoint& bp : bps) { QTreeWidgetItem* item = new QTreeWidgetItem(); - item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setFlags(item->flags() | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable); item->setCheckState(0, bp.enabled ? Qt::Checked : Qt::Unchecked); item->setText(0, QString::asprintf("%u", bp.number)); - item->setText(1, QString::asprintf("0x%08X", bp.address)); + item->setText(1, QString::asprintf("0x%08X", bp.dbg.address)); item->setText(2, QString::asprintf("%u", bp.hit_count)); m_ui.breakpointsWidget->addTopLevelItem(item); } diff --git a/src/duckstation-qt/debuggerwindow.h b/src/duckstation-qt/debuggerwindow.h index e7486b7a29..2c7e313559 100644 --- a/src/duckstation-qt/debuggerwindow.h +++ b/src/duckstation-qt/debuggerwindow.h @@ -20,6 +20,7 @@ class DebuggerWindow : public QMainWindow public: explicit DebuggerWindow(QWidget* parent = nullptr); ~DebuggerWindow(); + void SetActive(bool status); Q_SIGNALS: void closed(); @@ -50,9 +51,14 @@ private Q_SLOTS: void onStepIntoActionTriggered(); void onStepOverActionTriggered(); void onStepOutActionTriggered(); + void onCodeViewPressed(QModelIndex index); + void onCodeViewCurrentChanged(QModelIndex current, QModelIndex previous); void onCodeViewItemActivated(QModelIndex index); void onMemorySearchTriggered(); void onMemorySearchStringChanged(const QString&); + void onBreakpointsWidgetItemChanged(QTreeWidgetItem* item, int column); + void onBreakpointWidgetItemDoubleClicked(QTreeWidgetItem* item, int column); + void onBreakpointsWidgetMenuRequested(const QPoint& pos); private: @@ -62,13 +68,17 @@ private Q_SLOTS: void createModels(); void setUIEnabled(bool enabled); void setMemoryViewRegion(Bus::MemoryRegion region); - void toggleBreakpoint(VirtualMemoryAddress address); + void toggleBreakpoint(DebugAddress dbg); void clearBreakpoints(); std::optional getSelectedCodeAddress(); bool tryFollowLoadStore(VirtualMemoryAddress address); void scrollToCodeAddress(VirtualMemoryAddress address); bool scrollToMemoryAddress(VirtualMemoryAddress address); void refreshBreakpointList(); + void editBreakpoint(QTreeWidgetItem* item); + void deleteBreakpoint(QTreeWidgetItem* item); + void refreshCodeViewSelectedAddress(); + bool is_active = false; Ui::DebuggerWindow m_ui; diff --git a/src/duckstation-qt/debuggerwindow.ui b/src/duckstation-qt/debuggerwindow.ui index ac04ec0e44..ef400949a3 100644 --- a/src/duckstation-qt/debuggerwindow.ui +++ b/src/duckstation-qt/debuggerwindow.ui @@ -36,15 +36,14 @@ - - + + - @@ -82,7 +81,7 @@ - + @@ -206,6 +205,43 @@ + + + + + + 1 Byte + + + true + + + buttonGroup + + + + + + + 2 Bytes + + + buttonGroup + + + + + + + 4 Bytes + + + buttonGroup + + + + + @@ -481,8 +517,6 @@ Ctrl+T - - @@ -496,4 +530,7 @@ + + + diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index c76c47979b..dc4558ab88 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -1826,6 +1826,7 @@ void MainWindow::openCPUDebugger() m_debugger_window->setWindowIcon(windowIcon()); connect(m_debugger_window, &DebuggerWindow::closed, this, &MainWindow::onCPUDebuggerClosed); m_debugger_window->show(); + m_debugger_window->SetActive(true); // the debugger will miss the pause event above (or we were already paused), so fire it now m_debugger_window->onEmulationPaused(true); @@ -1834,6 +1835,7 @@ void MainWindow::openCPUDebugger() void MainWindow::onCPUDebuggerClosed() { Assert(m_debugger_window); + m_debugger_window->SetActive(false); m_debugger_window->deleteLater(); m_debugger_window = nullptr; } diff --git a/src/duckstation-qt/memoryviewwidget.cpp b/src/duckstation-qt/memoryviewwidget.cpp index 86d7a849e9..578b7f321c 100644 --- a/src/duckstation-qt/memoryviewwidget.cpp +++ b/src/duckstation-qt/memoryviewwidget.cpp @@ -8,6 +8,7 @@ MemoryViewWidget::MemoryViewWidget(QWidget* parent /* = nullptr */, size_t addre : QAbstractScrollArea(parent) { m_bytes_per_line = 16; + m_display_size = 1; updateMetrics(); @@ -88,6 +89,12 @@ void MemoryViewWidget::setFont(const QFont& font) updateMetrics(); } +void MemoryViewWidget::setDisplaySize(int display_size) +{ + m_display_size = display_size; + adjustContent(); +} + void MemoryViewWidget::resizeEvent(QResizeEvent*) { adjustContent(); @@ -116,6 +123,7 @@ void MemoryViewWidget::paintEvent(QPaintEvent*) y += m_char_height; + // Painting addresses const unsigned num_rows = static_cast(m_end_offset - m_start_offset) / m_bytes_per_line; for (unsigned row = 0; row <= num_rows; row++) { @@ -139,38 +147,51 @@ void MemoryViewWidget::paintEvent(QPaintEvent*) const int HEX_CHAR_WIDTH = 4 * m_char_width; x = lx - offsetX; - for (unsigned col = 0; col < m_bytes_per_line; col++) + for (unsigned col = 0; col < m_bytes_per_line / m_display_size; col++) { if ((col % 2) != 0) - painter.fillRect(x, 0, HEX_CHAR_WIDTH, height(), viewport()->palette().color(QPalette::AlternateBase)); + painter.fillRect(x, 0, HEX_CHAR_WIDTH * m_display_size, height(), viewport()->palette().color(QPalette::AlternateBase)); - x += HEX_CHAR_WIDTH; + x += HEX_CHAR_WIDTH * m_display_size; } + // Painting addresses indexes y = m_char_height; x = lx - offsetX + m_char_width; - for (unsigned col = 0; col < m_bytes_per_line; col++) + for (unsigned col = 0; col < m_bytes_per_line; col += m_display_size) { painter.drawText(x, y, QString::asprintf("%02X", col)); - x += HEX_CHAR_WIDTH; + x += HEX_CHAR_WIDTH * m_display_size; } painter.drawLine(0, y + 3, width(), y + 3); y += m_char_height; + // Painting adresses values size_t offset = m_start_offset; for (unsigned row = 0; row <= num_rows; row++) { x = lx - offsetX + m_char_width; - for (unsigned col = 0; col < m_bytes_per_line && offset < m_data_size; col++, offset++) + for (unsigned col = 0; col < (m_bytes_per_line / m_display_size) && offset < m_data_size; col++, offset += m_display_size) { - unsigned char value; + unsigned int value; std::memcpy(&value, static_cast(m_data) + offset, sizeof(value)); if (offset >= m_highlight_start && offset < m_highlight_end) painter.fillRect(x - m_char_width, y - m_char_height + 3, HEX_CHAR_WIDTH, m_char_height, highlight_color); - painter.drawText(x, y, QString::asprintf("%02X", value)); - x += HEX_CHAR_WIDTH; + if (m_display_size == 1) + { + painter.drawText(x, y, QString::asprintf("%02X", value & 0xFF)); + } + else if (m_display_size == 2) + { + painter.drawText(x, y, QString::asprintf("%04X", value & 0xFFFF)); + } + else + { + painter.drawText(x, y, QString::asprintf("%08X", value)); + } + x += HEX_CHAR_WIDTH * m_display_size; } y += m_char_height; } @@ -180,6 +201,7 @@ void MemoryViewWidget::paintEvent(QPaintEvent*) lx += m_char_width; + // Painting address ASCII index y = m_char_height; x = (lx - offsetX); for (unsigned col = 0; col < m_bytes_per_line; col++) @@ -191,6 +213,7 @@ void MemoryViewWidget::paintEvent(QPaintEvent*) y += m_char_height; + // Painting address ASCII offset = m_start_offset; for (unsigned row = 0; row <= num_rows; row++) { diff --git a/src/duckstation-qt/memoryviewwidget.h b/src/duckstation-qt/memoryviewwidget.h index 7601249893..ee4513f128 100644 --- a/src/duckstation-qt/memoryviewwidget.h +++ b/src/duckstation-qt/memoryviewwidget.h @@ -20,6 +20,7 @@ class MemoryViewWidget : public QAbstractScrollArea void scrolltoOffset(size_t offset); void scrollToAddress(size_t address); void setFont(const QFont& font); + void setDisplaySize(int display_size); protected: void paintEvent(QPaintEvent*); @@ -45,6 +46,7 @@ private Q_SLOTS: size_t m_highlight_end = 0; unsigned m_bytes_per_line; + int m_display_size; int m_char_width; int m_char_height; diff --git a/src/duckstation-qt/qtutils.cpp b/src/duckstation-qt/qtutils.cpp index 94a2922d76..14c31ca448 100644 --- a/src/duckstation-qt/qtutils.cpp +++ b/src/duckstation-qt/qtutils.cpp @@ -1,14 +1,19 @@ #include "qtutils.h" #include "common/byte_stream.h" #include "common/make_array.h" +#include "core/types.h" #include #include #include #include +#include #include #include +#include +#include #include #include +#include #include #include #include @@ -745,7 +750,7 @@ void FillComboBoxWithEmulationSpeeds(QComboBox* cb) } } -std::optional PromptForAddress(QWidget* parent, const QString& title, const QString& label, bool code) +std::optional PromptForAddress(QWidget* parent, const QString& title, bool code) { const QString address_str( QInputDialog::getText(parent, title, qApp->translate("DebuggerWindow", "Enter memory address:"))); @@ -772,4 +777,148 @@ std::optional PromptForAddress(QWidget* parent, const QString& title, return address; } +DebugAddress PromptForDebugAddress(QWidget* parent, const QString& title, const QString& default_address, + const QString& default_size, int default_read, int default_write, + int default_changed, int default_exec) +{ + DebugAddress ret; + + QDialog* display = new QDialog(parent); + display->setWindowTitle("New Breakpoint"); + + QGridLayout* grid = new QGridLayout(); + + QLabel* address_label = new QLabel(parent); + address_label->setText("Enter memory address (hex):"); + + QLineEdit* address_line = new QLineEdit(); + address_line->setText(default_address); + + QLabel* address_size_label = new QLabel(parent); + address_size_label->setText("Enter data size:"); + + QLineEdit* address_size_line = new QLineEdit(); + address_size_line->setText(default_size); + + QLabel* dbg_label = new QLabel(parent); + dbg_label->setText("Break when this memory is:"); + + QWidget* checkbox_display = new QWidget(); + QGridLayout* checkbox_grid = new QGridLayout(); + + QCheckBox* is_read = new QCheckBox("Read"); + is_read->setCheckState((Qt::CheckState) default_read); + QCheckBox* is_write = new QCheckBox("Written"); + is_write->setCheckState((Qt::CheckState) default_write); + QCheckBox* is_changed = new QCheckBox("Changed"); + is_changed->setCheckState((Qt::CheckState) default_changed); + QCheckBox* is_exec = new QCheckBox("Executed"); + is_exec->setCheckState((Qt::CheckState) default_exec); + + checkbox_display->setLayout(checkbox_grid); + + checkbox_grid->addWidget(is_read, 0, 0); + checkbox_grid->addWidget(is_write, 0, 1); + checkbox_grid->addWidget(is_changed, 1, 0); + checkbox_grid->addWidget(is_exec, 1, 1); + + QDialogButtonBox* button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + QObject::connect(button_box, SIGNAL(accepted()), display, SLOT(accept())); + QObject::connect(button_box, SIGNAL(rejected()), display, SLOT(reject())); + + grid->addWidget(address_label, 0, 0); + grid->addWidget(address_line, 1, 0); + grid->addWidget(address_size_label, 2, 0); + grid->addWidget(address_size_line, 3, 0); + grid->addWidget(dbg_label, 4, 0); + grid->addWidget(checkbox_display, 5, 0, Qt::AlignLeft); + grid->addWidget(button_box, 7, 0, Qt::AlignCenter); + + display->setLayout(grid); + int res = display->exec(); + + if (res == QDialog::Accepted) + { + if (is_read->checkState() == Qt::Unchecked && is_write->checkState() == Qt::Unchecked && + is_changed->checkState() == Qt::Unchecked && is_exec->checkState() == Qt::Unchecked) + { + is_exec->setCheckState(Qt::Checked); // if nothing was selected, assume it is an exec breakpoint + } + + bool ok; + QString address_str = address_line->text(); + u32 address; + if (address_str.startsWith("0x")) + address = address_str.mid(2).toUInt(&ok, 16); + else + address = address_str.toUInt(&ok, 16); + if (is_exec->checkState() == Qt::Checked) + address = address & 0xFFFFFFFC; // disassembly address should be divisible by 4 so make sure + + if (!ok) + { + QMessageBox::critical( + parent, title, + qApp->translate("New Breakpoint", "Invalid address. It should be in hex (0x12345678 or 12345678)")); + return ret; + } + + ret.address = address; + + if (is_read->checkState() == Qt::Checked) + ret.debug_type = ret.debug_type | DebugType::Read; + if (is_write->checkState() == Qt::Checked) + ret.debug_type = ret.debug_type | DebugType::Written; + if (is_changed->checkState() == Qt::Checked) + ret.debug_type = ret.debug_type | DebugType::Changed; + if (is_exec->checkState() == Qt::Checked) + ret.debug_type = ret.debug_type | DebugType::Executed; + + QString size_str = address_size_line->text(); + u32 address_size; + if (size_str.startsWith("0x")) + address_size = size_str.mid(2).toUInt(&ok, 16); + else + address_size = size_str.toUInt(&ok); + + if (!ok) + { + if (is_exec->checkState() == Qt::Checked) + { + ret.size = 4; // assume 4 bytes for execution breakpoints + } + else + { + if (size_str.size() == 0) + { + // assume the user is using the greatest possible size among word, halfword and byte + if (!(address & 0x3)) + ret.size = 4; + else if (!(address & 0x1)) + ret.size = 2; + else + ret.size = 1; + } + else + { + QMessageBox::critical( + parent, title, + qApp->translate("New Breakpoint", "Invalid size. It should be in hex (0xF) or decimal (15).")); + ret.debug_type = 0; + return ret; + } + } + } + else + { + ret.size = address_size; + } + + return ret; + } + + return ret; +} + } // namespace QtUtils diff --git a/src/duckstation-qt/qtutils.h b/src/duckstation-qt/qtutils.h index fb18a836db..52442841af 100644 --- a/src/duckstation-qt/qtutils.h +++ b/src/duckstation-qt/qtutils.h @@ -1,4 +1,5 @@ #pragma once +#include #include #include #include @@ -72,6 +73,12 @@ void FillComboBoxWithMSAAModes(QComboBox* cb); void FillComboBoxWithEmulationSpeeds(QComboBox* cb); /// Prompts for an address in hex. -std::optional PromptForAddress(QWidget* parent, const QString& title, const QString& label, bool code); +std::optional PromptForAddress(QWidget* parent, const QString& title, bool code); + +/// Prompts for an address in hex, with debug conditions +DebugAddress PromptForDebugAddress(QWidget* parent, const QString& title, const QString& default_address, + const QString& default_size, int default_read = Qt::Unchecked, + int default_write = Qt::Unchecked, int default_changed = Qt::Unchecked, + int default_exec = Qt::Unchecked); } // namespace QtUtils \ No newline at end of file