Debugger: Implemented step back for all main CPUs

This commit is contained in:
Sour 2022-09-11 14:50:40 -04:00
parent d97093ea89
commit c3dd78b5ab
33 changed files with 368 additions and 62 deletions

View file

@ -22,6 +22,7 @@
<ClInclude Include="Debugger\Base6502Assembler.h" />
<ClInclude Include="Debugger\CdlManager.h" />
<ClInclude Include="Debugger\DisassemblySearch.h" />
<ClInclude Include="Debugger\StepBackManager.h" />
<ClInclude Include="SNES\DSP\DspInterpolation.h" />
<ClInclude Include="SNES\DSP\Dsp.h" />
<ClInclude Include="NES\Debugger\NesCodeDataLogger.h" />
@ -685,6 +686,7 @@
<ClCompile Include="Debugger\ExpressionEvaluator.Pce.cpp" />
<ClCompile Include="Debugger\ExpressionEvaluator.Snes.cpp" />
<ClCompile Include="Debugger\ExpressionEvaluator.Spc.cpp" />
<ClCompile Include="Debugger\StepBackManager.cpp" />
<ClCompile Include="Gameboy\Debugger\DummyGbCpu.cpp" />
<ClCompile Include="Gameboy\Debugger\GbTraceLogger.cpp" />
<ClCompile Include="Gameboy\Debugger\GbPpuTools.cpp" />

View file

@ -2,11 +2,6 @@
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<None Include="Core.ruleset" />
<ClInclude Include="Shared\EventType.h" />
<ClInclude Include="Shared\FirmwareHelper.h" />
<ClInclude Include="Shared\MemoryOperationType.h" />
<ClInclude Include="Shared\MemoryType.h" />
<ClInclude Include="Shared\RomFinder.h" />
<ClCompile Include="pch.cpp" />
<ClInclude Include="pch.h" />
<ClCompile Include="Debugger\BaseEventManager.cpp">
@ -2597,6 +2592,24 @@
<ClInclude Include="SNES\DSP\DspTypes.h">
<Filter>SNES\DSP</Filter>
</ClInclude>
<ClInclude Include="Debugger\StepBackManager.h">
<Filter>Debugger</Filter>
</ClInclude>
<ClInclude Include="Shared\RomFinder.h">
<Filter>Shared</Filter>
</ClInclude>
<ClInclude Include="Shared\MemoryType.h">
<Filter>Shared</Filter>
</ClInclude>
<ClInclude Include="Shared\MemoryOperationType.h">
<Filter>Shared</Filter>
</ClInclude>
<ClInclude Include="Shared\FirmwareHelper.h">
<Filter>Shared</Filter>
</ClInclude>
<ClInclude Include="Shared\EventType.h">
<Filter>Shared</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Shared\Video\RotateFilter.cpp">
@ -2719,6 +2732,9 @@
<ClCompile Include="SNES\DSP\DspVoice.cpp">
<Filter>SNES\DSP</Filter>
</ClCompile>
<ClCompile Include="Debugger\StepBackManager.cpp">
<Filter>Debugger</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="PCE">

View file

@ -343,6 +343,7 @@ enum class StepType
SpecificScanline,
RunToNmi,
RunToIrq,
StepBack
};
struct StepRequest

View file

