CPU: Provide debugger/breakpoint/step functionality

This commit is contained in:
Connor McLaughlin 2020-12-17 01:18:02 +10:00
parent a8af0f7ecb
commit 3b23542ec9
9 changed files with 579 additions and 68 deletions

View File

@ -174,7 +174,9 @@ static void ExecuteImpl()
{ {
CodeBlockKey next_block_key; CodeBlockKey next_block_key;
g_using_interpreter = false;
g_state.frame_done = false; g_state.frame_done = false;
while (!g_state.frame_done) while (!g_state.frame_done)
{ {
if (HasPendingInterrupt()) if (HasPendingInterrupt())
@ -301,7 +303,9 @@ CodeBlock::HostCodePointer* GetFastMapPointer()
void ExecuteRecompiler() void ExecuteRecompiler()
{ {
g_using_interpreter = false;
g_state.frame_done = false; g_state.frame_done = false;
#if 0 #if 0
while (!g_state.frame_done) while (!g_state.frame_done)
{ {
@ -568,7 +572,7 @@ bool CompileBlock(CodeBlock* block)
Log_DebugPrintf("Block at 0x%08X", block->GetPC()); Log_DebugPrintf("Block at 0x%08X", block->GetPC());
for (const CodeBlockInstruction& cbi : block->instructions) 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" : " ", 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()); cbi.is_load_delay_slot ? "LD" : " ", cbi.pc, cbi.instruction.bits, disasm.GetCharArray());
} }

View File

@ -8,8 +8,10 @@
#include "cpu_disasm.h" #include "cpu_disasm.h"
#include "cpu_recompiler_thunks.h" #include "cpu_recompiler_thunks.h"
#include "gte.h" #include "gte.h"
#include "host_interface.h"
#include "pgxp.h" #include "pgxp.h"
#include "settings.h" #include "settings.h"
#include "system.h"
#include "timing_event.h" #include "timing_event.h"
#include <cstdio> #include <cstdio>
Log_SetChannel(CPU::Core); Log_SetChannel(CPU::Core);
@ -22,9 +24,16 @@ static void Branch(u32 target);
static void FlushPipeline(); static void FlushPipeline();
State g_state; State g_state;
bool g_using_interpreter = false;
bool TRACE_EXECUTION = false; bool TRACE_EXECUTION = false;
bool LOG_EXECUTION = false; bool LOG_EXECUTION = false;
static constexpr u32 INVALID_BREAKPOINT_PC = UINT32_C(0xFFFFFFFF);
static std::vector<Breakpoint> 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, ...) void WriteToExecutionLog(const char* format, ...)
{ {
static std::FILE* log_file = nullptr; static std::FILE* log_file = nullptr;
@ -53,6 +62,12 @@ void Initialize()
// From nocash spec. // From nocash spec.
g_state.cop0_regs.PRID = UINT32_C(0x00000002); 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(); GTE::Initialize();
if (g_settings.gpu_pgxp_enable) if (g_settings.gpu_pgxp_enable)
@ -63,6 +78,7 @@ void Shutdown()
{ {
// GTE::Shutdown(); // GTE::Shutdown();
PGXP::Shutdown(); PGXP::Shutdown();
ClearBreakpoints();
} }
void Reset() 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) static void PrintInstruction(u32 bits, u32 pc, Registers* regs)
{ {
TinyString instr; 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()); 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) static void LogInstruction(u32 bits, u32 pc, Registers* regs)
{ {
TinyString instr; 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()); WriteToExecutionLog("%08x: %08x %s\n", pc, bits, instr.GetCharArray());
} }
@ -1429,9 +1463,265 @@ void DispatchInterrupt()
g_state.regs.pc); g_state.regs.pc);
} }
template<PGXPMode pgxp_mode> 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<unsigned>(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<u32>(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<PGXPMode pgxp_mode, bool debug>
static void ExecuteImpl() static void ExecuteImpl()
{ {
g_using_interpreter = true;
g_state.frame_done = false; g_state.frame_done = false;
while (!g_state.frame_done) while (!g_state.frame_done)
{ {
@ -1442,6 +1732,15 @@ static void ExecuteImpl()
if (HasPendingInterrupt() && !g_state.interrupt_delay) if (HasPendingInterrupt() && !g_state.interrupt_delay)
DispatchInterrupt(); 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.interrupt_delay = false;
g_state.pending_ticks++; g_state.pending_ticks++;
@ -1454,7 +1753,7 @@ static void ExecuteImpl()
g_state.branch_was_taken = false; g_state.branch_was_taken = false;
g_state.exception_raised = 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()) if (!FetchInstruction())
continue; continue;
@ -1482,16 +1781,38 @@ void Execute()
if (g_settings.gpu_pgxp_enable) if (g_settings.gpu_pgxp_enable)
{ {
if (g_settings.gpu_pgxp_cpu) if (g_settings.gpu_pgxp_cpu)
ExecuteImpl<PGXPMode::CPU>(); ExecuteImpl<PGXPMode::CPU, false>();
else else
ExecuteImpl<PGXPMode::Memory>(); ExecuteImpl<PGXPMode::Memory, false>();
} }
else else
{ {
ExecuteImpl<PGXPMode::Disabled>(); ExecuteImpl<PGXPMode::Disabled, false>();
} }
} }
void ExecuteDebug()
{
if (g_settings.gpu_pgxp_enable)
{
if (g_settings.gpu_pgxp_cpu)
ExecuteImpl<PGXPMode::CPU, true>();
else
ExecuteImpl<PGXPMode::Memory, true>();
}
else
{
ExecuteImpl<PGXPMode::Disabled, true>();
}
}
void SingleStep()
{
s_single_step = true;
ExecuteDebug();
g_host_interface->ReportFormattedDebuggerMessage("Stepped to 0x%08X.", g_state.regs.pc);
}
namespace CodeCache { namespace CodeCache {
template<PGXPMode pgxp_mode> template<PGXPMode pgxp_mode>

View File

@ -5,6 +5,7 @@
#include "types.h" #include "types.h"
#include <array> #include <array>
#include <optional> #include <optional>
#include <vector>
class StateWrapper; 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 registers are stored here so we can access them on ARM with a single instruction
GTE::Regs gte_regs = {}; GTE::Regs gte_regs = {};
// 4 bytes of padding here on x64
bool use_debug_dispatcher = false;
u8* fastmem_base = nullptr; u8* fastmem_base = nullptr;
// data cache (used as scratchpad) // data cache (used as scratchpad)
@ -83,6 +87,7 @@ struct State
}; };
extern State g_state; extern State g_state;
extern bool g_using_interpreter;
void Initialize(); void Initialize();
void Shutdown(); void Shutdown();
@ -92,6 +97,11 @@ void ClearICache();
/// Executes interpreter loop. /// Executes interpreter loop.
void Execute(); void Execute();
void ExecuteDebug();
void SingleStep();
// Forces an early exit from the CPU dispatcher.
void ForceDispatcherExit();
ALWAYS_INLINE Registers& GetRegs() { return g_state.regs; } 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. // Write to CPU execution log file.
void WriteToExecutionLog(const char* format, ...); 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<Breakpoint>;
// 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 TRACE_EXECUTION;
extern bool LOG_EXECUTION; extern bool LOG_EXECUTION;

View File

@ -1,6 +1,6 @@
#include "cpu_disasm.h" #include "cpu_disasm.h"
#include "cpu_core.h"
#include "common/assert.h" #include "common/assert.h"
#include "cpu_core.h"
#include <array> #include <array>
namespace CPU { namespace CPU {
@ -167,12 +167,10 @@ static const std::array<std::pair<CopCommonInstruction, const char*>, 5> s_cop_c
static const std::array<std::pair<Cop0Instruction, const char*>, 1> s_cop0_table = {{{Cop0Instruction::rfe, "rfe"}}}; static const std::array<std::pair<Cop0Instruction, const char*>, 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(); dest->Clear();
TinyString comment;
const char* str = format; const char* str = format;
while (*str != '\0') 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) if (std::strncmp(str, "rs", 2) == 0)
{ {
dest->AppendString(GetRegName(inst.r.rs)); dest->AppendString(GetRegName(inst.r.rs));
if (regs)
{
comment.AppendFormattedString("%s%s=0x%08X", comment.IsEmpty() ? "" : ", ", GetRegName(inst.r.rs),
regs->r[static_cast<u8>(inst.r.rs.GetValue())]);
}
str += 2; str += 2;
} }
else if (std::strncmp(str, "rt", 2) == 0) else if (std::strncmp(str, "rt", 2) == 0)
{ {
dest->AppendString(GetRegName(inst.r.rt)); dest->AppendString(GetRegName(inst.r.rt));
if (regs)
{
comment.AppendFormattedString("%s%s=0x%08X", comment.IsEmpty() ? "" : ", ", GetRegName(inst.r.rt),
regs->r[static_cast<u8>(inst.r.rt.GetValue())]);
}
str += 2; str += 2;
} }
else if (std::strncmp(str, "rd", 2) == 0) else if (std::strncmp(str, "rd", 2) == 0)
{ {
dest->AppendString(GetRegName(inst.r.rd)); dest->AppendString(GetRegName(inst.r.rd));
if (regs)
{
comment.AppendFormattedString("%s%s=0x%08X", comment.IsEmpty() ? "" : ", ", GetRegName(inst.r.rd),
regs->r[static_cast<u8>(inst.r.rd.GetValue())]);
}
str += 2; str += 2;
} }
else if (std::strncmp(str, "shamt", 5) == 0) 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<s32>(inst.i.imm_sext32()); const s32 offset = static_cast<s32>(inst.i.imm_sext32());
dest->AppendFormattedString("%d(%s)", offset, GetRegName(inst.i.rs)); dest->AppendFormattedString("%d(%s)", offset, GetRegName(inst.i.rs));
if (regs)
{
comment.AppendFormattedString("%saddr=0x%08X", comment.IsEmpty() ? "" : ", ",
regs->r[static_cast<u8>(inst.i.rs.GetValue())] + offset);
}
str += 8; str += 8;
} }
else if (std::strncmp(str, "jt", 2) == 0) 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"); 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++) const char ch = *(str++);
dest->AppendCharacter(' '); if (ch != '$')
dest->AppendString("; "); continue;
dest->AppendString(comment);
if (std::strncmp(str, "rs", 2) == 0)
{
dest->AppendFormattedString("%s%s=0x%08X", dest->IsEmpty() ? "" : ", ", GetRegName(inst.r.rs),
regs->r[static_cast<u8>(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<u8>(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<u8>(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<s32>(inst.i.imm_sext32());
dest->AppendFormattedString("%saddr=0x%08X", dest->IsEmpty() ? "" : ", ",
regs->r[static_cast<u8>(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<typename T> template<typename T>
void FormatCopInstruction(String* dest, u32 pc, Registers* regs, const Instruction inst, void FormatCopInstruction(String* dest, u32 pc, const Instruction inst, const std::pair<T, const char*>* table,
const std::pair<T, const char*>* table, size_t table_size, T table_key) size_t table_size, T table_key)
{ {
for (size_t i = 0; i < table_size; i++) for (size_t i = 0; i < table_size; i++)
{ {
if (table[i].first == table_key) if (table[i].first == table_key)
{ {
FormatInstruction(dest, inst, pc, regs, table[i].second); FormatInstruction(dest, inst, pc, table[i].second);
return; return;
} }
} }
@ -307,13 +351,27 @@ void FormatCopInstruction(String* dest, u32 pc, Registers* regs, const Instructi
dest->Format("<cop%u 0x%08X>", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue()); dest->Format("<cop%u 0x%08X>", ZeroExtend32(inst.cop.cop_n.GetValue()), inst.cop.imm25.GetValue());
} }
void DisassembleInstruction(String* dest, u32 pc, u32 bits, Registers* regs) template<typename T>
void FormatCopComment(String* dest, u32 pc, Registers* regs, const Instruction inst,
const std::pair<T, const char*>* 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}; const Instruction inst{bits};
switch (inst.op) switch (inst.op)
{ {
case InstructionOp::funct: case InstructionOp::funct:
FormatInstruction(dest, inst, pc, regs, s_special_table[static_cast<u8>(inst.r.funct.GetValue())]); FormatInstruction(dest, inst, pc, s_special_table[static_cast<u8>(inst.r.funct.GetValue())]);
return; return;
case InstructionOp::cop0: case InstructionOp::cop0:
@ -323,8 +381,7 @@ void DisassembleInstruction(String* dest, u32 pc, u32 bits, Registers* regs)
{ {
if (inst.cop.IsCommonInstruction()) if (inst.cop.IsCommonInstruction())
{ {
FormatCopInstruction(dest, pc, regs, inst, s_cop_common_table.data(), s_cop_common_table.size(), FormatCopInstruction(dest, pc, inst, s_cop_common_table.data(), s_cop_common_table.size(), inst.cop.CommonOp());
inst.cop.CommonOp());
} }
else else
{ {
@ -332,7 +389,7 @@ void DisassembleInstruction(String* dest, u32 pc, u32 bits, Registers* regs)
{ {
case InstructionOp::cop0: 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; break;
@ -356,14 +413,75 @@ void DisassembleInstruction(String* dest, u32 pc, u32 bits, Registers* regs)
const bool bgez = ConvertToBoolUnchecked(rt & u8(1)); const bool bgez = ConvertToBoolUnchecked(rt & u8(1));
const bool link = ConvertToBoolUnchecked((rt >> 4) & u8(1)); const bool link = ConvertToBoolUnchecked((rt >> 4) & u8(1));
if (link) 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 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; break;
default: default:
FormatInstruction(dest, inst, pc, regs, s_base_table[static_cast<u8>(inst.op.GetValue())]); FormatInstruction(dest, inst, pc, s_base_table[static_cast<u8>(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<u8>(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("<cop%u 0x%08X>", 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<u8>(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<u8>(inst.op.GetValue())]);
break; break;
} }
} }

View File

@ -3,5 +3,6 @@
#include "cpu_types.h" #include "cpu_types.h"
namespace CPU { 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 } // namespace CPU

View File

@ -35,7 +35,7 @@ bool CodeGenerator::CompileBlock(CodeBlock* block, CodeBlock::HostCodePointer* o
{ {
#ifdef _DEBUG #ifdef _DEBUG
SmallString disasm; SmallString disasm;
DisassembleInstruction(&disasm, cbi->pc, cbi->instruction.bits, nullptr); DisassembleInstruction(&disasm, cbi->pc, cbi->instruction.bits);
Log_DebugPrintf("Compiling instruction '%s'", disasm.GetCharArray()); Log_DebugPrintf("Compiling instruction '%s'", disasm.GetCharArray());
#endif #endif

View File

@ -111,9 +111,15 @@ State GetState()
void SetState(State new_state) void SetState(State new_state)
{ {
if (s_state == new_state)
return;
Assert(s_state == State::Paused || s_state == State::Running); Assert(s_state == State::Paused || s_state == State::Running);
Assert(new_state == State::Paused || new_state == State::Running); Assert(new_state == State::Paused || new_state == State::Running);
s_state = new_state; s_state = new_state;
if (new_state == State::Paused)
CPU::ForceDispatcherExit();
} }
bool IsRunning() bool IsRunning()
@ -1163,30 +1169,55 @@ bool SaveState(ByteStream* state, u32 screenshot_size /* = 128 */)
return true; 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() void RunFrame()
{ {
s_frame_timer.Reset(); s_frame_timer.Reset();
g_gpu->RestoreGraphicsAPIState(); 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 #ifdef WITH_RECOMPILER
CPU::CodeCache::ExecuteRecompiler(); CPU::CodeCache::ExecuteRecompiler();
#else #else
CPU::CodeCache::Execute(); CPU::CodeCache::Execute();
#endif #endif
break; break;
case CPUExecutionMode::CachedInterpreter: case CPUExecutionMode::CachedInterpreter:
CPU::CodeCache::Execute(); CPU::CodeCache::Execute();
break; break;
case CPUExecutionMode::Interpreter: case CPUExecutionMode::Interpreter:
default: default:
CPU::Execute(); CPU::Execute();
break; break;
}
} }
// Generate any pending samples from the SPU before sleeping, this way we reduce the chances of underruns. // Generate any pending samples from the SPU before sleeping, this way we reduce the chances of underruns.

View File

@ -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. /// 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); bool RecreateGPU(GPURenderer renderer, bool update_display = true);
void SingleStepCPU();
void RunFrame(); void RunFrame();
/// Sets target emulation speed. /// Sets target emulation speed.

View File

@ -48,8 +48,7 @@ std::unique_ptr<TimingEvent> CreateTimingEvent(std::string name, TickCount perio
void UpdateCPUDowncount() void UpdateCPUDowncount()
{ {
if (!CPU::g_state.frame_done && if (!CPU::g_state.frame_done && (!CPU::HasPendingInterrupt() || CPU::g_using_interpreter))
(!CPU::HasPendingInterrupt() || g_settings.cpu_execution_mode == CPUExecutionMode::Interpreter))
{ {
CPU::g_state.downcount = s_active_events_head->GetDowncount(); CPU::g_state.downcount = s_active_events_head->GetDowncount();
} }