1681 lines
48 KiB
C++
1681 lines
48 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;
|
|
bool isUnlogged = absoluteAddr >= 0 ? _codeDataLogger->IsNone(absoluteAddr) : false;
|
|
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;
|
|
} else if(CheckFlag(DebuggerFlags::BreakOnUnlogged) && isUnlogged && type == MemoryOperationType::ExecOpCode && absoluteAddr >= 0) {
|
|
Step(1);
|
|
breakSource = BreakSource::BreakOnUnlogged;
|
|
}
|
|
|
|
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;
|
|
int whilePausedRunCounter = 0;
|
|
while((((stepCount == 0 || _breakRequested) && _suspendCount == 0) || _preventResume > 0) && !_stopFlag) {
|
|
std::this_thread::sleep_for(std::chrono::duration<int, std::milli>(10));
|
|
if (preventResume == 0) {
|
|
whilePausedRunCounter++;
|
|
if (whilePausedRunCounter > 10) {
|
|
ProcessEvent(EventType::WhilePaused);
|
|
whilePausedRunCounter = 0;
|
|
}
|
|
}
|
|
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());
|
|
}
|