Mesen2/Core/SNES/Debugger/NecDspDebugger.cpp
Sour 614e61a2d1 Debugger: Fixed issues with step over/out in some scenarios (bank swapping, recursive functions, etc.)
Only break when both the stack pointer and the return address match what the debugger was expecting, instead of just the return address
2024-12-07 17:46:28 +09:00

209 lines
6.7 KiB
C++

#include "pch.h"
#include "SNES/BaseCartridge.h"
#include "SNES/SnesMemoryManager.h"
#include "SNES/SnesConsole.h"
#include "SNES/Coprocessors/DSP/NecDsp.h"
#include "SNES/Debugger/NecDspDebugger.h"
#include "SNES/Debugger/NecDspDisUtils.h"
#include "SNES/Debugger/TraceLogger/NecDspTraceLogger.h"
#include "Debugger/DisassemblyInfo.h"
#include "Debugger/Disassembler.h"
#include "Debugger/CallstackManager.h"
#include "Debugger/BreakpointManager.h"
#include "Debugger/Debugger.h"
#include "Debugger/MemoryAccessCounter.h"
#include "Debugger/ExpressionEvaluator.h"
#include "Shared/Emulator.h"
#include "Shared/EmuSettings.h"
#include "Shared/MemoryOperationType.h"
NecDspDebugger::NecDspDebugger(Debugger* debugger) : IDebugger(debugger->GetEmulator())
{
SnesConsole* console = (SnesConsole*)debugger->GetConsole();
_debugger = debugger;
_disassembler = debugger->GetDisassembler();
_memoryAccessCounter = debugger->GetMemoryAccessCounter();
_dsp = console->GetCartridge()->GetDsp();
_settings = debugger->GetEmulator()->GetSettings();
_memoryManager = console->GetMemoryManager();
_traceLogger.reset(new NecDspTraceLogger(debugger, this, console->GetPpu(), console->GetMemoryManager()));
_callstackManager.reset(new CallstackManager(debugger, this));
_breakpointManager.reset(new BreakpointManager(debugger, this, CpuType::NecDsp, debugger->GetEventManager(CpuType::Snes)));
_step.reset(new StepRequest());
}
void NecDspDebugger::Reset()
{
_callstackManager->Clear();
_prevOpCode = 0;
}
void NecDspDebugger::ProcessInstruction()
{
NecDspState& state = _dsp->GetState();
uint32_t pc = state.PC * 3;
uint32_t opCode = _dsp->GetOpCode(state.PC);
AddressInfo addressInfo = { (int32_t)pc, MemoryType::DspProgramRom };
MemoryOperationInfo operation(pc, opCode, MemoryOperationType::ExecOpCode, MemoryType::NecDspMemory);
InstructionProgress.LastMemOperation = operation;
InstructionProgress.StartCycle = state.CycleCount;
_disassembler->BuildCache(addressInfo, 0, CpuType::NecDsp);
if(NecDspDisUtils::IsJumpToSub(_prevOpCode)) {
//CALL and RST, and PC doesn't match the next instruction, so the call was (probably) done
uint32_t returnPc = _prevProgramCounter + NecDspDisUtils::GetOpSize();
AddressInfo src = { (int32_t)_prevProgramCounter, MemoryType::DspProgramRom };
AddressInfo ret = { (int32_t)returnPc, MemoryType::DspProgramRom };
_callstackManager->Push(src, _prevProgramCounter, addressInfo, pc, ret, returnPc, _prevStackPointer, StackFrameFlags::None);
} else if(NecDspDisUtils::IsReturnInstruction(_prevOpCode)) {
_callstackManager->Pop(addressInfo, pc, state.SP);
if(_step->BreakAddress == (int32_t)pc && _step->BreakStackPointer == state.SP) {
//If we're on the expected return address, break immediately (for step over/step out)
_step->Break(BreakSource::CpuStep);
}
}
_prevProgramCounter = pc;
_prevOpCode = opCode;
_prevStackPointer = state.SP;
_step->ProcessCpuExec();
_debugger->ProcessBreakConditions(CpuType::NecDsp, *_step.get(), _breakpointManager.get(), operation, addressInfo);
}
void NecDspDebugger::ProcessRead(uint32_t addr, uint8_t value, MemoryOperationType type)
{
if(type == MemoryOperationType::ExecOpCode) {
AddressInfo addressInfo = { (int32_t)addr, MemoryType::DspProgramRom };
MemoryOperationInfo operation(addr, value, MemoryOperationType::ExecOpCode, MemoryType::NecDspMemory);
InstructionProgress.LastMemOperation = operation;
if(_traceLogger->IsEnabled()) {
DisassemblyInfo disInfo = _disassembler->GetDisassemblyInfo(addressInfo, addr, 0, CpuType::NecDsp);
_traceLogger->Log(_dsp->GetState(), disInfo, operation, addressInfo);
}
} else {
MemoryType memType = (addr & NecDsp::DataRomReadFlag) ? MemoryType::DspDataRom : MemoryType::DspDataRam;
addr &= ~NecDsp::DataRomReadFlag;
AddressInfo addressInfo = { (int32_t)addr, memType };
MemoryOperationInfo operation(addr, value, type, memType);
InstructionProgress.LastMemOperation = operation;
_debugger->ProcessBreakConditions(CpuType::NecDsp, *_step.get(), _breakpointManager.get(), operation, addressInfo);
_memoryAccessCounter->ProcessMemoryRead(addressInfo, _memoryManager->GetMasterClock());
if(_traceLogger->IsEnabled()) {
_traceLogger->LogNonExec(operation, addressInfo);
}
}
}
void NecDspDebugger::ProcessWrite(uint32_t addr, uint8_t value, MemoryOperationType type)
{
AddressInfo addressInfo = { (int32_t)addr, MemoryType::DspDataRam };
MemoryOperationInfo operation(addr, value, type, MemoryType::DspDataRam);
InstructionProgress.LastMemOperation = operation;
_debugger->ProcessBreakConditions(CpuType::NecDsp, *_step.get(), _breakpointManager.get(), operation, addressInfo);
_memoryAccessCounter->ProcessMemoryWrite(addressInfo, _memoryManager->GetMasterClock());
if(_traceLogger->IsEnabled()) {
_traceLogger->LogNonExec(operation, addressInfo);
}
}
void NecDspDebugger::Run()
{
_step.reset(new StepRequest());
}
void NecDspDebugger::Step(int32_t stepCount, StepType type)
{
StepRequest step;
switch(type) {
case StepType::Step: step.StepCount = stepCount; break;
case StepType::StepOut:
step.BreakAddress = _callstackManager->GetReturnAddress();
step.BreakStackPointer = _callstackManager->GetReturnStackPointer();
break;
case StepType::StepOver:
if(NecDspDisUtils::IsJumpToSub(_prevOpCode)) {
step.BreakAddress = _prevProgramCounter + NecDspDisUtils::GetOpSize();
step.BreakStackPointer = _prevStackPointer;
} else {
//For any other instruction, step over is the same as step into
step.StepCount = 1;
}
break;
case StepType::SpecificScanline:
case StepType::PpuStep:
break;
}
_step.reset(new StepRequest(step));
}
DebuggerFeatures NecDspDebugger::GetSupportedFeatures()
{
DebuggerFeatures features = {};
features.ChangeProgramCounter = AllowChangeProgramCounter;
features.StepOut = true;
features.StepOver = true;
return features;
}
void NecDspDebugger::SetProgramCounter(uint32_t addr, bool updateDebuggerOnly)
{
if(!updateDebuggerOnly) {
_dsp->GetState().PC = addr / 3;
}
_prevProgramCounter = addr;
_prevStackPointer = _dsp->GetState().SP;
}
uint32_t NecDspDebugger::GetProgramCounter(bool getInstPc)
{
return getInstPc ? _prevProgramCounter : (_dsp->GetState().PC * 3);
}
uint64_t NecDspDebugger::GetCpuCycleCount(bool forProfiler)
{
return _dsp->GetState().CycleCount;
}
CallstackManager* NecDspDebugger::GetCallstackManager()
{
return _callstackManager.get();
}
BreakpointManager* NecDspDebugger::GetBreakpointManager()
{
return _breakpointManager.get();
}
IAssembler* NecDspDebugger::GetAssembler()
{
throw std::runtime_error("Assembler not supported for NEC DSP");
}
BaseEventManager* NecDspDebugger::GetEventManager()
{
return nullptr;
}
BaseState& NecDspDebugger::GetState()
{
return _dsp->GetState();
}
ITraceLogger* NecDspDebugger::GetTraceLogger()
{
return _traceLogger.get();
}