Mesen/Core/Debugger.cpp

1669 lines
47 KiB
C++

#include "stdafx.h"
#include <thread>
#include "../Utilities/FolderUtilities.h"
#include "MessageManager.h"
#include "Debugger.h"
#include "Console.h"
#include "BaseMapper.h"
#include "Disassembler.h"
#include "VideoDecoder.h"
#include "APU.h"
#include "SoundMixer.h"
#include "CodeDataLogger.h"
#include "ExpressionEvaluator.h"
#include "LabelManager.h"
#include "MemoryDumper.h"
#include "MemoryAccessCounter.h"
#include "Profiler.h"
#include "Assembler.h"
#include "CodeRunner.h"
#include "DisassemblyInfo.h"
#include "PPU.h"
#include "MemoryManager.h"
#include "RewindManager.h"
#include "DebugBreakHelper.h"
#include "ScriptHost.h"
#include "StandardController.h"
#include "TraceLogger.h"
#include "Breakpoint.h"
#include "CodeDataLogger.h"
#include "NotificationManager.h"
#include "DebugHud.h"
#include "DummyCpu.h"
#include "PerformanceTracker.h"
#include "EventManager.h"
string Debugger::_disassemblerOutput = "";
Debugger::Debugger(shared_ptr<Console> console, shared_ptr<CPU> cpu, shared_ptr<PPU> ppu, shared_ptr<APU> apu, shared_ptr<MemoryManager> memoryManager, shared_ptr<BaseMapper> mapper)
{
_romName = console->GetRomInfo().RomName;
_console = console;
_cpu = cpu;
_apu = apu;
_memoryManager = memoryManager;
_mapper = mapper;
_dummyCpu.reset(new DummyCpu(console));
_breakOnFirstCycle = false;
_labelManager.reset(new LabelManager(_mapper));
_assembler.reset(new Assembler(_labelManager));
_disassembler.reset(new Disassembler(memoryManager.get(), mapper.get(), this));
_codeDataLogger.reset(new CodeDataLogger(this, mapper->GetMemorySize(DebugMemoryType::PrgRom), mapper->GetMemorySize(DebugMemoryType::ChrRom)));
SetPpu(ppu);
_memoryAccessCounter.reset(new MemoryAccessCounter(this));
_profiler.reset(new Profiler(this));
_performanceTracker.reset(new PerformanceTracker(console));
_eventManager.reset(new EventManager(this, cpu.get(), ppu.get(), _console->GetSettings()));
_traceLogger.reset(new TraceLogger(this, memoryManager, _labelManager));
_bpExpEval.reset(new ExpressionEvaluator(this));
_watchExpEval.reset(new ExpressionEvaluator(this));
#if _DEBUG
_bpExpEval->RunTests();
#endif
_stepOut = false;
_stepCount = -1;
_stepOverAddr = -1;
_stepCycleCount = -1;
_ppuStepCount = -1;
_breakRequested = false;
_pausedForDebugHelper = false;
_breakOnScanline = -2;
_breakSource = BreakSource::Unspecified;
memset(_hasBreakpoint, 0, sizeof(_hasBreakpoint));
_bpDummyCpuRequired = false;
_preventResume = 0;
_stopFlag = false;
_suspendCount = 0;
_opCodeCycle = 0;
_lastInstruction = 0;
_stepOutReturnAddress = -1;
_currentReadAddr = nullptr;
_currentReadValue = nullptr;
_nextReadAddr = -1;
_returnToAddress = 0;
_ppuScrollX = 0;
_ppuScrollY = 0;
_flags = 0;
_runToCycle = -1;
_prevInstructionCycle = -1;
_curInstructionCycle = -1;
_needRewind = false;
//Only enable break on uninitialized reads when debugger is opened at power on/reset
_enableBreakOnUninitRead = _cpu->GetPC() == 0;
_executionStopped = false;
_disassemblerOutput = "";
memset(_inputOverride, 0, sizeof(_inputOverride));
_frozenAddresses.insert(_frozenAddresses.end(), 0x10000, 0);
if(!LoadCdlFile(FolderUtilities::CombinePath(FolderUtilities::GetDebuggerFolder(), FolderUtilities::GetFilename(_romName, false) + ".cdl"))) {
_disassembler->Reset();
}
_hasScript = false;
_nextScriptId = 0;
_released = false;
UpdatePpuCyclesToProcess();
}
Debugger::~Debugger()
{
if(!_released) {
ReleaseDebugger(true);
}
}
void Debugger::ReleaseDebugger(bool needPause)
{
auto lock = _releaseLock.AcquireSafe();
if(!_released) {
_codeDataLogger->SaveCdlFile(FolderUtilities::CombinePath(FolderUtilities::GetDebuggerFolder(), FolderUtilities::GetFilename(_romName, false) + ".cdl"));
_stopFlag = true;
if(needPause) {
//ReleaseDebugger is called in the callback for "BeforeEmulationStop"
//calling Pause in this scenario will cause a deadlock, but doing so is
//unnecessary, so we can just skip it.
_console->Pause();
}
{
auto lock = _scriptLock.AcquireSafe();
for(shared_ptr<ScriptHost> script : _scripts) {
//Send a ScriptEnded event to all active scripts
script->ProcessEvent(EventType::ScriptEnded);
}
_scripts.clear();
_hasScript = false;
}
_breakLock.Acquire();
_breakLock.Release();
if(needPause) {
_console->Resume();
}
_released = true;
}
}
void Debugger::SetPpu(shared_ptr<PPU> ppu)
{
_ppu = ppu;
_memoryDumper.reset(new MemoryDumper(_ppu, _memoryManager, _mapper, _codeDataLogger, this, _disassembler));
}
Console* Debugger::GetConsole()
{
return _console.get();
}
void Debugger::Suspend()
{
_suspendCount++;
while(_executionStopped) {}
}
void Debugger::Resume()
{
_suspendCount--;
if(_suspendCount < 0) {
_suspendCount = 0;
}
}
void Debugger::SetFlags(uint32_t flags)
{
bool needUpdate = ((flags ^ _flags) & (int)DebuggerFlags::DisplayOpCodesInLowerCase) != 0;
_flags = flags;
_breakOnFirstCycle = CheckFlag(DebuggerFlags::BreakOnFirstCycle);
if(needUpdate) {
_disassembler->BuildOpCodeTables(CheckFlag(DebuggerFlags::DisplayOpCodesInLowerCase));
}
}
bool Debugger::CheckFlag(DebuggerFlags flag)
{
return (_flags & (uint32_t)flag) == (uint32_t)flag;
}
bool Debugger::LoadCdlFile(string cdlFilepath)
{
if(_codeDataLogger->LoadCdlFile(cdlFilepath)) {
//Can't use DebugBreakHelper due to the fact this is called in the constructor
bool isEmulationThread = _console->GetEmulationThreadId() == std::this_thread::get_id();
if(!isEmulationThread) {
_console->Pause();
}
UpdateCdlCache();
if(!isEmulationThread) {
_console->Resume();
}
return true;
}
return false;
}
void Debugger::SetCdlData(uint8_t* cdlData, uint32_t length)
{
DebugBreakHelper helper(this);
_codeDataLogger->SetCdlData(cdlData, length);
UpdateCdlCache();
}
void Debugger::ResetCdl()
{
DebugBreakHelper helper(this);
_codeDataLogger->Reset();
UpdateCdlCache();
}
void Debugger::UpdateCdlCache()
{
DebugBreakHelper helper(this);
_disassembler->Reset();
for(int i = 0, len = _mapper->GetMemorySize(DebugMemoryType::PrgRom); i < len; i++) {
if(_codeDataLogger->IsCode(i)) {
AddressTypeInfo info = { i, AddressType::PrgRom };
i = _disassembler->BuildCache(info, 0, false, false) - 1;
}
}
_functionEntryPoints.clear();
for(int i = 0, len = _mapper->GetMemorySize(DebugMemoryType::PrgRom); i < len; i++) {
if(_codeDataLogger->IsSubEntryPoint(i)) {
_functionEntryPoints.emplace(i);
//After resetting the cache, set the entry point flags in the disassembly cache
AddressTypeInfo info = { i, AddressType::PrgRom };
_disassembler->BuildCache(info, 0, true, false);
}
}
}
bool Debugger::IsMarkedAsCode(uint16_t relativeAddress)
{
AddressTypeInfo info;
GetAbsoluteAddressAndType(relativeAddress, &info);
if(info.Address >= 0 && info.Type == AddressType::PrgRom) {
return _codeDataLogger->IsCode(info.Address);
} else {
return false;
}
}
shared_ptr<CodeDataLogger> Debugger::GetCodeDataLogger()
{
return _codeDataLogger;
}
shared_ptr<LabelManager> Debugger::GetLabelManager()
{
return _labelManager;
}
shared_ptr<Profiler> Debugger::GetProfiler()
{
return _profiler;
}
void Debugger::SetBreakpoints(Breakpoint breakpoints[], uint32_t length)
{
DebugBreakHelper helper(this);
for(int i = 0; i < Debugger::BreakpointTypeCount; i++) {
_breakpoints[i].clear();
_breakpointRpnList[i].clear();
_hasBreakpoint[i] = false;
}
_bpDummyCpuRequired = false;
_bpExpEval.reset(new ExpressionEvaluator(this));
for(uint32_t j = 0; j < length; j++) {
Breakpoint &bp = breakpoints[j];
for(int i = 0; i < Debugger::BreakpointTypeCount; i++) {
bool isEnabled = bp.IsEnabled() && _console->GetSettings()->CheckFlag(EmulationFlags::DebuggerWindowEnabled);
if((bp.IsMarked() || isEnabled) && bp.HasBreakpointType((BreakpointType)i)) {
_breakpoints[i].push_back(bp);
if(bp.HasCondition()) {
bool success = true;
ExpressionData data = _bpExpEval->GetRpnList(bp.GetCondition(), success);
_breakpointRpnList[i].push_back(success ? data : ExpressionData());
} else {
_breakpointRpnList[i].push_back(ExpressionData());
}
if(isEnabled) {
bool isReadWriteBp = i == BreakpointType::ReadVram || i == BreakpointType::ReadRam || i == BreakpointType::WriteVram || i == BreakpointType::WriteRam || i == BreakpointType::DummyReadRam || i == BreakpointType::DummyWriteRam;
_bpDummyCpuRequired |= isReadWriteBp;
}
_hasBreakpoint[i] = true;
}
}
}
}
bool Debugger::ProcessBreakpoints(BreakpointType type, OperationInfo &operationInfo, bool allowBreak, bool allowMark)
{
//Disable breakpoints if debugger window is closed
allowBreak &= _console->GetSettings()->CheckFlag(EmulationFlags::DebuggerWindowEnabled);
if(_runToCycle != -1) {
//Disable all breakpoints while stepping backwards
return false;
} else if(!allowBreak && !allowMark) {
//Nothing to be done, skip processing
return false;
}
AddressTypeInfo info { -1, AddressType::InternalRam };
PpuAddressTypeInfo ppuInfo { -1, PpuAddressType::None };
bool isPpuBreakpoint = false;
switch(type) {
case BreakpointType::Global:
break;
case BreakpointType::Execute:
case BreakpointType::ReadRam:
case BreakpointType::WriteRam:
case BreakpointType::DummyReadRam:
case BreakpointType::DummyWriteRam:
GetAbsoluteAddressAndType(operationInfo.Address, &info);
break;
case BreakpointType::ReadVram:
case BreakpointType::WriteVram:
GetPpuAbsoluteAddressAndType(operationInfo.Address, &ppuInfo);
isPpuBreakpoint = true;
break;
}
vector<Breakpoint> &breakpoints = _breakpoints[(int)type];
bool needBreak = false;
bool needMark = false;
bool needState = true;
uint32_t markBreakpointId = 0;
uint32_t breakpointId = 0;
EvalResultType resultType;
auto processBreakpoint = [&needMark, &needBreak, &markBreakpointId, &breakpointId](Breakpoint &bp) {
if(bp.IsMarked()) {
needMark = true;
markBreakpointId = bp.GetId();
}
if(bp.IsEnabled()) {
needBreak = true;
breakpointId = bp.GetId();
}
};
for(size_t i = 0, len = breakpoints.size(); i < len; i++) {
Breakpoint &breakpoint = breakpoints[i];
if(
((breakpoint.IsEnabled() && allowBreak) || (breakpoint.IsMarked() && allowMark)) &&
(type == BreakpointType::Global ||
(!isPpuBreakpoint && breakpoint.Matches(operationInfo.Address, info)) ||
(isPpuBreakpoint && breakpoint.Matches(operationInfo.Address, ppuInfo)))
) {
if(!breakpoint.HasCondition()) {
processBreakpoint(breakpoint);
} else {
if(needState) {
GetState(&_debugState, false);
needState = false;
}
if(_bpExpEval->Evaluate(_breakpointRpnList[(int)type][i], _debugState, resultType, operationInfo) != 0) {
processBreakpoint(breakpoint);
}
}
if((needMark || !allowMark) && (needBreak || !allowBreak)) {
//No need to process remaining breakpoints
break;
}
}
}
if(needMark && allowMark) {
_eventManager->AddDebugEvent(DebugEventType::Breakpoint, operationInfo.Address, (uint8_t)operationInfo.Value, markBreakpointId);
}
if(needBreak && allowBreak) {
//Found a matching breakpoint, stop execution
Step(1);
SleepUntilResume(BreakSource::Breakpoint, breakpointId, type, operationInfo.Address, (uint8_t)operationInfo.Value, operationInfo.OperationType);
return true;
} else {
return false;
}
}
void Debugger::ProcessAllBreakpoints(OperationInfo &operationInfo)
{
if(_runToCycle != -1) {
//Disable all breakpoints while stepping backwards
return;
}
if(_hasBreakpoint[BreakpointType::Execute]) {
ProcessBreakpoints(BreakpointType::Execute, operationInfo, true, true);
}
bool checkUninitReads = _enableBreakOnUninitRead && CheckFlag(DebuggerFlags::BreakOnUninitMemoryRead);
if(!checkUninitReads && !_bpDummyCpuRequired) {
//Nothing to do, no read/write breakpoints are active and don't need to check uninit reads
return;
}
_dummyCpu->SetDummyState(_cpu.get());
_dummyCpu->Exec();
DebugState &state = _debugState;
uint32_t readCount = _dummyCpu->GetReadCount();
if(readCount > 0) {
uint16_t addr;
uint8_t value;
bool isDummyRead;
for(uint32_t i = 0; i < readCount; i++) {
_dummyCpu->GetReadAddr(i, addr, value, isDummyRead);
OperationInfo info;
if(addr >= 0x2000 && addr < 0x4000 && (addr & 0x07) == 0x07) {
//Reads to $2007 will trigger a PPU read
if(_hasBreakpoint[BreakpointType::ReadVram]) {
OperationInfo ppuInfo;
ppuInfo.OperationType = MemoryOperationType::Read;
ppuInfo.Address = state.PPU.BusAddress;
ppuInfo.Value = _ppu->PeekRAM(addr);
if(ProcessBreakpoints(BreakpointType::ReadVram, ppuInfo, true, false)) {
return;
}
}
info.Value = state.PPU.MemoryReadBuffer;
} else {
if(!isDummyRead && checkUninitReads) {
//Break on uninit memory read
AddressTypeInfo info;
GetAbsoluteAddressAndType(addr, &info);
if(info.Address >= 0 && _memoryAccessCounter->IsAddressUninitialized(info)) {
Step(1);
SleepUntilResume(BreakSource::BreakOnUninitMemoryRead, 0, BreakpointType::ReadRam, addr, value);
return;
}
}
info.Value = value;
}
info.Address = addr;
if(isDummyRead) {
if(_hasBreakpoint[BreakpointType::DummyReadRam]) {
info.OperationType = MemoryOperationType::DummyRead;
if(ProcessBreakpoints(BreakpointType::DummyReadRam, info, true, false)) {
return;
}
}
} else {
if(_hasBreakpoint[BreakpointType::ReadRam]) {
info.OperationType = MemoryOperationType::Read;
if(ProcessBreakpoints(BreakpointType::ReadRam, info, true, false)) {
return;
}
}
}
}
}
uint32_t writeCount = _dummyCpu->GetWriteCount();
if(writeCount > 0) {
uint16_t addr;
uint8_t value;
bool isDummyWrite;
for(uint32_t i = 0; i < writeCount; i++) {
_dummyCpu->GetWriteAddrValue(i, addr, value, isDummyWrite);
OperationInfo info;
info.Address = addr;
info.Value = value;
if(isDummyWrite) {
if(_hasBreakpoint[BreakpointType::DummyWriteRam]) {
info.OperationType = MemoryOperationType::DummyWrite;
if(ProcessBreakpoints(BreakpointType::DummyWriteRam, info, true, false)) {
return;
}
}
} else {
if(_hasBreakpoint[BreakpointType::WriteRam]) {
info.OperationType = MemoryOperationType::Write;
if(ProcessBreakpoints(BreakpointType::WriteRam, info, true, false)) {
return;
}
}
}
if(_hasBreakpoint[BreakpointType::WriteVram]) {
if(addr >= 0x2000 && addr < 0x4000 && (addr & 0x07) == 0x07) {
//Write to $2007 will trigger a PPU write
OperationInfo ppuInfo;
ppuInfo.Address = state.PPU.BusAddress;
ppuInfo.Value = value;
ppuInfo.OperationType = MemoryOperationType::Write;
if(ProcessBreakpoints(BreakpointType::WriteVram, ppuInfo, true, false)) {
return;
}
}
}
}
}
}
int32_t Debugger::EvaluateExpression(string expression, EvalResultType &resultType, bool useCache)
{
DebugState state;
OperationInfo operationInfo { 0, 0, MemoryOperationType::DummyRead };
GetState(&state);
if(useCache) {
return _watchExpEval->Evaluate(expression, state, resultType, operationInfo);
} else {
ExpressionEvaluator expEval(this);
return expEval.Evaluate(expression, state, resultType, operationInfo);
}
}
void Debugger::UpdateCallstack(uint8_t instruction, uint32_t addr)
{
if((instruction == 0x60 || instruction == 0x40) && !_callstack.empty()) {
//RTS & RTI
uint16_t expectedReturnAddress = _callstack[_callstack.size() - 1].JumpSource;
_callstack.pop_back();
_subReturnAddresses.pop_back();
int spOffset = instruction == 0x40 ? 2 : 1; //RTI has an extra byte on the stack (flags)
uint16_t targetAddr = _memoryManager->DebugReadWord(0x100 + ((_debugState.CPU.SP + spOffset) & 0xFF));
if(targetAddr < expectedReturnAddress || targetAddr - expectedReturnAddress > 3) {
//Mismatch, pop that stack frame and add the new one
if(!_callstack.empty()) {
bool foundMatch = false;
for(int i = (int)_callstack.size() - 1; i >= 0; i--) {
if(targetAddr > _callstack[i].JumpSource && targetAddr < _callstack[i].JumpSource + 3) {
//Found a matching stack frame, unstack until that point
foundMatch = true;
for(int j = (int)_callstack.size() - i - 1; j >= 0; j--) {
_callstack.pop_back();
_subReturnAddresses.pop_back();
_profiler->UnstackFunction();
}
break;
}
}
if(!foundMatch) {
//Couldn't find a matching frame, replace the current one
AddCallstackFrame(expectedReturnAddress, targetAddr, StackFrameFlags::None);
_subReturnAddresses.push_back(expectedReturnAddress + 3);
}
}
}
_profiler->UnstackFunction();
} else if(instruction == 0x20) {
//JSR
uint16_t targetAddr = _memoryManager->DebugRead(addr + 1) | (_memoryManager->DebugRead(addr + 2) << 8);
AddCallstackFrame(addr, targetAddr, StackFrameFlags::None);
_subReturnAddresses.push_back(addr + 3);
AddressTypeInfo dest;
_mapper->GetAbsoluteAddressAndType(targetAddr, &dest);
_profiler->StackFunction(dest, StackFrameFlags::None);
}
}
void Debugger::AddCallstackFrame(uint16_t source, uint16_t target, StackFrameFlags flags)
{
if(_callstack.size() >= 511) {
//Ensure callstack stays below 512 entries - games can use various tricks that could keep making the callstack grow
_callstack.pop_front();
_subReturnAddresses.pop_front();
}
StackFrameInfo stackFrame;
stackFrame.JumpSource = source;
stackFrame.JumpSourceAbsolute = _mapper->ToAbsoluteAddress(source);
stackFrame.JumpTarget = target;
stackFrame.JumpTargetAbsolute = _mapper->ToAbsoluteAddress(target);
stackFrame.Flags = flags;
_callstack.push_back(stackFrame);
}
void Debugger::ProcessInterrupt(uint16_t cpuAddr, uint16_t destCpuAddr, bool forNmi)
{
AddCallstackFrame(cpuAddr, destCpuAddr, forNmi ? StackFrameFlags::Nmi : StackFrameFlags::Irq);
_subReturnAddresses.push_back(cpuAddr);
AddressTypeInfo addressInfo;
_mapper->GetAbsoluteAddressAndType(destCpuAddr, &addressInfo);
_profiler->StackFunction(addressInfo, forNmi ? StackFrameFlags::Nmi : StackFrameFlags::Irq);
ProcessEvent(forNmi ? EventType::Nmi : EventType::Irq);
}
void Debugger::ProcessStepConditions(uint16_t addr)
{
if(_stepOut && (_lastInstruction == 0x60 || _lastInstruction == 0x40) && _stepOutReturnAddress == addr) {
//RTS/RTI found, if we're on the expected return address, break immediately
Step(1);
} else if(_stepOverAddr != -1 && addr == (uint32_t)_stepOverAddr) {
Step(1);
}
}
bool Debugger::IsPpuCycleToProcess()
{
return _proccessPpuCycle[_ppu->GetCurrentCycle()] || _hasBreakpoint[BreakpointType::Global] || _ppuStepCount > 0;
}
void Debugger::ProcessPpuCycle()
{
if(_proccessPpuCycle[_ppu->GetCurrentCycle()]) {
int32_t currentCycle = (_ppu->GetCurrentCycle() << 9) + _ppu->GetCurrentScanline();
for(auto updateCycle : _ppuViewerUpdateCycle) {
if(updateCycle.second == currentCycle) {
_console->GetNotificationManager()->SendNotification(ConsoleNotificationType::PpuViewerDisplayFrame, (void*)(uint64_t)updateCycle.first);
}
}
if(_ppu->GetCurrentCycle() == 0) {
if(_breakOnScanline == _ppu->GetCurrentScanline()) {
Step(1);
SleepUntilResume(BreakSource::Pause);
}
if(_ppu->GetCurrentScanline() == 240) {
ProcessEvent(EventType::EndFrame);
} else if(_ppu->GetCurrentScanline() == -1) {
ProcessEvent(EventType::StartFrame);
}
}
}
if(_hasBreakpoint[BreakpointType::Global]) {
OperationInfo operationInfo { 0, 0, MemoryOperationType::DummyRead };
ProcessBreakpoints(BreakpointType::Global, operationInfo);
}
if(_ppuStepCount > 0) {
_ppuStepCount--;
if(_ppuStepCount == 0) {
Step(1);
SleepUntilResume(BreakSource::PpuStep);
}
}
}
bool Debugger::ProcessRamOperation(MemoryOperationType type, uint16_t &addr, uint8_t &value)
{
OperationInfo operationInfo { addr, (int16_t)value, type };
_memoryOperationType = type;
bool isDmcRead = false;
if(type == MemoryOperationType::DmcRead) {
//Used to flag the data in the CDL file
isDmcRead = true;
type = MemoryOperationType::Read;
_eventManager->AddDebugEvent(DebugEventType::DmcDmaRead, addr, value);
}
ProcessCpuOperation(addr, value, type);
if(type == MemoryOperationType::ExecOpCode) {
_cpu->SetDebugPC(addr);
if(_runToCycle == -1) {
_rewindCache.clear();
_rewindPrevInstructionCycleCache.clear();
}
if(_nextReadAddr != -1) {
//SetNextStatement (either from manual action or code runner)
if(addr < 0x3000 || addr >= 0x4000) {
_returnToAddress = addr;
}
addr = _nextReadAddr;
value = _memoryManager->DebugRead(addr, true);
_cpu->SetDebugPC(addr);
_nextReadAddr = -1;
} else if(_needRewind) {
//Step back - Need to load a state, and then alter the current opcode based on the new program counter
if(!_rewindCache.empty()) {
//Restore the state, and the cycle number of the instruction that preceeded that state
//Otherwise, the target cycle number when building the next cache will be incorrect
_console->LoadState(_rewindCache.back());
_curInstructionCycle = _rewindPrevInstructionCycleCache.back();
_rewindCache.pop_back();
_rewindPrevInstructionCycleCache.pop_back();
//This state is for the instruction we want to stop on, break here.
_runToCycle = -1;
Step(1);
} else {
_console->GetRewindManager()->StartRewinding(true);
//Adjust the cycle counter by 1 because the state was taken before the instruction started
//whereas the CPU already read the first byte of the instruction by the time we get here
State cpuState;
_cpu->GetState(cpuState);
cpuState.CycleCount++;
_cpu->SetState(cpuState);
}
UpdateProgramCounter(addr, value);
_needRewind = false;
}
ProcessScriptSaveState(addr, value);
_currentReadAddr = &addr;
_currentReadValue = &value;
}
//Check if a breakpoint has been hit and freeze execution if one has
bool breakDone = false;
AddressTypeInfo addressInfo;
GetAbsoluteAddressAndType(addr, &addressInfo);
int32_t absoluteAddr = addressInfo.Type == AddressType::PrgRom ? addressInfo.Address : -1;
if(addressInfo.Type == AddressType::PrgRom && addressInfo.Address >= 0 && type != MemoryOperationType::DummyRead && type != MemoryOperationType::DummyWrite && _runToCycle == -1) {
if(type == MemoryOperationType::ExecOperand) {
_codeDataLogger->SetFlag(absoluteAddr, CdlPrgFlags::Code);
} else if(type == MemoryOperationType::Read) {
_codeDataLogger->SetFlag(absoluteAddr, CdlPrgFlags::Data);
if(isDmcRead) {
_codeDataLogger->SetFlag(absoluteAddr, CdlPrgFlags::PcmData);
}
}
}
if(type == MemoryOperationType::ExecOpCode) {
_opCodeCycle = 0;
_prevInstructionCycle = _curInstructionCycle;
_curInstructionCycle = (int64_t)_cpu->GetCycleCount();
if(absoluteAddr >= 0) {
_codeDataLogger->SetFlag(absoluteAddr, CdlPrgFlags::Code);
}
if(addressInfo.Address >= 0) {
_disassembler->BuildCache(addressInfo, addr, false, true);
if(_disassembler->IsJump(value)) {
uint16_t targetPc = _disassembler->GetDisassemblyInfo(addressInfo).GetJumpDestination(_cpu->GetPC(), _memoryManager.get());
AddressTypeInfo targetAddr;
GetAbsoluteAddressAndType(targetPc, &targetAddr);
if(targetAddr.Address >= 0 && targetAddr.Type == AddressType::PrgRom) {
if(value == 0x20) {
//JSR, mark target as a sub entry point
_disassembler->BuildCache(targetAddr, targetPc, true, false);
_functionEntryPoints.emplace(targetAddr.Address);
_codeDataLogger->SetFlag(targetAddr.Address, CdlPrgFlags::SubEntryPoint);
} else {
//Only mark as jump target if not marked as sub entry point
_codeDataLogger->SetFlag(targetAddr.Address, CdlPrgFlags::JumpTarget);
}
}
}
_performanceTracker->ProcessCpuExec(addressInfo);
}
ProcessStepConditions(addr);
BreakSource breakSource = BreakSource::Unspecified;
if(value == 0 && CheckFlag(DebuggerFlags::BreakOnBrk)) {
Step(1);
breakSource = BreakSource::BreakOnBrk;
} else if(CheckFlag(DebuggerFlags::BreakOnUnofficialOpCode) && _disassembler->IsUnofficialOpCode(value)) {
Step(1);
breakSource = BreakSource::BreakOnUnofficialOpCode;
}
if(_runToCycle != -1) {
if((int64_t)_cpu->GetCycleCount() >= _runToCycle) {
//Step back operation is done, revert RewindManager's state & break debugger
_console->GetRewindManager()->StopRewinding(true);
_runToCycle = -1;
Step(1);
} else if(_runToCycle - (int64_t)_cpu->GetCycleCount() < 500) {
_rewindCache.push_back(stringstream());
_console->SaveState(_rewindCache.back());
_rewindPrevInstructionCycleCache.push_back(_prevInstructionCycle);
}
}
_lastInstruction = value;
breakDone = SleepUntilResume(breakSource);
if(_codeRunner && !_codeRunner->IsRunning()) {
_codeRunner.reset();
}
GetState(&_debugState, false);
DisassemblyInfo disassemblyInfo;
if(_codeRunner && _codeRunner->IsRunning() && addr >= 0x3000 && addr < 0x4000) {
disassemblyInfo = _codeRunner->GetDisassemblyInfo(addr);
} else {
if(addressInfo.Address >= 0) {
disassemblyInfo = _disassembler->GetDisassemblyInfo(addressInfo);
} else {
disassemblyInfo.Initialize(addr, _memoryManager.get(), false);
}
}
_traceLogger->Log(_debugState, disassemblyInfo, operationInfo);
} else {
_opCodeCycle++;
_traceLogger->LogNonExec(operationInfo);
}
if(!breakDone && _stepCycleCount > 0) {
_stepCycleCount--;
if(_stepCycleCount == 0) {
Step(1);
breakDone = SleepUntilResume(BreakSource::CpuStep);
}
}
BreakpointType breakpointType;
switch(type) {
default: breakpointType = BreakpointType::Execute; break;
case MemoryOperationType::DummyRead:
case MemoryOperationType::Read: breakpointType = BreakpointType::ReadRam; break;
case MemoryOperationType::DummyWrite:
case MemoryOperationType::Write: breakpointType = BreakpointType::WriteRam; break;
}
//For DMC reads, always break when it happens, rather than at the start (because we can't predict it easily)
if(_breakOnFirstCycle && !isDmcRead) {
if(type == MemoryOperationType::ExecOpCode && !breakDone) {
ProcessAllBreakpoints(operationInfo);
} else if(_hasBreakpoint[breakpointType]) {
//Process marked breakpoints
ProcessBreakpoints(breakpointType, operationInfo, false, true);
}
} else {
if(_hasBreakpoint[breakpointType]) {
ProcessBreakpoints(breakpointType, operationInfo, !breakDone, true);
}
}
_currentReadAddr = nullptr;
_currentReadValue = nullptr;
if(type == MemoryOperationType::Write) {
if(_runToCycle == -1 && !CheckFlag(DebuggerFlags::IgnoreRedundantWrites) || _memoryManager->DebugRead(addr) != value) {
_memoryAccessCounter->ProcessMemoryWrite(addressInfo, _cpu->GetCycleCount());
}
_disassembler->InvalidateCache(addressInfo);
if(addr >= 0x2000 && addr <= 0x3FFF) {
if((addr & 0x07) == 5 || (addr & 0x07) == 6) {
GetState(&_debugState, false);
_eventManager->AddDebugEvent(DebugEventType::PpuRegisterWrite, addr, value, -1, _debugState.PPU.State.WriteToggle ? 1 : 0);
} else {
_eventManager->AddDebugEvent(DebugEventType::PpuRegisterWrite, addr, value);
}
} else if(addr >= 0x4018 && _mapper->IsWriteRegister(addr)) {
_eventManager->AddDebugEvent(DebugEventType::MapperRegisterWrite, addr, value);
} else if(addr >= 0x4000 && addr <= 0x4015 || addr == 0x4017) {
_eventManager->AddDebugEvent(DebugEventType::ApuRegisterWrite, addr, value);
} else if(addr == 0x4016) {
_eventManager->AddDebugEvent(DebugEventType::ControlRegisterWrite, addr, value);
}
if(_frozenAddresses[addr]) {
return false;
}
} else if(type == MemoryOperationType::Read) {
if(addr >= 0x2000 && addr <= 0x3FFF) {
_eventManager->AddDebugEvent(DebugEventType::PpuRegisterRead, addr, value);
} else if(addr >= 0x4018 && _mapper->IsReadRegister(addr)) {
_eventManager->AddDebugEvent(DebugEventType::MapperRegisterRead, addr, value);
} else if(addr >= 0x4000 && addr <= 0x4015) {
_eventManager->AddDebugEvent(DebugEventType::ApuRegisterRead, addr, value);
} else if(addr == 0x4016 || addr == 0x4017) {
_eventManager->AddDebugEvent(DebugEventType::ControlRegisterRead, addr, value);
}
//Ignore dummy read/writes and do not change counters while using the step back feature
if(_runToCycle == -1 && _memoryAccessCounter->ProcessMemoryRead(addressInfo, _cpu->GetCycleCount())) {
if(!breakDone && !_breakOnFirstCycle && _enableBreakOnUninitRead && CheckFlag(DebuggerFlags::BreakOnUninitMemoryRead)) {
//Break on uninit memory read
Step(1);
SleepUntilResume(BreakSource::BreakOnUninitMemoryRead, 0, BreakpointType::Global, operationInfo.Address, (uint8_t)operationInfo.Value, operationInfo.OperationType);
}
}
} else {
if(_runToCycle == -1 && (type == MemoryOperationType::ExecOpCode || type == MemoryOperationType::ExecOperand)) {
_memoryAccessCounter->ProcessMemoryExec(addressInfo, _cpu->GetCycleCount());
}
if(!_needRewind && type == MemoryOperationType::ExecOpCode) {
UpdateCallstack(_lastInstruction, addr);
}
}
return true;
}
bool Debugger::SleepUntilResume(BreakSource source, uint32_t breakpointId, BreakpointType bpType, uint16_t bpAddress, uint8_t bpValue, MemoryOperationType bpMemOpType)
{
int32_t stepCount = _stepCount.load();
if(stepCount > 0) {
_stepCount--;
stepCount = _stepCount.load();
} else if(stepCount == 0) {
//If stepCount was already 0 when we enter the function, it means
//Debugger::Suspend() and Debugger::Resume() were called by another thread
source = BreakSource::BreakAfterSuspend;
}
//Read both values here since they might change while executing the code below
int32_t preventResume = _preventResume;
bool breakRequested = _breakRequested;
if((stepCount == 0 || breakRequested) && !_stopFlag && _suspendCount == 0) {
//Break
auto lock = _breakLock.AcquireSafe();
if(preventResume == 0) {
_console->GetSoundMixer()->StopAudio();
if(source == BreakSource::Unspecified) {
source = _breakSource;
}
_breakSource = BreakSource::Unspecified;
uint64_t param = (
((uint64_t)breakpointId << 40) |
((uint64_t)bpValue << 32) |
((uint64_t)(bpAddress & 0xFFFF) << 16) |
((uint64_t)((int)bpMemOpType & 0x0F) << 12) |
((uint64_t)(bpType & 0x0F) << 8) |
((uint64_t)source & 0xFF)
);
_console->GetNotificationManager()->SendNotification(ConsoleNotificationType::CodeBreak, (void*)(uint64_t)param);
ProcessEvent(EventType::CodeBreak);
_stepOverAddr = -1;
if(CheckFlag(DebuggerFlags::PpuPartialDraw)) {
_ppu->DebugSendFrame();
}
}
_executionStopped = true;
_pausedForDebugHelper = breakRequested;
while((((stepCount == 0 || _breakRequested) && _suspendCount == 0) || _preventResume > 0) && !_stopFlag) {
std::this_thread::sleep_for(std::chrono::duration<int, std::milli>(10));
if(stepCount == 0) {
_console->ResetRunTimers();
}
stepCount = _stepCount.load();
}
_pausedForDebugHelper = false;
_executionStopped = false;
return true;
}
return false;
}
void Debugger::ProcessVramReadOperation(MemoryOperationType type, uint16_t addr, uint8_t &value)
{
PpuAddressTypeInfo addressInfo;
_mapper->GetPpuAbsoluteAddressAndType(addr, &addressInfo);
_codeDataLogger->SetFlag(addressInfo.Address, type == MemoryOperationType::Read ? CdlChrFlags::Read : CdlChrFlags::Drawn);
if(_hasBreakpoint[BreakpointType::ReadVram]) {
OperationInfo operationInfo{ addr, value, type };
ProcessBreakpoints(BreakpointType::ReadVram, operationInfo, !_breakOnFirstCycle, true);
}
_memoryAccessCounter->ProcessPpuMemoryRead(addressInfo, _cpu->GetCycleCount());
ProcessPpuOperation(addr, value, MemoryOperationType::Read);
}
void Debugger::ProcessVramWriteOperation(uint16_t addr, uint8_t &value)
{
PpuAddressTypeInfo addressInfo;
_mapper->GetPpuAbsoluteAddressAndType(addr, &addressInfo);
if(_hasBreakpoint[BreakpointType::WriteVram]) {
OperationInfo operationInfo{ addr, value, MemoryOperationType::Write };
ProcessBreakpoints(BreakpointType::WriteVram, operationInfo, !_breakOnFirstCycle, true);
}
_memoryAccessCounter->ProcessPpuMemoryWrite(addressInfo, _cpu->GetCycleCount());
ProcessPpuOperation(addr, value, MemoryOperationType::Write);
}
void Debugger::GetInstructionProgress(InstructionProgress &state)
{
state.OpCode = _lastInstruction;
state.OpCycle = _opCodeCycle;
state.OpMemoryOperationType = _memoryOperationType;
}
void Debugger::GetApuState(ApuState *state)
{
//Pause the emulation
DebugBreakHelper helper(this);
//Force APU to catch up before we retrieve its state
_apu->Run();
*state = _apu->GetState();
}
void Debugger::GetState(DebugState *state, bool includeMapperInfo)
{
state->Model = _console->GetModel();
state->ClockRate = _cpu->GetClockRate(_console->GetModel());
_cpu->GetState(state->CPU);
_ppu->GetState(state->PPU);
if(includeMapperInfo) {
state->Cartridge = _mapper->GetState();
state->APU = _apu->GetState();
}
}
void Debugger::SetState(DebugState state)
{
_cpu->SetState(state.CPU);
_ppu->SetState(state.PPU);
if(state.CPU.PC != _cpu->GetPC()) {
SetNextStatement(state.CPU.PC);
}
}
void Debugger::Break()
{
_breakRequested = true;
}
void Debugger::ResumeFromBreak()
{
_breakRequested = false;
}
void Debugger::ResetStepState()
{
_ppuStepCount = -1;
_stepOverAddr = -1;
_stepCycleCount = -1;
_stepCount = -1;
_breakOnScanline = -2;
_stepOut = false;
}
void Debugger::PpuStep(uint32_t count)
{
ResetStepState();
_ppuStepCount = count;
_breakSource = BreakSource::PpuStep;
}
void Debugger::Step(uint32_t count, BreakSource source)
{
//Run CPU for [count] INSTRUCTIONS before breaking again
ResetStepState();
_stepCount = count;
_breakSource = source;
}
void Debugger::StepCycles(uint32_t count)
{
//Run CPU for [count] CYCLES before breaking again
ResetStepState();
_stepCycleCount = count;
_breakSource = BreakSource::CpuStep;
}
void Debugger::StepOut()
{
if(_subReturnAddresses.empty()) {
return;
}
ResetStepState();
_stepOut = true;
_stepOutReturnAddress = _subReturnAddresses.back();
}
void Debugger::StepOver()
{
if(_lastInstruction == 0x20 || _lastInstruction == 0x00) {
//We are on a JSR/BRK instruction, need to continue until the following instruction
_stepOverAddr = _cpu->GetPC() + (_lastInstruction == 0x20 ? 3 : 1);
Run();
} else {
//Except for JSR & BRK, StepOver behaves the same as StepTnto
Step(1);
}
}
void Debugger::StepBack()
{
if(_runToCycle == -1 && _prevInstructionCycle < _curInstructionCycle) {
_runToCycle = _prevInstructionCycle;
_needRewind = true;
Run();
}
}
void Debugger::Run()
{
//Resume execution after a breakpoint has been hit
_ppuStepCount = -1;
_stepCount = -1;
_breakOnScanline = -2;
_stepCycleCount = -1;
_stepOut = false;
}
void Debugger::BreakImmediately(BreakSource source)
{
Step(1);
SleepUntilResume(source);
}
void Debugger::BreakOnScanline(int16_t scanline)
{
Run();
_breakOnScanline = scanline;
}
void Debugger::GenerateCodeOutput()
{
State cpuState;
_cpu->GetState(cpuState);
_disassemblerOutput.clear();
_disassemblerOutput.reserve(10000);
for(uint32_t i = 0; i < 0x10000; i += 0x100) {
//Merge all sequential ranges into 1 chunk
AddressTypeInfo startInfo, currentInfo, endInfo;
GetAbsoluteAddressAndType(i, &startInfo);
currentInfo = startInfo;
GetAbsoluteAddressAndType(i+0x100, &endInfo);
uint32_t startMemoryAddr = i;
int32_t startAddr, endAddr;
if(startInfo.Address >= 0) {
startAddr = startInfo.Address;
endAddr = startAddr + 0xFF;
while(currentInfo.Type == endInfo.Type && currentInfo.Address + 0x100 == endInfo.Address && i < 0x10000) {
endAddr += 0x100;
currentInfo = endInfo;
i+=0x100;
GetAbsoluteAddressAndType(i + 0x100, &endInfo);
}
_disassemblerOutput += _disassembler->GetCode(startInfo, endAddr, startMemoryAddr, cpuState, _memoryManager, _labelManager);
}
}
}
const char* Debugger::GetCode(uint32_t &length)
{
string previousCode = _disassemblerOutput;
GenerateCodeOutput();
bool forceRefresh = length == (uint32_t)-1;
length = (uint32_t)_disassemblerOutput.size();
if(!forceRefresh && previousCode.compare(_disassemblerOutput) == 0) {
//Return null pointer if the code is identical to last call
//This avois the UTF8->UTF16 conversion that the UI needs to do
//before comparing the strings
return nullptr;
} else {
return _disassemblerOutput.c_str();
}
}
int32_t Debugger::GetRelativeAddress(uint32_t addr, AddressType type)
{
switch(type) {
case AddressType::InternalRam:
case AddressType::Register:
return addr;
case AddressType::PrgRom:
case AddressType::WorkRam:
case AddressType::SaveRam:
return _mapper->FromAbsoluteAddress(addr, type);
}
return -1;
}
int32_t Debugger::GetRelativePpuAddress(uint32_t addr, PpuAddressType type)
{
if(type == PpuAddressType::PaletteRam) {
return 0x3F00 | (addr & 0x1F);
}
return _mapper->FromAbsolutePpuAddress(addr, type);
}
int32_t Debugger::GetAbsoluteAddress(uint32_t addr)
{
return _mapper->ToAbsoluteAddress(addr);
}
int32_t Debugger::GetAbsoluteChrAddress(uint32_t addr)
{
return _mapper->ToAbsoluteChrAddress(addr);
}
void Debugger::SetNextStatement(uint16_t addr)
{
if(_currentReadAddr) {
_cpu->SetDebugPC(addr);
*_currentReadAddr = addr;
*_currentReadValue = _memoryManager->DebugRead(addr, false);
} else {
//Can't change the address right away (CPU is in the middle of an instruction)
//Address will change after current instruction is done executing
_nextReadAddr = addr;
//Force the current instruction to finish
Step(1);
}
}
void Debugger::GetCallstack(StackFrameInfo* callstackArray, uint32_t &callstackSize)
{
DebugBreakHelper helper(this);
int i = 0;
for(StackFrameInfo &info : _callstack) {
callstackArray[i] = info;
i++;
}
callstackSize = i;
}
int32_t Debugger::GetFunctionEntryPointCount()
{
DebugBreakHelper helper(this);
return (int32_t)_functionEntryPoints.size();
}
void Debugger::GetFunctionEntryPoints(int32_t* entryPoints, int32_t maxCount)
{
DebugBreakHelper helper(this);
int32_t i = 0;
for(auto itt = _functionEntryPoints.begin(); itt != _functionEntryPoints.end(); itt++) {
entryPoints[i] = *itt;
i++;
if(i == maxCount - 1) {
break;
}
}
entryPoints[i] = -1;
}
shared_ptr<Assembler> Debugger::GetAssembler()
{
return _assembler;
}
shared_ptr<TraceLogger> Debugger::GetTraceLogger()
{
return _traceLogger;
}
shared_ptr<MemoryDumper> Debugger::GetMemoryDumper()
{
return _memoryDumper;
}
shared_ptr<MemoryAccessCounter> Debugger::GetMemoryAccessCounter()
{
return _memoryAccessCounter;
}
shared_ptr<PerformanceTracker> Debugger::GetPerformanceTracker()
{
return _performanceTracker;
}
shared_ptr<EventManager> Debugger::GetEventManager()
{
return _eventManager;
}
bool Debugger::IsExecutionStopped()
{
return _executionStopped || _console->IsExecutionStopped();
}
bool Debugger::IsPauseIconShown()
{
return (_executionStopped || _console->IsPaused()) && !CheckFlag(DebuggerFlags::HidePauseIcon) && _preventResume == 0 && !_pausedForDebugHelper;
}
void Debugger::PreventResume()
{
_preventResume++;
}
void Debugger::AllowResume()
{
_preventResume--;
}
void Debugger::GetAbsoluteAddressAndType(uint32_t relativeAddr, AddressTypeInfo* info)
{
return _mapper->GetAbsoluteAddressAndType(relativeAddr, info);
}
void Debugger::GetPpuAbsoluteAddressAndType(uint32_t relativeAddr, PpuAddressTypeInfo* info)
{
return _mapper->GetPpuAbsoluteAddressAndType(relativeAddr, info);
}
void Debugger::UpdatePpuCyclesToProcess()
{
memset(_proccessPpuCycle, 0, sizeof(_proccessPpuCycle));
for(auto updateCycle : _ppuViewerUpdateCycle) {
int16_t cycle = updateCycle.second >> 9;
if(cycle < 341) {
_proccessPpuCycle[cycle] = true;
}
}
_proccessPpuCycle[0] = true;
}
void Debugger::SetPpuViewerScanlineCycle(int32_t ppuViewerId, int32_t scanline, int32_t cycle)
{
DebugBreakHelper helper(this);
_ppuViewerUpdateCycle[ppuViewerId] = (cycle << 9) + scanline;
UpdatePpuCyclesToProcess();
}
void Debugger::ClearPpuViewerSettings(int32_t ppuViewer)
{
DebugBreakHelper helper(this);
_ppuViewerUpdateCycle.erase(ppuViewer);
UpdatePpuCyclesToProcess();
}
void Debugger::SetLastFramePpuScroll(uint16_t addr, uint8_t xScroll, bool updateHorizontalScrollOnly)
{
_ppuScrollX = ((addr & 0x1F) << 3) | xScroll | ((addr & 0x400) ? 0x100 : 0);
if(!updateHorizontalScrollOnly) {
_ppuScrollY = (((addr & 0x3E0) >> 2) | ((addr & 0x7000) >> 12)) + ((addr & 0x800) ? 240 : 0);
}
}
uint32_t Debugger::GetPpuScroll()
{
return (_ppuScrollY << 16) | _ppuScrollX;
}
void Debugger::SetFreezeState(uint16_t address, bool frozen)
{
_frozenAddresses[address] = frozen ? 1 : 0;
}
void Debugger::GetFreezeState(uint16_t startAddress, uint16_t length, bool* freezeState)
{
for(uint16_t i = 0; i < length; i++) {
freezeState[i] = _frozenAddresses[startAddress + i] ? true : false;
}
}
void Debugger::StartCodeRunner(uint8_t *byteCode, uint32_t codeLength)
{
_codeRunner.reset(new CodeRunner(vector<uint8_t>(byteCode, byteCode + codeLength), this));
_memoryManager->RegisterIODevice(_codeRunner.get());
_returnToAddress = _cpu->GetDebugPC();
SetNextStatement(CodeRunner::BaseAddress);
}
void Debugger::StopCodeRunner()
{
_memoryManager->UnregisterIODevice(_codeRunner.get());
_memoryManager->RegisterIODevice(_ppu.get());
//Break debugger when code has finished executing
SetNextStatement(_returnToAddress);
if(_console->GetSettings()->CheckFlag(EmulationFlags::DebuggerWindowEnabled)) {
Step(1);
} else {
Run();
}
}
void Debugger::GetNesHeader(uint8_t* header)
{
NESHeader nesHeader = _mapper->GetRomInfo().NesHeader;
memcpy(header, &nesHeader, sizeof(NESHeader));
}
void Debugger::SaveRomToDisk(string filename, bool saveAsIps, uint8_t* header, CdlStripFlag cdlStripflag)
{
vector<uint8_t> fileData;
_mapper->GetRomFileData(fileData, saveAsIps, header);
_codeDataLogger->StripData(fileData.data() + sizeof(NESHeader), cdlStripflag);
ofstream file(filename, ios::out | ios::binary);
if(file.good()) {
file.write((char*)fileData.data(), fileData.size());
file.close();
}
}
void Debugger::RevertPrgChrChanges()
{
DebugBreakHelper helper(this);
_mapper->RevertPrgChrChanges();
_disassembler->Reset();
UpdateCdlCache();
}
bool Debugger::HasPrgChrChanges()
{
return _mapper->HasPrgChrChanges();
}
int32_t Debugger::FindSubEntryPoint(uint16_t relativeAddress)
{
AddressTypeInfo info;
int32_t address = relativeAddress;
do {
GetAbsoluteAddressAndType(address, &info);
if(info.Address < 0 || info.Type != AddressType::PrgRom || _codeDataLogger->IsData(info.Address)) {
break;
}
address--;
if(_codeDataLogger->IsSubEntryPoint(info.Address)) {
break;
}
} while(address >= 0);
return address > relativeAddress ? relativeAddress : (address + 1);
}
void Debugger::SetInputOverride(uint8_t port, uint32_t state)
{
_inputOverride[port] = state;
}
int Debugger::LoadScript(string name, string content, int32_t scriptId)
{
DebugBreakHelper helper(this);
auto lock = _scriptLock.AcquireSafe();
if(scriptId < 0) {
shared_ptr<ScriptHost> script(new ScriptHost(_nextScriptId++));
script->LoadScript(name, content, this);
_scripts.push_back(script);
_hasScript = true;
return script->GetScriptId();
} else {
auto result = std::find_if(_scripts.begin(), _scripts.end(), [=](shared_ptr<ScriptHost> &script) {
return script->GetScriptId() == scriptId;
});
if(result != _scripts.end()) {
//Send a ScriptEnded event before reloading the code
(*result)->ProcessEvent(EventType::ScriptEnded);
(*result)->LoadScript(name, content, this);
return scriptId;
}
}
return -1;
}
void Debugger::RemoveScript(int32_t scriptId)
{
DebugBreakHelper helper(this);
auto lock = _scriptLock.AcquireSafe();
_scripts.erase(std::remove_if(_scripts.begin(), _scripts.end(), [=](const shared_ptr<ScriptHost>& script) {
if(script->GetScriptId() == scriptId) {
//Send a ScriptEnded event before unloading the script
script->ProcessEvent(EventType::ScriptEnded);
_console->GetDebugHud()->ClearScreen();
return true;
}
return false;
}), _scripts.end());
_hasScript = _scripts.size() > 0;
}
const char* Debugger::GetScriptLog(int32_t scriptId)
{
auto lock = _scriptLock.AcquireSafe();
for(shared_ptr<ScriptHost> &script : _scripts) {
if(script->GetScriptId() == scriptId) {
return script->GetLog();
}
}
return "";
}
void Debugger::ResetCounters()
{
//This is called when loading a state (among other things)
//Prevent counter reset when using step back (_runToCycle != 0), because step back will load a state
if(_runToCycle == -1) {
_memoryAccessCounter->ResetCounts();
}
_profiler->Reset();
}
void Debugger::UpdateProgramCounter(uint16_t &addr, uint8_t &value)
{
addr = _cpu->GetPC();
value = _memoryManager->DebugRead(addr, true);
_cpu->SetDebugPC(addr);
}
void Debugger::ProcessScriptSaveState(uint16_t &addr, uint8_t &value)
{
if(_hasScript) {
for(shared_ptr<ScriptHost> &script : _scripts) {
if(script->ProcessSavestate()) {
//Adjust PC and current addr/value if a state was loaded due to a call to loadSavestateAsync
UpdateProgramCounter(addr, value);
}
}
}
}
void Debugger::ProcessCpuOperation(uint16_t &addr, uint8_t &value, MemoryOperationType type)
{
if(_hasScript) {
for(shared_ptr<ScriptHost> &script : _scripts) {
script->ProcessCpuOperation(addr, value, type);
if(type == MemoryOperationType::ExecOpCode && script->CheckStateLoadedFlag()) {
//Adjust PC and current addr/value when a state was loaded during a CpuExec callback
UpdateProgramCounter(addr, value);
}
}
}
}
void Debugger::ProcessPpuOperation(uint16_t addr, uint8_t &value, MemoryOperationType type)
{
if(_hasScript) {
for(shared_ptr<ScriptHost> &script : _scripts) {
script->ProcessPpuOperation(addr, value, type);
}
}
}
void Debugger::ProcessEvent(EventType type)
{
if(_hasScript) {
for(shared_ptr<ScriptHost> &script : _scripts) {
script->ProcessEvent(type);
}
}
switch(type) {
case EventType::InputPolled:
for(int i = 0; i < 4; i++) {
if(_inputOverride[i] != 0) {
shared_ptr<StandardController> controller = std::dynamic_pointer_cast<StandardController>(_console->GetControlManager()->GetControlDevice(i));
if(controller) {
controller->SetBitValue(StandardController::Buttons::A, (_inputOverride[i] & 0x01) != 0);
controller->SetBitValue(StandardController::Buttons::B, (_inputOverride[i] & 0x02) != 0);
controller->SetBitValue(StandardController::Buttons::Select, (_inputOverride[i] & 0x04) != 0);
controller->SetBitValue(StandardController::Buttons::Start, (_inputOverride[i] & 0x08) != 0);
controller->SetBitValue(StandardController::Buttons::Up, (_inputOverride[i] & 0x10) != 0);
controller->SetBitValue(StandardController::Buttons::Down, (_inputOverride[i] & 0x20) != 0);
controller->SetBitValue(StandardController::Buttons::Left, (_inputOverride[i] & 0x40) != 0);
controller->SetBitValue(StandardController::Buttons::Right, (_inputOverride[i] & 0x80) != 0);
}
}
}
break;
case EventType::EndFrame:
_performanceTracker->ProcessEndOfFrame();
_memoryDumper->GatherChrPaletteInfo();
break;
case EventType::StartFrame:
//Update the event viewer
_console->GetNotificationManager()->SendNotification(ConsoleNotificationType::EventViewerDisplayFrame);
//Clear the current frame/event log
if(CheckFlag(DebuggerFlags::PpuPartialDraw)) {
_ppu->DebugUpdateFrameBuffer(CheckFlag(DebuggerFlags::PpuShowPreviousFrame));
}
_eventManager->ClearFrameEvents();
break;
case EventType::Nmi: _eventManager->AddDebugEvent(DebugEventType::Nmi); break;
case EventType::Irq: _eventManager->AddDebugEvent(DebugEventType::Irq); break;
case EventType::SpriteZeroHit: _eventManager->AddDebugEvent(DebugEventType::SpriteZeroHit); break;
case EventType::Reset: _enableBreakOnUninitRead = true; break;
case EventType::BusConflict:
if(CheckFlag(DebuggerFlags::BreakOnBusConflict)) {
BreakImmediately(BreakSource::BreakOnBusConflict);
}
break;
default: break;
}
}
uint32_t Debugger::GetScreenPixel(uint8_t x, uint8_t y)
{
return _ppu->GetPixel(x, y);
}
void Debugger::AddTrace(const char* log)
{
_traceLogger->LogExtraInfo(log, _cpu->GetCycleCount());
}