@ -166,8 +166,21 @@ uint64_t Debugger::GetCpuCycleCount()
template<CpuType type>
void Debugger::ProcessInstruction()
{
_debuggers[(int)type].Debugger->IgnoreBreakpoints = false;
_debuggers[(int)type].Debugger->AllowChangeProgramCounter = true;
IDebugger* debugger = _debuggers[(int)type].Debugger.get();
if(debugger->CheckStepBack()) {
//Step back target reached, break at the current instruction
debugger->GetStepRequest()->Break(BreakSource::CpuStep);
//Reset prev op code flag to prevent debugger code from incorrectly flagging
//an instruction as the start of a function, etc. after loading the state
debugger->ResetPrevOpCode();
} else if(debugger->IsStepBack()) {
//While step back is running, don't process instructions
return;
}
debugger->IgnoreBreakpoints = false;
debugger->AllowChangeProgramCounter = true;
switch(type) {
case CpuType::Snes: GetDebugger<type, SnesDebugger>()->ProcessInstruction(); break;
@ -181,10 +194,10 @@ void Debugger::ProcessInstruction()
case CpuType::Pce: GetDebugger<type, PceDebugger>()->ProcessInstruction(); break;
}
_debuggers[(int)type].Debugger->AllowChangeProgramCounter = false;
debugger->AllowChangeProgramCounter = false;
if(_scriptManager->HasCpuMemoryCallbacks()) {
MemoryOperationInfo memOp = _debuggers[(int)type].Debugger->InstructionProgress.LastMemOperation;
MemoryOperationInfo memOp = debugger->InstructionProgress.LastMemOperation;
AddressInfo relAddr = { (int32_t)memOp.Address, memOp.MemType };
uint8_t value = (uint8_t)memOp.Value;
_scriptManager->ProcessMemoryOperation(relAddr, value, MemoryOperationType::ExecOpCode, type, true);
@ -194,6 +207,10 @@ void Debugger::ProcessInstruction()
template<CpuType type, typename T>
void Debugger::ProcessMemoryRead(uint32_t addr, T& value, MemoryOperationType opType)
{
if(_debuggers[(int)type].Debugger->IsStepBack()) {
return;
}
switch(type) {
case CpuType::Snes: GetDebugger<type, SnesDebugger>()->ProcessRead(addr, value, opType); break;
case CpuType::Spc: GetDebugger<type, SpcDebugger>()->ProcessRead(addr, value, opType); break;
@ -214,6 +231,10 @@ void Debugger::ProcessMemoryRead(uint32_t addr, T& value, MemoryOperationType op
template<CpuType type, typename T>
void Debugger::ProcessMemoryWrite(uint32_t addr, T& value, MemoryOperationType opType)
{
if(_debuggers[(int)type].Debugger->IsStepBack()) {
return;
}
switch(type) {
case CpuType::Snes: GetDebugger<type, SnesDebugger>()->ProcessWrite(addr, value, opType); break;
case CpuType::Spc: GetDebugger<type, SpcDebugger>()->ProcessWrite(addr, value, opType); break;
@ -234,6 +255,10 @@ void Debugger::ProcessMemoryWrite(uint32_t addr, T& value, MemoryOperationType o
template<CpuType type>
void Debugger::ProcessIdleCycle()
{
if(_debuggers[(int)type].Debugger->IsStepBack()) {
return;
}
_debuggers[(int)type].Debugger->InstructionProgress.LastMemOperation.Type = MemoryOperationType::Idle;
switch(type) {
@ -245,6 +270,10 @@ void Debugger::ProcessIdleCycle()
template<CpuType type, typename T>
void Debugger::ProcessPpuRead(uint16_t addr, T& value, MemoryType memoryType, MemoryOperationType opType)
{
if(_debuggers[(int)type].Debugger->IsStepBack()) {
return;
}
switch(type) {
case CpuType::Snes: GetDebugger<type, SnesDebugger>()->ProcessPpuRead(addr, value, memoryType); break;
case CpuType::Gameboy: GetDebugger<type, GbDebugger>()->ProcessPpuRead(addr, value, memoryType); break;
@ -261,6 +290,10 @@ void Debugger::ProcessPpuRead(uint16_t addr, T& value, MemoryType memoryType, Me
template<CpuType type, typename T>
void Debugger::ProcessPpuWrite(uint16_t addr, T& value, MemoryType memoryType)
{
if(_debuggers[(int)type].Debugger->IsStepBack()) {
return;
}
switch(type) {
case CpuType::Snes: GetDebugger<type, SnesDebugger>()->ProcessPpuWrite(addr, value, memoryType); break;
case CpuType::Gameboy: GetDebugger<type, GbDebugger>()->ProcessPpuWrite(addr, value, memoryType); break;
@ -277,6 +310,10 @@ void Debugger::ProcessPpuWrite(uint16_t addr, T& value, MemoryType memoryType)
template<CpuType type>
void Debugger::ProcessPpuCycle()
{
if(_debuggers[(int)type].Debugger->IsStepBack()) {
return;
}
switch(type) {
case CpuType::Snes: GetDebugger<type, SnesDebugger>()->ProcessPpuCycle(); break;
case CpuType::Gameboy: GetDebugger<type, GbDebugger>()->ProcessPpuCycle(); break;
@ -359,6 +396,10 @@ void Debugger::ProcessPredictiveBreakpoint(CpuType sourceCpu, BreakpointManager*
template<CpuType type>
void Debugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi)
{
if(_debuggers[(int)type].Debugger->IsStepBack()) {
return;
}
_debuggers[(int)type].Debugger->ProcessInterrupt(originalPc, currentPc, forNmi);
ProcessEvent(forNmi ? EventType::Nmi : EventType::Irq);
}
@ -476,6 +517,7 @@ void Debugger::Run()
{
for(int i = 0; i <= (int)DebugUtilities::GetLastCpuType(); i++) {
if(_debuggers[i].Debugger) {
_debuggers[i].Debugger->ResetStepBackCache();
_debuggers[i].Debugger->Run();
}
}
@ -499,12 +541,19 @@ void Debugger::Step(CpuType cpuType, int32_t stepCount, StepType type, BreakSour
IDebugger* debugger = _debuggers[(int)cpuType].Debugger.get();
if(debugger) {
if(type != StepType::StepBack) {
debugger->ResetStepBackCache();
} else {
debugger->StepBack();
}
debugger->Step(stepCount, type);
debugger->GetStepRequest()->SetBreakSource(source);
}
for(int i = 0; i <= (int)DebugUtilities::GetLastCpuType(); i++) {
if(_debuggers[i].Debugger && _debuggers[i].Debugger.get() != debugger) {
_debuggers[i].Debugger->ResetStepBackCache();
_debuggers[i].Debugger->Run();
}
}

View file

@ -2,6 +2,7 @@
#include "pch.h"
#include "Debugger/DebuggerFeatures.h"
#include "Debugger/DebugTypes.h"
#include "Debugger/StepBackManager.h"
enum class StepType;
class BreakpointManager;
@ -15,10 +16,12 @@ struct BaseState;
enum class EventType;
enum class MemoryOperationType;
//TODO rename/refactor to BaseDebugger
class IDebugger
{
protected:
unique_ptr<StepRequest> _step;
unique_ptr<StepBackManager> _stepBackManager = unique_ptr<StepBackManager>(new StepBackManager(nullptr, nullptr));
public:
bool IgnoreBreakpoints = false;
@ -28,6 +31,12 @@ public:
virtual ~IDebugger() = default;
StepRequest* GetStepRequest() { return _step.get(); }
bool CheckStepBack() { return _stepBackManager->CheckStepBack(); }
bool IsStepBack() { return _stepBackManager->IsRewinding(); }
void ResetStepBackCache() { return _stepBackManager->ResetCache(); }
void StepBack() { return _stepBackManager->StepBack(); }
virtual void ResetPrevOpCode() {}
virtual void Step(int32_t stepCount, StepType type) = 0;
virtual void Reset() = 0;

View file

@ -174,21 +174,23 @@ int LuaApi::GetLibrary(lua_State *lua)
GenerateEnumDefinition<AccessCounterType>(lua, "counterType");
GenerateEnumDefinition<CpuType>(lua, "cpuType");
GenerateEnumDefinition<ScriptDrawSurface>(lua, "drawSurface");
GenerateEnumDefinition<EventType>(lua, "eventType");
GenerateEnumDefinition<StepType>(lua, "stepType");
GenerateEnumDefinition<EventType>(lua, "eventType", { EventType::LastValue });
GenerateEnumDefinition<StepType>(lua, "stepType", { StepType::StepBack });
return 1;
}
template<typename T>
void LuaApi::GenerateEnumDefinition(lua_State* lua, string enumName)
void LuaApi::GenerateEnumDefinition(lua_State* lua, string enumName, unordered_set<T> excludedValues)
{
lua_pushstring(lua, enumName.c_str());
lua_newtable(lua);
for(auto& entry : magic_enum::enum_entries<T>()) {
string name = string(entry.second);
name[0] = ::tolower(name[0]);
LuaPushIntValue(lua, name, (int)entry.first);
if(excludedValues.find(entry.first) == excludedValues.end()) {
string name = string(entry.second);
name[0] = ::tolower(name[0]);
LuaPushIntValue(lua, name, (int)entry.first);
}
}
lua_settable(lua, -3);
}

View file

@ -97,5 +97,5 @@ private:
static ScriptingContext* _context;
static std::pair<unique_ptr<BaseVideoFilter>, FrameInfo> GetRenderedFrame();
template<typename T> static void GenerateEnumDefinition(lua_State* lua, string enumName);
template<typename T> static void GenerateEnumDefinition(lua_State* lua, string enumName, unordered_set<T> excludedValues = {});
};

View file

@ -0,0 +1,95 @@
#include "pch.h"
#include "Debugger/StepBackManager.h"
#include "Debugger/IDebugger.h"
#include "Shared/Emulator.h"
#include "Shared/SaveStateManager.h"
#include "Shared/NotificationManager.h"
#include "Shared/RewindManager.h"
StepBackManager::StepBackManager(Emulator* emu, IDebugger* debugger)
{
_emu = emu;
_debugger = debugger;
}
void StepBackManager::StepBack()
{
if(!_active) {
_targetClock = _debugger->GetCpuCycleCount();
_active = true;
_allowRetry = true;
_stateClockLimit = StepBackManager::DefaultClockLimit;
}
}
bool StepBackManager::CheckStepBack()
{
if(!_active) {
return false;
}
uint64_t clock = _debugger->GetCpuCycleCount();
if(!_emu->GetRewindManager()->IsStepBack()) {
if(_cache.size() > 1) {
//Check to see if previous instruction is already in cache
if(_cache.back().Clock == _targetClock) {
//End of cache is the current instruction, remove it first
_cache.pop_back();
if(_cache.size()) {
//If cache isn't empty, load the last state
_emu->GetRewindManager()->SetIgnoreLoadState(true);
_emu->Deserialize(_cache.back().SaveState, SaveStateManager::FileFormatVersion, true);
_emu->GetRewindManager()->SetIgnoreLoadState(false);
_emu->GetRewindManager()->StopRewinding(true);
_active = false;
_prevClock = clock;
return true;
}
} else {
//On mismatch, clear cache and rewind normally instead
_cache.clear();
}
}
//Start rewinding on next instruction after StepBack() is called
_cache.clear();
_emu->GetRewindManager()->StartRewinding(true);
clock = _debugger->GetCpuCycleCount();
}
if(clock < _targetClock && _targetClock - clock < _stateClockLimit) {
//Create a save state every instruction for the last X clocks
_cache.push_back({});
_cache.back().Clock = clock;
_emu->Serialize(_cache.back().SaveState, true, 0);
}
if(clock >= _targetClock) {
//If the CPU is back to where it was before step back, check if the cache contains data
if(_cache.size() > 0) {
//If it does, load the last state
_emu->GetRewindManager()->SetIgnoreLoadState(true);
_emu->Deserialize(_cache.back().SaveState, SaveStateManager::FileFormatVersion, true);
_emu->GetRewindManager()->SetIgnoreLoadState(false);
} else if(_allowRetry && clock > _prevClock && (clock - _prevClock) > StepBackManager::DefaultClockLimit) {
//Cache is empty, this can happen when a single instruction takes more than X clocks (e.g block transfers, dma)
//In this case, re-run the step back process again but start recordings state earlier
_emu->GetRewindManager()->StopRewinding(true);
_emu->GetRewindManager()->StartRewinding(true);
_stateClockLimit = (clock - _prevClock) + StepBackManager::DefaultClockLimit;
_allowRetry = false;
return false;
}
//Stop rewinding, even if the target was not found
_emu->GetRewindManager()->StopRewinding(true);
_active = false;
_prevClock = clock;
return true;
}
_prevClock = clock;
return false;
}

View file

@ -0,0 +1,36 @@
#pragma once
#include "pch.h"
class Emulator;
class IDebugger;
struct StepBackCacheEntry
{
stringstream SaveState;
uint64_t Clock;
};
class StepBackManager
{
private:
static constexpr uint64_t DefaultClockLimit = 600; //Default to 600 clocks to avoid retry when NES sprite DMA occurs (~512 cycles)
Emulator* _emu = nullptr;
IDebugger* _debugger = nullptr;
vector<StepBackCacheEntry> _cache;
uint64_t _targetClock = 0;
uint64_t _prevClock = 0;
bool _active = false;
bool _allowRetry = false;
uint64_t _stateClockLimit = StepBackManager::DefaultClockLimit;
public:
StepBackManager(Emulator* emu, IDebugger* debugger);
void StepBack();
bool CheckStepBack();
void ResetCache() { _cache.clear(); }
bool IsRewinding() { return _active; }
};

View file

@ -18,6 +18,7 @@
#include "Debugger/MemoryDumper.h"
#include "Debugger/CodeDataLogger.h"
#include "Debugger/BaseEventManager.h"
#include "Debugger/StepBackManager.h"
#include "Utilities/Patches/IpsPatcher.h"
#include "Utilities/HexUtilities.h"
#include "Gameboy/Debugger/GbAssembler.h"
@ -56,6 +57,7 @@ GbDebugger::GbDebugger(Debugger* debugger)
_traceLogger.reset(new GbTraceLogger(debugger, this, _ppu));
_ppuTools.reset(new GbPpuTools(debugger, debugger->GetEmulator()));
_stepBackManager.reset(new StepBackManager(_emu, this));
_eventManager.reset(new GbEventManager(debugger, _gameboy->GetCpu(), _ppu));
_callstackManager.reset(new CallstackManager(debugger, _gameboy));
_breakpointManager.reset(new BreakpointManager(debugger, this, CpuType::Gameboy, _eventManager.get()));
@ -79,7 +81,7 @@ GbDebugger::~GbDebugger()
void GbDebugger::Reset()
{
_callstackManager->Clear();
_prevOpCode = 0;
ResetPrevOpCode();
}
void GbDebugger::ProcessInstruction()
@ -277,7 +279,7 @@ void GbDebugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool
//If a call/return occurred just before IRQ, it needs to be processed now
ProcessCallStackUpdates(ret, originalPc);
_prevOpCode = 0;
ResetPrevOpCode();
_debugger->InternalProcessInterrupt(
CpuType::Gameboy, *this, *_step.get(),
@ -326,6 +328,7 @@ DebuggerFeatures GbDebugger::GetSupportedFeatures()
features.RunToNmi = false;
features.StepOver = true;
features.StepOut = true;
features.StepBack = true;
features.CallStack = true;
features.ChangeProgramCounter = AllowChangeProgramCounter;
@ -351,6 +354,16 @@ uint32_t GbDebugger::GetProgramCounter(bool getInstPc)
return getInstPc ? _prevProgramCounter : _cpu->GetState().PC;
}
uint64_t GbDebugger::GetCpuCycleCount()
{
return _gameboy->GetCycleCount();
}
void GbDebugger::ResetPrevOpCode()
{
_prevOpCode = 0;
}
BaseEventManager* GbDebugger::GetEventManager()
{
return _eventManager.get();

View file

@ -76,6 +76,8 @@ public:
void SetProgramCounter(uint32_t addr) override;
uint32_t GetProgramCounter(bool getInstPc) override;
uint64_t GetCpuCycleCount() override;
void ResetPrevOpCode();
DebuggerFeatures GetSupportedFeatures() override;

View file

@ -6,6 +6,7 @@
#include "Debugger/CodeDataLogger.h"
#include "Debugger/ScriptManager.h"
#include "Debugger/Debugger.h"
#include "Debugger/StepBackManager.h"
#include "Debugger/MemoryDumper.h"
#include "Debugger/MemoryAccessCounter.h"
#include "Debugger/ExpressionEvaluator.h"
@ -73,6 +74,7 @@ NesDebugger::NesDebugger(Debugger* debugger)
_ppuTools.reset(new NesPpuTools(debugger, debugger->GetEmulator(), console));
_stepBackManager.reset(new StepBackManager(_emu, this));
_eventManager.reset(new NesEventManager(debugger, console));
_callstackManager.reset(new CallstackManager(debugger, console));
_breakpointManager.reset(new BreakpointManager(debugger, this, CpuType::Nes, _eventManager.get()));
@ -94,7 +96,7 @@ void NesDebugger::Reset()
{
_enableBreakOnUninitRead = true;
_callstackManager->Clear();
_prevOpCode = 0xFF;
ResetPrevOpCode();
}
uint64_t NesDebugger::GetCpuCycleCount()
@ -102,6 +104,11 @@ uint64_t NesDebugger::GetCpuCycleCount()
return _cpu->GetState().CycleCount;
}
void NesDebugger::ResetPrevOpCode()
{
_prevOpCode = 0xFF;
}
void NesDebugger::ProcessInstruction()
{
NesCpuState& state = _cpu->GetState();
@ -306,7 +313,7 @@ void NesDebugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool
//If a call/return occurred just before IRQ, it needs to be processed now
ProcessCallStackUpdates(ret, originalPc);
_prevOpCode = 0xFF;
ResetPrevOpCode();
_debugger->InternalProcessInterrupt(
CpuType::Nes, *this, *_step.get(),
@ -378,6 +385,7 @@ DebuggerFeatures NesDebugger::GetSupportedFeatures()
features.RunToNmi = true;
features.StepOver = true;
features.StepOut = true;
features.StepBack = true;
features.CallStack = true;
features.CpuCycleStep = true;
features.ChangeProgramCounter = AllowChangeProgramCounter;

View file

@ -67,6 +67,7 @@ public:
void Reset() override;
uint64_t GetCpuCycleCount() override;
void ResetPrevOpCode() override;
void ProcessInstruction();
void ProcessRead(uint32_t addr, uint8_t value, MemoryOperationType type);

View file

@ -10,6 +10,7 @@
#include "Debugger/MemoryAccessCounter.h"
#include "Debugger/ExpressionEvaluator.h"
#include "Debugger/CodeDataLogger.h"
#include "Debugger/StepBackManager.h"
#include "PCE/PceConsole.h"
#include "PCE/PceCpu.h"
#include "PCE/PceVdc.h"
@ -59,6 +60,7 @@ PceDebugger::PceDebugger(Debugger* debugger)
_cdlFile = _codeDataLogger->GetCdlFilePath(_console->GetRomFormat() == RomFormat::PceCdRom ? "PceCdromBios.cdl" : _emu->GetRomInfo().RomFile.GetFileName());
_codeDataLogger->LoadCdlFile(_cdlFile, _settings->GetDebugConfig().AutoResetCdl);
_stepBackManager.reset(new StepBackManager(_emu, this));
_eventManager.reset(new PceEventManager(debugger, console));
_callstackManager.reset(new CallstackManager(debugger, console));
_breakpointManager.reset(new BreakpointManager(debugger, this, CpuType::Pce, _eventManager.get()));
@ -80,7 +82,7 @@ void PceDebugger::Reset()
{
_enableBreakOnUninitRead = true;
_callstackManager->Clear();
_prevOpCode = 0x01;
ResetPrevOpCode();
}
uint64_t PceDebugger::GetCpuCycleCount()
@ -88,6 +90,11 @@ uint64_t PceDebugger::GetCpuCycleCount()
return _cpu->GetState().CycleCount;
}
void PceDebugger::ResetPrevOpCode()
{
_prevOpCode = 0x01;
}
void PceDebugger::ProcessInstruction()
{
PceCpuState& state = _cpu->GetState();
@ -285,7 +292,7 @@ void PceDebugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool
//If a call/return occurred just before IRQ, it needs to be processed now
ProcessCallStackUpdates(ret, originalPc);
_prevOpCode = 0x01;
ResetPrevOpCode();
_debugger->InternalProcessInterrupt(
CpuType::Pce, *this, *_step.get(),
@ -341,6 +348,7 @@ DebuggerFeatures PceDebugger::GetSupportedFeatures()
features.RunToNmi = false;
features.StepOver = true;
features.StepOut = true;
features.StepBack = true;
features.CallStack = true;
features.ChangeProgramCounter = AllowChangeProgramCounter;
features.CpuCycleStep = true;

View file

@ -68,6 +68,7 @@ public:
void Reset() override;
uint64_t GetCpuCycleCount() override;
void ResetPrevOpCode() override;
void ProcessInstruction();
void ProcessRead(uint32_t addr, uint8_t value, MemoryOperationType type);

View file

@ -28,6 +28,7 @@
#include "Debugger/ScriptManager.h"
#include "Debugger/Debugger.h"
#include "Debugger/CodeDataLogger.h"
#include "Debugger/StepBackManager.h"
#include "Shared/SettingTypes.h"
#include "Shared/BaseControlManager.h"
#include "Shared/EmuSettings.h"
@ -75,7 +76,8 @@ SnesDebugger::SnesDebugger(Debugger* debugger, CpuType cpuType)
} else {
_cdl = (SnesCodeDataLogger*)_debugger->GetCdlManager()->GetCodeDataLogger(MemoryType::SnesPrgRom);
}
_stepBackManager.reset(new StepBackManager(_emu, this));
_eventManager.reset(new SnesEventManager(debugger, _cpu, console->GetPpu(), _memoryManager, console->GetDmaController()));
_callstackManager.reset(new CallstackManager(debugger, console));
_breakpointManager.reset(new BreakpointManager(debugger, this, cpuType, _eventManager.get()));
@ -107,7 +109,7 @@ void SnesDebugger::Reset()
{
_enableBreakOnUninitRead = true;
_callstackManager->Clear();
_prevOpCode = 0xFF;
ResetPrevOpCode();
}
void SnesDebugger::ProcessConfigChange()
@ -129,6 +131,11 @@ uint64_t SnesDebugger::GetCpuCycleCount()
return GetCpuState().CycleCount;
}
void SnesDebugger::ResetPrevOpCode()
{
_prevOpCode = 0xFF;
}
void SnesDebugger::ProcessInstruction()
{
SnesCpuState& state = GetCpuState();
@ -354,7 +361,7 @@ void SnesDebugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, boo
//If a call/return occurred just before IRQ, it needs to be processed now
ProcessCallStackUpdates(ret, originalPc, GetCpuState().PS);
_prevOpCode = 0xFF;
ResetPrevOpCode();
_debugger->InternalProcessInterrupt(
_cpuType, *this, *_step.get(),
@ -427,6 +434,7 @@ DebuggerFeatures SnesDebugger::GetSupportedFeatures()
features.RunToNmi = true;
features.StepOver = true;
features.StepOut = true;
features.StepBack = true;
features.CallStack = true;
features.ChangeProgramCounter = AllowChangeProgramCounter;
features.CpuCycleStep = true;

View file

@ -87,6 +87,7 @@ public:
void ProcessConfigChange() override;
uint64_t GetCpuCycleCount() override;
void ResetPrevOpCode() override;
void ProcessInstruction();
void ProcessRead(uint32_t addr, uint8_t value, MemoryOperationType type);

View file

@ -85,7 +85,7 @@ void RewindManager::ProcessNotification(ConsoleNotificationType type, void * par
ClearBuffer();
}
} else if(type == ConsoleNotificationType::StateLoaded) {
if(_rewindState == RewindState::Stopped) {
if(_rewindState == RewindState::Stopped && !_ignoreLoadState) {
//A save state was loaded by the user, mark as the end of the current "segment" (for history viewer)
_currentHistory.EndOfSegment = true;
AddHistoryBlock();
@ -111,10 +111,10 @@ void RewindManager::AddHistoryBlock()
void RewindManager::PopHistory()
{
if(_history.empty() && _currentHistory.FrameCount <= 0) {
if(_history.empty() && _currentHistory.FrameCount <= 0 && !IsStepBack()) {
StopRewinding();
} else {
if(_currentHistory.FrameCount <= 0) {
if(_currentHistory.FrameCount <= 0 && !IsStepBack()) {
_currentHistory = _history.back();
_history.pop_back();
}
@ -131,22 +131,30 @@ void RewindManager::PopHistory()
void RewindManager::Start(bool forDebugger)
{
if(_rewindState == RewindState::Stopped && _settings->GetRewindBufferSize() > 0) {
auto lock = _emu->AcquireLock();
_rewindState = forDebugger ? RewindState::Debugging : RewindState::Starting;
_videoHistoryBuilder.clear();
_videoHistory.clear();
_audioHistoryBuilder.clear();
_audioHistory.clear();
_historyBackup.clear();
PopHistory();
_emu->GetSoundMixer()->StopAudio(true);
_settings->SetFlag(EmulationFlags::MaximumSpeed);
_settings->SetFlag(EmulationFlags::Rewind);
if(forDebugger) {
InternalStart(forDebugger);
} else {
auto lock = _emu->AcquireLock();
InternalStart(forDebugger);
}
}
}
void RewindManager::InternalStart(bool forDebugger)
{
_rewindState = forDebugger ? RewindState::Debugging : RewindState::Starting;
_videoHistoryBuilder.clear();
_videoHistory.clear();
_audioHistoryBuilder.clear();
_audioHistory.clear();
_historyBackup.clear();
PopHistory();
_emu->GetSoundMixer()->StopAudio(true);
_settings->SetFlag(EmulationFlags::MaximumSpeed);
_settings->SetFlag(EmulationFlags::Rewind);
}
void RewindManager::ForceStop()
{
if(_rewindState != RewindState::Stopped) {
@ -374,3 +382,8 @@ bool RewindManager::SendAudio(int16_t* soundBuffer, uint32_t sampleCount)
{
return ProcessAudio(soundBuffer, sampleCount);
}
void RewindManager::SetIgnoreLoadState(bool ignore)
{
_ignoreLoadState = ignore;
}

View file

@ -39,6 +39,7 @@ private:
EmuSettings* _settings = nullptr;
bool _hasHistory = false;
bool _ignoreLoadState = false;
deque<RewindData> _history;
deque<RewindData> _historyBackup;
@ -56,6 +57,7 @@ private:
void PopHistory();
void Start(bool forDebugger);
void InternalStart(bool forDebugger);
void Stop();
void ForceStop();
@ -85,4 +87,5 @@ public:
void SendFrame(RenderedFrame& frame, bool forRewind);
bool SendAudio(int16_t *soundBuffer, uint32_t sampleCount);
void SetIgnoreLoadState(bool ignore);
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 B

After

Width:  |  Height:  |  Size: 227 B

View file

@ -1,6 +1,7 @@
using Mesen.ViewModels;
using ReactiveUI.Fody.Helpers;
using System;
using System.ComponentModel;
namespace Mesen.Debugger.StatusViews
{
@ -10,6 +11,18 @@ namespace Mesen.Debugger.StatusViews
[Reactive] public UInt64 ElapsedCycles { get; set; }
[Reactive] public UInt64 CycleCount { get; set; }
private bool _needUpdate = false;
public BaseConsoleStatusViewModel()
{
PropertyChanged += BaseConsoleStatusViewModel_PropertyChanged;
}
private void BaseConsoleStatusViewModel_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
_needUpdate = true;
}
public void UpdateCycleCount(UInt64 newCycleCount)
{
if(newCycleCount > CycleCount) {
@ -20,7 +33,22 @@ namespace Mesen.Debugger.StatusViews
CycleCount = newCycleCount;
}
public abstract void UpdateUiState();
public abstract void UpdateConsoleState();
public void UpdateUiState()
{
InternalUpdateUiState();
_needUpdate = false;
}
public void UpdateConsoleState()
{
if(_needUpdate) {
//Only update emulator state if user manually changed the state in the UI
//Otherwise this causes issues when a state is loaded (e.g step back)
InternalUpdateConsoleState();
}
}
protected abstract void InternalUpdateUiState();
protected abstract void InternalUpdateConsoleState();
}
}

View file

@ -51,7 +51,7 @@ namespace Mesen.Debugger.StatusViews
{
}
public override void UpdateUiState()
protected override void InternalUpdateUiState()
{
Cx4State cpu = DebugApi.GetCpuState<Cx4State>(CpuType.Cx4);
@ -95,7 +95,7 @@ namespace Mesen.Debugger.StatusViews
FlagIrq = cpu.IrqFlag;
}
public override void UpdateConsoleState()
protected override void InternalUpdateConsoleState()
{
Cx4State cpu = DebugApi.GetCpuState<Cx4State>(CpuType.Cx4);

View file

@ -58,7 +58,7 @@ namespace Mesen.Debugger.StatusViews
);
}
public override void UpdateUiState()
protected override void InternalUpdateUiState()
{
GbState state = DebugApi.GetConsoleState<GbState>(ConsoleType.Gameboy);
@ -94,7 +94,7 @@ namespace Mesen.Debugger.StatusViews
StackPreview = sb.ToString();
}
public override void UpdateConsoleState()
protected override void InternalUpdateConsoleState()
{
GbCpuState cpu = DebugApi.GetCpuState<GbCpuState>(CpuType.Gameboy);

View file

@ -75,7 +75,7 @@ namespace Mesen.Debugger.StatusViews
);
}
public override void UpdateUiState()
protected override void InternalUpdateUiState()
{
GsuState cpu = DebugApi.GetCpuState<GsuState>(CpuType.Gsu);
@ -118,7 +118,7 @@ namespace Mesen.Debugger.StatusViews
FlagIrq = cpu.SFR.Irq;
}
public override void UpdateConsoleState()
protected override void InternalUpdateConsoleState()
{
GsuState cpu = DebugApi.GetCpuState<GsuState>(CpuType.Gsu);

View file

@ -42,7 +42,7 @@ namespace Mesen.Debugger.StatusViews
{
}
public override void UpdateUiState()
protected override void InternalUpdateUiState()
{
NecDspState cpu = DebugApi.GetCpuState<NecDspState>(CpuType.NecDsp);
@ -80,7 +80,7 @@ namespace Mesen.Debugger.StatusViews
RegB_S1 = cpu.FlagsB.Sign1;
}
public override void UpdateConsoleState()
protected override void InternalUpdateConsoleState()
{
NecDspState cpu = DebugApi.GetCpuState<NecDspState>(CpuType.NecDsp);

View file

@ -84,7 +84,7 @@ namespace Mesen.Debugger.StatusViews
});
}
public override void UpdateUiState()
protected override void InternalUpdateUiState()
{
NesCpuState cpu = DebugApi.GetCpuState<NesCpuState>(CpuType.Nes);
NesPpuState ppu = DebugApi.GetPpuState<NesPpuState>(CpuType.Nes);
@ -139,7 +139,7 @@ namespace Mesen.Debugger.StatusViews
IntensifyBlue = ppu.Mask.IntensifyBlue;
}
public override void UpdateConsoleState()
protected override void InternalUpdateConsoleState()
{
NesCpuState cpu = DebugApi.GetCpuState<NesCpuState>(CpuType.Nes);
NesPpuState ppu = DebugApi.GetPpuState<NesPpuState>(CpuType.Nes);

View file

@ -55,7 +55,7 @@ namespace Mesen.Debugger.StatusViews
});
}
public override void UpdateUiState()
protected override void InternalUpdateUiState()
{
PceCpuState cpu = DebugApi.GetCpuState<PceCpuState>(CpuType.Pce);
PceVideoState video = DebugApi.GetPpuState<PceVideoState>(CpuType.Pce);
@ -80,7 +80,7 @@ namespace Mesen.Debugger.StatusViews
FrameCount = video.Vdc.FrameCount;
}
public override void UpdateConsoleState()
protected override void InternalUpdateConsoleState()
{
PceCpuState cpu = DebugApi.GetCpuState<PceCpuState>(CpuType.Pce);

View file

@ -80,7 +80,7 @@ namespace Mesen.Debugger.StatusViews
);
}
public override void UpdateUiState()
protected override void InternalUpdateUiState()
{
SnesCpuState cpu = DebugApi.GetCpuState<SnesCpuState>(_cpuType);
SnesPpuState ppu = DebugApi.GetPpuState<SnesPpuState>(CpuType.Snes);
@ -115,7 +115,7 @@ namespace Mesen.Debugger.StatusViews
Scanline = ppu.Scanline;
}
public override void UpdateConsoleState()
protected override void InternalUpdateConsoleState()
{
SnesCpuState cpu = DebugApi.GetCpuState<SnesCpuState>(_cpuType);

View file

@ -58,7 +58,7 @@ namespace Mesen.Debugger.StatusViews
);
}
public override void UpdateUiState()
protected override void InternalUpdateUiState()
{
SpcState cpu = DebugApi.GetCpuState<SpcState>(CpuType.Spc);
@ -78,7 +78,7 @@ namespace Mesen.Debugger.StatusViews
StackPreview = sb.ToString();
}
public override void UpdateConsoleState()
protected override void InternalUpdateConsoleState()
{
SpcState cpu = DebugApi.GetCpuState<SpcState>(CpuType.Spc);

View file

@ -66,8 +66,7 @@ namespace Mesen.Debugger.Utilities
ActionType = ActionType.StepBack,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.StepBack),
IsVisible = () => DebugApi.GetDebuggerFeatures(getCpuType()).StepBack,
IsEnabled = () => false,
OnClick = () => { } //TODO
OnClick = () => Step(getCpuType(), StepType.StepBack, 1)
},
new ContextMenuSeparator() { IsVisible = () => DebugApi.GetDebuggerFeatures(getCpuType()).CpuCycleStep },

View file

@ -217,7 +217,7 @@ namespace Mesen.Debugger.ViewModels
DebuggerShortcut.StepInto,
DebuggerShortcut.StepOver,
DebuggerShortcut.StepOut,
//DebuggerShortcut.StepBack,
DebuggerShortcut.StepBack,
DebuggerShortcut.RunCpuCycle,
DebuggerShortcut.RunPpuCycle,
DebuggerShortcut.RunPpuScanline,

View file

@ -1236,6 +1236,7 @@ namespace Mesen.Interop
SpecificScanline,
RunToNmi,
RunToIrq,
StepBack
}
public enum BreakSource

View file

@ -19,6 +19,8 @@ using Mesen.Debugger.Utilities;
using System.ComponentModel;
using System.Threading;
using Mesen.Debugger.Windows;
using Avalonia.Input.Platform;
using System.Collections.Generic;
namespace Mesen.Windows
{
@ -46,6 +48,14 @@ namespace Mesen.Windows
{
DataContext = new MainWindowViewModel();
List<KeyGesture> gestures = AvaloniaLocator.Current.GetRequiredService<PlatformHotkeyConfiguration>().OpenContextMenu;
for(int i = gestures.Count - 1; i >= 0; i--) {
if(gestures[i].Key == Key.F10 && gestures[i].KeyModifiers == KeyModifiers.Shift) {
//Disable Shift-F10 shortcut to open context menu - interferes with default shortcut for step back
gestures.RemoveAt(i);
}
}
EmuApi.InitDll();
Directory.CreateDirectory(ConfigManager.HomeFolder);