From 3b23542ec953730f4fb98464d844d58c835d0147 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Thu, 17 Dec 2020 01:18:02 +1000 Subject: [PATCH] CPU: Provide debugger/breakpoint/step functionality --- src/core/cpu_code_cache.cpp | 6 +- src/core/cpu_core.cpp | 335 ++++++++++++++++++++- src/core/cpu_core.h | 36 +++ src/core/cpu_disasm.cpp | 206 ++++++++++--- src/core/cpu_disasm.h | 3 +- src/core/cpu_recompiler_code_generator.cpp | 2 +- src/core/system.cpp | 55 +++- src/core/system.h | 1 + src/core/timing_event.cpp | 3 +- 9 files changed, 579 insertions(+), 68 deletions(-) diff --git a/src/core/cpu_code_cache.cpp b/src/core/cpu_code_cache.cpp index 3c196d365..ceea4d7d1 100644 --- a/src/core/cpu_code_cache.cpp +++ b/src/core/cpu_code_cache.cpp @@ -174,7 +174,9 @@ static void ExecuteImpl() { CodeBlockKey next_block_key; + g_using_interpreter = false; g_state.frame_done = false; + while (!g_state.frame_done) { if (HasPendingInterrupt()) @@ -301,7 +303,9 @@ CodeBlock::HostCodePointer* GetFastMapPointer() void ExecuteRecompiler() { + g_using_interpreter = false; g_state.frame_done = false; + #if 0 while (!g_state.frame_done) { @@ -568,7 +572,7 @@ bool CompileBlock(CodeBlock* block) Log_DebugPrintf("Block at 0x%08X", block->GetPC()); for (const CodeBlockInstruction& cbi : block->instructions) { - CPU::DisassembleInstruction(&disasm, cbi.pc, cbi.instruction.bits, nullptr); + CPU::DisassembleInstruction(&disasm, cbi.pc, cbi.instruction.bits); Log_DebugPrintf("[%s %s 0x%08X] %08X %s", cbi.is_branch_delay_slot ? "BD" : " ", cbi.is_load_delay_slot ? "LD" : " ", cbi.pc, cbi.instruction.bits, disasm.GetCharArray()); } diff --git a/src/core/cpu_core.cpp b/src/core/cpu_core.cpp index 1e75a8f7a..1cea04320 100644 --- a/src/core/cpu_core.cpp +++ b/src/core/cpu_core.cpp @@ -8,8 +8,10 @@ #include "cpu_disasm.h" #include "cpu_recompiler_thunks.h" #include "gte.h" +#include "host_interface.h" #include "pgxp.h" #include "settings.h" +#include "system.h" #include "timing_event.h" #include Log_SetChannel(CPU::Core); @@ -22,9 +24,16 @@ static void Branch(u32 target); static void FlushPipeline(); State g_state; +bool g_using_interpreter = false; bool TRACE_EXECUTION = false; bool LOG_EXECUTION = 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 bool s_single_step = false; + void WriteToExecutionLog(const char* format, ...) { static std::FILE* log_file = nullptr; @@ -53,6 +62,12 @@ void Initialize() // From nocash spec. g_state.cop0_regs.PRID = UINT32_C(0x00000002); + g_state.use_debug_dispatcher = false; + s_breakpoints.clear(); + s_breakpoint_counter = 1; + s_last_breakpoint_check_pc = INVALID_BREAKPOINT_PC; + s_single_step = false; + GTE::Initialize(); if (g_settings.gpu_pgxp_enable) @@ -63,6 +78,7 @@ void Shutdown() { // GTE::Shutdown(); PGXP::Shutdown(); + ClearBreakpoints(); } void Reset() @@ -427,7 +443,16 @@ ALWAYS_INLINE_RELEASE static void WriteCop0Reg(Cop0Reg reg, u32 value) static void PrintInstruction(u32 bits, u32 pc, Registers* regs) { TinyString instr; - DisassembleInstruction(&instr, pc, bits, regs); + TinyString comment; + DisassembleInstruction(&instr, pc, bits); + DisassembleInstructionComment(&comment, pc, bits, regs); + if (!comment.IsEmpty()) + { + for (u32 i = instr.GetLength(); i < 30; i++) + instr.AppendCharacter(' '); + instr.AppendString("; "); + instr.AppendString(comment); + } std::printf("%08x: %08x %s\n", pc, bits, instr.GetCharArray()); } @@ -435,7 +460,16 @@ static void PrintInstruction(u32 bits, u32 pc, Registers* regs) static void LogInstruction(u32 bits, u32 pc, Registers* regs) { TinyString instr; - DisassembleInstruction(&instr, pc, bits, regs); + TinyString comment; + DisassembleInstruction(&instr, pc, bits); + DisassembleInstructionComment(&comment, pc, bits, regs); + if (!comment.IsEmpty()) + { + for (u32 i = instr.GetLength(); i < 30; i++) + instr.AppendCharacter(' '); + instr.AppendString("; "); + instr.AppendString(comment); + } WriteToExecutionLog("%08x: %08x %s\n", pc, bits, instr.GetCharArray()); } @@ -1429,9 +1463,265 @@ void DispatchInterrupt() g_state.regs.pc); } -template +static void UpdateDebugDispatcherFlag() +{ + const bool has_any_breakpoints = !s_breakpoints.empty(); + + // TODO: cop0 breakpoints + + const bool use_debug_dispatcher = has_any_breakpoints; + if (use_debug_dispatcher == g_state.use_debug_dispatcher) + return; + + g_state.use_debug_dispatcher = use_debug_dispatcher; + ForceDispatcherExit(); +} + +void ForceDispatcherExit() +{ + // zero the downcount so we break out and switch + g_state.downcount = 0; + g_state.frame_done = true; +} + +bool HasAnyBreakpoints() +{ + return !s_breakpoints.empty(); +} + +bool HasBreakpointAtAddress(VirtualMemoryAddress address) +{ + for (const Breakpoint& bp : s_breakpoints) + { + if (bp.address == address) + return true; + } + + return false; +} + +BreakpointList GetBreakpointList(bool include_auto_clear, bool include_callbacks) +{ + BreakpointList bps; + bps.reserve(s_breakpoints.size()); + + for (const Breakpoint& bp : s_breakpoints) + { + if (bp.callback && !include_callbacks) + continue; + if (bp.auto_clear && !include_auto_clear) + continue; + + bps.push_back(bp); + } + + return bps; +} + +bool AddBreakpoint(VirtualMemoryAddress address, bool auto_clear, bool enabled) +{ + if (HasBreakpointAtAddress(address)) + return false; + + Log_InfoPrintf("Adding breakpoint at %08X, auto clear = %u", address, static_cast(auto_clear)); + + Breakpoint bp{address, 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); + } + + return true; +} + +bool AddBreakpointWithCallback(VirtualMemoryAddress address, BreakpointCallback callback) +{ + if (HasBreakpointAtAddress(address)) + return false; + + Log_InfoPrintf("Adding breakpoint with callback at %08X", address); + + Breakpoint bp{address, callback, 0, 0, false, true}; + s_breakpoints.push_back(std::move(bp)); + UpdateDebugDispatcherFlag(); + return true; +} + +bool RemoveBreakpoint(VirtualMemoryAddress address) +{ + auto it = std::find_if(s_breakpoints.begin(), s_breakpoints.end(), + [address](const Breakpoint& bp) { return bp.address == address; }); + if (it == s_breakpoints.end()) + return false; + + g_host_interface->ReportFormattedDebuggerMessage( + g_host_interface->TranslateString("DebuggerMessage", "Removed breakpoint at 0x%08X."), address); + + s_breakpoints.erase(it); + UpdateDebugDispatcherFlag(); + + if (address == s_last_breakpoint_check_pc) + s_last_breakpoint_check_pc = INVALID_BREAKPOINT_PC; + + return true; +} + +void ClearBreakpoints() +{ + s_breakpoints.clear(); + s_breakpoint_counter = 0; + s_last_breakpoint_check_pc = INVALID_BREAKPOINT_PC; + UpdateDebugDispatcherFlag(); +} + +bool AddStepOverBreakpoint() +{ + u32 bp_pc = g_state.regs.pc; + + Instruction inst; + if (!SafeReadInstruction(bp_pc, &inst.bits)) + return false; + + bp_pc += sizeof(Instruction); + + if (!IsCallInstruction(inst)) + { + g_host_interface->ReportFormattedDebuggerMessage( + g_host_interface->TranslateString("DebuggerMessage", "0x%08X is not a call instruction."), g_state.regs.pc); + return false; + } + + if (!SafeReadInstruction(bp_pc, &inst.bits)) + return false; + + if (IsBranchInstruction(inst)) + { + g_host_interface->ReportFormattedDebuggerMessage( + g_host_interface->TranslateString("DebuggerMessage", "Can't step over double branch at 0x%08X"), g_state.regs.pc); + return false; + } + + // skip the delay slot + bp_pc += sizeof(Instruction); + + g_host_interface->ReportFormattedDebuggerMessage( + g_host_interface->TranslateString("DebuggerMessage", "Stepping over to 0x%08X."), bp_pc); + + return AddBreakpoint(bp_pc, true); +} + +bool AddStepOutBreakpoint(u32 max_instructions_to_search) +{ + // find the branch-to-ra instruction. + u32 ret_pc = g_state.regs.pc; + for (u32 i = 0; i < max_instructions_to_search; i++) + { + ret_pc += sizeof(Instruction); + + Instruction inst; + if (!SafeReadInstruction(ret_pc, &inst.bits)) + { + g_host_interface->ReportFormattedDebuggerMessage( + g_host_interface->TranslateString("DebuggerMessage", + "Instruction read failed at %08X while searching for function end."), + ret_pc); + return false; + } + + if (IsReturnInstruction(inst)) + { + g_host_interface->ReportFormattedDebuggerMessage( + g_host_interface->TranslateString("DebuggerMessage", "Stepping out to 0x%08X."), ret_pc); + + return AddBreakpoint(ret_pc, true); + } + } + + g_host_interface->ReportFormattedDebuggerMessage( + g_host_interface->TranslateString("DebuggerMessage", + "No return instruction found after %u instructions for step-out at %08X."), + max_instructions_to_search, g_state.regs.pc); + + return false; +} + +static ALWAYS_INLINE_RELEASE bool BreakpointCheck() +{ + const u32 pc = g_state.regs.pc; + + // single step - we want to break out after this instruction, so set a pending exit + // the bp check happens just before execution, so this is fine + if (s_single_step) + { + ForceDispatcherExit(); + s_single_step = false; + s_last_breakpoint_check_pc = pc; + return false; + } + + if (pc == s_last_breakpoint_check_pc) + { + // we don't want to trigger the same breakpoint which just paused us repeatedly. + return false; + } + + u32 count = static_cast(s_breakpoints.size()); + for (u32 i = 0; i < count;) + { + Breakpoint& bp = s_breakpoints[i]; + if (!bp.enabled || bp.address != pc) + { + i++; + continue; + } + + bp.hit_count++; + + if (bp.callback) + { + // if callback returns false, the bp is no longer recorded + if (!bp.callback(pc)) + { + s_breakpoints.erase(s_breakpoints.begin() + i); + count--; + UpdateDebugDispatcherFlag(); + } + else + { + i++; + } + } + else + { + g_host_interface->PauseSystem(true); + + if (bp.auto_clear) + { + g_host_interface->ReportFormattedDebuggerMessage("Stopped execution at 0x%08X.", pc); + s_breakpoints.erase(s_breakpoints.begin() + i); + count--; + UpdateDebugDispatcherFlag(); + } + else + { + g_host_interface->ReportFormattedDebuggerMessage("Hit breakpoint %u at 0x%08X.", bp.number, pc); + i++; + } + } + } + + s_last_breakpoint_check_pc = pc; + return System::IsPaused(); +} + +template static void ExecuteImpl() { + g_using_interpreter = true; g_state.frame_done = false; while (!g_state.frame_done) { @@ -1442,6 +1732,15 @@ static void ExecuteImpl() if (HasPendingInterrupt() && !g_state.interrupt_delay) DispatchInterrupt(); + if constexpr (debug) + { + if (BreakpointCheck()) + { + // continue is measurably faster than break on msvc for some reason + continue; + } + } + g_state.interrupt_delay = false; g_state.pending_ticks++; @@ -1454,7 +1753,7 @@ static void ExecuteImpl() g_state.branch_was_taken = false; g_state.exception_raised = false; - // fetch the next instruction + // fetch the next instruction - even if this fails, it'll still refetch on the flush so we can continue if (!FetchInstruction()) continue; @@ -1482,16 +1781,38 @@ void Execute() if (g_settings.gpu_pgxp_enable) { if (g_settings.gpu_pgxp_cpu) - ExecuteImpl(); + ExecuteImpl(); else - ExecuteImpl(); + ExecuteImpl(); } else { - ExecuteImpl(); + ExecuteImpl(); } } +void ExecuteDebug() +{ + if (g_settings.gpu_pgxp_enable) + { + if (g_settings.gpu_pgxp_cpu) + ExecuteImpl(); + else + ExecuteImpl(); + } + else + { + ExecuteImpl(); + } +} + +void SingleStep() +{ + s_single_step = true; + ExecuteDebug(); + g_host_interface->ReportFormattedDebuggerMessage("Stepped to 0x%08X.", g_state.regs.pc); +} + namespace CodeCache { template diff --git a/src/core/cpu_core.h b/src/core/cpu_core.h index edff8a2aa..718cd78ba 100644 --- a/src/core/cpu_core.h +++ b/src/core/cpu_core.h @@ -5,6 +5,7 @@ #include "types.h" #include #include +#include class StateWrapper; @@ -74,6 +75,9 @@ struct State // GTE registers are stored here so we can access them on ARM with a single instruction GTE::Regs gte_regs = {}; + // 4 bytes of padding here on x64 + bool use_debug_dispatcher = false; + u8* fastmem_base = nullptr; // data cache (used as scratchpad) @@ -83,6 +87,7 @@ struct State }; extern State g_state; +extern bool g_using_interpreter; void Initialize(); void Shutdown(); @@ -92,6 +97,11 @@ void ClearICache(); /// Executes interpreter loop. void Execute(); +void ExecuteDebug(); +void SingleStep(); + +// Forces an early exit from the CPU dispatcher. +void ForceDispatcherExit(); ALWAYS_INLINE Registers& GetRegs() { return g_state.regs; } @@ -122,6 +132,32 @@ void DisassembleAndPrint(u32 addr, u32 instructions_before, u32 instructions_aft // Write to CPU execution log file. void WriteToExecutionLog(const char* format, ...); +// Breakpoint callback - if the callback returns false, the breakpoint will be removed. +using BreakpointCallback = bool (*)(VirtualMemoryAddress address); + +struct Breakpoint +{ + VirtualMemoryAddress address; + BreakpointCallback callback; + u32 number; + u32 hit_count; + bool auto_clear; + bool enabled; +}; + +using BreakpointList = std::vector; + +// Breakpoints +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); +void ClearBreakpoints(); +bool AddStepOverBreakpoint(); +bool AddStepOutBreakpoint(u32 max_instructions_to_search = 1000); + extern bool TRACE_EXECUTION; extern bool LOG_EXECUTION; diff --git a/src/core/cpu_disasm.cpp b/src/core/cpu_disasm.cpp index 6a797ace4..3e0baf246 100644 --- a/src/core/cpu_disasm.cpp +++ b/src/core/cpu_disasm.cpp @@ -1,6 +1,6 @@ #include "cpu_disasm.h" -#include "cpu_core.h" #include "common/assert.h" +#include "cpu_core.h" #include namespace CPU { @@ -167,12 +167,10 @@ static const std::array, 5> s_cop_c static const std::array, 1> s_cop0_table = {{{Cop0Instruction::rfe, "rfe"}}}; -static void FormatInstruction(String* dest, const Instruction inst, u32 pc, Registers* regs, const char* format) +static void FormatInstruction(String* dest, const Instruction inst, u32 pc, const char* format) { dest->Clear(); - TinyString comment; - const char* str = format; while (*str != '\0') { @@ -186,34 +184,16 @@ static void FormatInstruction(String* dest, const Instruction inst, u32 pc, Regi if (std::strncmp(str, "rs", 2) == 0) { dest->AppendString(GetRegName(inst.r.rs)); - if (regs) - { - comment.AppendFormattedString("%s%s=0x%08X", comment.IsEmpty() ? "" : ", ", GetRegName(inst.r.rs), - regs->r[static_cast(inst.r.rs.GetValue())]); - } - str += 2; } else if (std::strncmp(str, "rt", 2) == 0) { dest->AppendString(GetRegName(inst.r.rt)); - if (regs) - { - comment.AppendFormattedString("%s%s=0x%08X", comment.IsEmpty() ? "" : ", ", GetRegName(inst.r.rt), - regs->r[static_cast(inst.r.rt.GetValue())]); - } - str += 2; } else if (std::strncmp(str, "rd", 2) == 0) { dest->AppendString(GetRegName(inst.r.rd)); - if (regs) - { - comment.AppendFormattedString("%s%s=0x%08X", comment.IsEmpty() ? "" : ", ", GetRegName(inst.r.rd), - regs->r[static_cast(inst.r.rd.GetValue())]); - } - str += 2; } else if (std::strncmp(str, "shamt", 5) == 0) @@ -242,12 +222,6 @@ static void FormatInstruction(String* dest, const Instruction inst, u32 pc, Regi { const s32 offset = static_cast(inst.i.imm_sext32()); dest->AppendFormattedString("%d(%s)", offset, GetRegName(inst.i.rs)); - if (regs) - { - comment.AppendFormattedString("%saddr=0x%08X", comment.IsEmpty() ? "" : ", ", - regs->r[static_cast(inst.i.rs.GetValue())] + offset); - } - str += 8; } else if (std::strncmp(str, "jt", 2) == 0) @@ -281,25 +255,95 @@ static void FormatInstruction(String* dest, const Instruction inst, u32 pc, Regi Panic("Unknown operand"); } } +} - if (!comment.IsEmpty()) +static void FormatComment(String* dest, const Instruction inst, u32 pc, Registers* regs, const char* format) +{ + const char* str = format; + while (*str != '\0') { - for (u32 i = dest->GetLength(); i < 30; i++) - dest->AppendCharacter(' '); - dest->AppendString("; "); - dest->AppendString(comment); + const char ch = *(str++); + if (ch != '$') + continue; + + if (std::strncmp(str, "rs", 2) == 0) + { + dest->AppendFormattedString("%s%s=0x%08X", dest->IsEmpty() ? "" : ", ", GetRegName(inst.r.rs), + regs->r[static_cast(inst.r.rs.GetValue())]); + + str += 2; + } + else if (std::strncmp(str, "rt", 2) == 0) + { + dest->AppendFormattedString("%s%s=0x%08X", dest->IsEmpty() ? "" : ", ", GetRegName(inst.r.rt), + regs->r[static_cast(inst.r.rt.GetValue())]); + str += 2; + } + else if (std::strncmp(str, "rd", 2) == 0) + { + dest->AppendFormattedString("%s%s=0x%08X", dest->IsEmpty() ? "" : ", ", GetRegName(inst.r.rd), + regs->r[static_cast(inst.r.rd.GetValue())]); + str += 2; + } + else if (std::strncmp(str, "shamt", 5) == 0) + { + str += 5; + } + else if (std::strncmp(str, "immu", 4) == 0) + { + str += 4; + } + else if (std::strncmp(str, "imm", 3) == 0) + { + str += 3; + } + else if (std::strncmp(str, "rel", 3) == 0) + { + str += 3; + } + else if (std::strncmp(str, "offsetrs", 8) == 0) + { + const s32 offset = static_cast(inst.i.imm_sext32()); + dest->AppendFormattedString("%saddr=0x%08X", dest->IsEmpty() ? "" : ", ", + regs->r[static_cast(inst.i.rs.GetValue())] + offset); + str += 8; + } + else if (std::strncmp(str, "jt", 2) == 0) + { + str += 2; + } + else if (std::strncmp(str, "copcc", 5) == 0) + { + str += 5; + } + else if (std::strncmp(str, "coprd", 5) == 0) + { + str += 5; + } + else if (std::strncmp(str, "coprt", 5) == 0) + { + str += 5; + } + else if (std::strncmp(str, "cop", 3) == 0) + { + str += 3; + } + else + { + Panic("Unknown operand"); + } } } template -void FormatCopInstruction(String* dest, u32 pc, Registers* regs, const Instruction inst, - const std::pair* table, size_t table_size, T table_key) +void FormatCopInstruction(String* dest, u32 pc, const Instruction inst, const std::pair* table, + size_t table_size, T table_key) { for (size_t i = 0; i < table_size; i++) { if (table[i].first == table_key) { - FormatInstruction(dest, inst, pc, regs, table[i].second); + FormatInstruction(dest, inst, pc, table[i].second); return; } } @@ -307,13 +351,27 @@ void FormatCopInstruction(String* dest, u32 pc, Registers* regs, const Instructi dest->Format("", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue()); } -void DisassembleInstruction(String* dest, u32 pc, u32 bits, Registers* regs) +template +void FormatCopComment(String* dest, u32 pc, Registers* regs, const Instruction inst, + const std::pair* table, size_t table_size, T table_key) +{ + for (size_t i = 0; i < table_size; i++) + { + if (table[i].first == table_key) + { + FormatComment(dest, inst, pc, regs, table[i].second); + return; + } + } +} + +void DisassembleInstruction(String* dest, u32 pc, u32 bits) { const Instruction inst{bits}; switch (inst.op) { case InstructionOp::funct: - FormatInstruction(dest, inst, pc, regs, s_special_table[static_cast(inst.r.funct.GetValue())]); + FormatInstruction(dest, inst, pc, s_special_table[static_cast(inst.r.funct.GetValue())]); return; case InstructionOp::cop0: @@ -323,8 +381,7 @@ void DisassembleInstruction(String* dest, u32 pc, u32 bits, Registers* regs) { if (inst.cop.IsCommonInstruction()) { - FormatCopInstruction(dest, pc, regs, inst, s_cop_common_table.data(), s_cop_common_table.size(), - inst.cop.CommonOp()); + FormatCopInstruction(dest, pc, inst, s_cop_common_table.data(), s_cop_common_table.size(), inst.cop.CommonOp()); } else { @@ -332,7 +389,7 @@ void DisassembleInstruction(String* dest, u32 pc, u32 bits, Registers* regs) { case InstructionOp::cop0: { - FormatCopInstruction(dest, pc, regs, inst, s_cop0_table.data(), s_cop0_table.size(), inst.cop.Cop0Op()); + FormatCopInstruction(dest, pc, inst, s_cop0_table.data(), s_cop0_table.size(), inst.cop.Cop0Op()); } break; @@ -356,14 +413,75 @@ void DisassembleInstruction(String* dest, u32 pc, u32 bits, Registers* regs) const bool bgez = ConvertToBoolUnchecked(rt & u8(1)); const bool link = ConvertToBoolUnchecked((rt >> 4) & u8(1)); if (link) - FormatInstruction(dest, inst, pc, regs, bgez ? "bgezal $rs, $rel" : "bltzal $rs, $rel"); + FormatInstruction(dest, inst, pc, bgez ? "bgezal $rs, $rel" : "bltzal $rs, $rel"); else - FormatInstruction(dest, inst, pc, regs, bgez ? "bgez $rs, $rel" : "bltz $rs, $rel"); + FormatInstruction(dest, inst, pc, bgez ? "bgez $rs, $rel" : "bltz $rs, $rel"); } break; default: - FormatInstruction(dest, inst, pc, regs, s_base_table[static_cast(inst.op.GetValue())]); + FormatInstruction(dest, inst, pc, s_base_table[static_cast(inst.op.GetValue())]); + break; + } +} + +void DisassembleInstructionComment(String* dest, u32 pc, u32 bits, Registers* regs) +{ + const Instruction inst{bits}; + switch (inst.op) + { + case InstructionOp::funct: + FormatComment(dest, inst, pc, regs, s_special_table[static_cast(inst.r.funct.GetValue())]); + return; + + case InstructionOp::cop0: + case InstructionOp::cop1: + case InstructionOp::cop2: + case InstructionOp::cop3: + { + if (inst.cop.IsCommonInstruction()) + { + FormatCopComment(dest, pc, regs, inst, s_cop_common_table.data(), s_cop_common_table.size(), + inst.cop.CommonOp()); + } + else + { + switch (inst.op) + { + case InstructionOp::cop0: + { + FormatCopComment(dest, pc, regs, inst, s_cop0_table.data(), s_cop0_table.size(), inst.cop.Cop0Op()); + } + break; + + case InstructionOp::cop1: + case InstructionOp::cop2: + case InstructionOp::cop3: + default: + { + dest->Format("", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue()); + } + break; + } + } + } + break; + + // special case for bltz/bgez{al} + case InstructionOp::b: + { + const u8 rt = static_cast(inst.i.rt.GetValue()); + const bool bgez = ConvertToBoolUnchecked(rt & u8(1)); + const bool link = ConvertToBoolUnchecked((rt >> 4) & u8(1)); + if (link) + FormatComment(dest, inst, pc, regs, bgez ? "bgezal $rs, $rel" : "bltzal $rs, $rel"); + else + FormatComment(dest, inst, pc, regs, bgez ? "bgez $rs, $rel" : "bltz $rs, $rel"); + } + break; + + default: + FormatComment(dest, inst, pc, regs, s_base_table[static_cast(inst.op.GetValue())]); break; } } diff --git a/src/core/cpu_disasm.h b/src/core/cpu_disasm.h index c807f6c3c..e99d06601 100644 --- a/src/core/cpu_disasm.h +++ b/src/core/cpu_disasm.h @@ -3,5 +3,6 @@ #include "cpu_types.h" namespace CPU { -void DisassembleInstruction(String* dest, u32 pc, u32 bits, Registers* regs = nullptr); +void DisassembleInstruction(String* dest, u32 pc, u32 bits); +void DisassembleInstructionComment(String* dest, u32 pc, u32 bits, Registers* regs); } // namespace CPU diff --git a/src/core/cpu_recompiler_code_generator.cpp b/src/core/cpu_recompiler_code_generator.cpp index 959221d38..68492b030 100644 --- a/src/core/cpu_recompiler_code_generator.cpp +++ b/src/core/cpu_recompiler_code_generator.cpp @@ -35,7 +35,7 @@ bool CodeGenerator::CompileBlock(CodeBlock* block, CodeBlock::HostCodePointer* o { #ifdef _DEBUG SmallString disasm; - DisassembleInstruction(&disasm, cbi->pc, cbi->instruction.bits, nullptr); + DisassembleInstruction(&disasm, cbi->pc, cbi->instruction.bits); Log_DebugPrintf("Compiling instruction '%s'", disasm.GetCharArray()); #endif diff --git a/src/core/system.cpp b/src/core/system.cpp index 7f50bb192..51ca574a0 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -111,9 +111,15 @@ State GetState() void SetState(State new_state) { + if (s_state == new_state) + return; + Assert(s_state == State::Paused || s_state == State::Running); Assert(new_state == State::Paused || new_state == State::Running); s_state = new_state; + + if (new_state == State::Paused) + CPU::ForceDispatcherExit(); } bool IsRunning() @@ -1163,30 +1169,55 @@ bool SaveState(ByteStream* state, u32 screenshot_size /* = 128 */) return true; } +void SingleStepCPU() +{ + const u32 old_frame_number = s_frame_number; + + s_frame_timer.Reset(); + + g_gpu->RestoreGraphicsAPIState(); + + CPU::SingleStep(); + + g_spu.GeneratePendingSamples(); + + if (s_frame_number != old_frame_number && s_cheat_list) + s_cheat_list->Apply(); + + g_gpu->ResetGraphicsAPIState(); +} + void RunFrame() { s_frame_timer.Reset(); g_gpu->RestoreGraphicsAPIState(); - switch (g_settings.cpu_execution_mode) + if (CPU::g_state.use_debug_dispatcher) { - case CPUExecutionMode::Recompiler: + CPU::ExecuteDebug(); + } + else + { + switch (g_settings.cpu_execution_mode) + { + case CPUExecutionMode::Recompiler: #ifdef WITH_RECOMPILER - CPU::CodeCache::ExecuteRecompiler(); + CPU::CodeCache::ExecuteRecompiler(); #else - CPU::CodeCache::Execute(); + CPU::CodeCache::Execute(); #endif - break; + break; - case CPUExecutionMode::CachedInterpreter: - CPU::CodeCache::Execute(); - break; + case CPUExecutionMode::CachedInterpreter: + CPU::CodeCache::Execute(); + break; - case CPUExecutionMode::Interpreter: - default: - CPU::Execute(); - break; + case CPUExecutionMode::Interpreter: + default: + CPU::Execute(); + break; + } } // Generate any pending samples from the SPU before sleeping, this way we reduce the chances of underruns. diff --git a/src/core/system.h b/src/core/system.h index 17d97f787..5f923fabb 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -144,6 +144,7 @@ bool SaveState(ByteStream* state, u32 screenshot_size = 128); /// Recreates the GPU component, saving/loading the state so it is preserved. Call when the GPU renderer changes. bool RecreateGPU(GPURenderer renderer, bool update_display = true); +void SingleStepCPU(); void RunFrame(); /// Sets target emulation speed. diff --git a/src/core/timing_event.cpp b/src/core/timing_event.cpp index d7fd02f1d..008cba496 100644 --- a/src/core/timing_event.cpp +++ b/src/core/timing_event.cpp @@ -48,8 +48,7 @@ std::unique_ptr CreateTimingEvent(std::string name, TickCount perio void UpdateCPUDowncount() { - if (!CPU::g_state.frame_done && - (!CPU::HasPendingInterrupt() || g_settings.cpu_execution_mode == CPUExecutionMode::Interpreter)) + if (!CPU::g_state.frame_done && (!CPU::HasPendingInterrupt() || CPU::g_using_interpreter)) { CPU::g_state.downcount = s_active_events_head->GetDowncount(); }