2020-05-18 16:10:53 -04:00
|
|
|
#include "stdafx.h"
|
|
|
|
#include "GbDebugger.h"
|
|
|
|
#include "DisassemblyInfo.h"
|
|
|
|
#include "Disassembler.h"
|
|
|
|
#include "Gameboy.h"
|
|
|
|
#include "TraceLogger.h"
|
|
|
|
#include "CallstackManager.h"
|
|
|
|
#include "BreakpointManager.h"
|
|
|
|
#include "MemoryManager.h"
|
|
|
|
#include "Debugger.h"
|
|
|
|
#include "Console.h"
|
|
|
|
#include "MemoryAccessCounter.h"
|
|
|
|
#include "ExpressionEvaluator.h"
|
|
|
|
#include "EmuSettings.h"
|
|
|
|
#include "BaseCartridge.h"
|
|
|
|
#include "GameboyDisUtils.h"
|
2020-06-06 16:13:02 -04:00
|
|
|
#include "CodeDataLogger.h"
|
2020-05-21 20:57:00 -04:00
|
|
|
#include "GbEventManager.h"
|
|
|
|
#include "BaseEventManager.h"
|
2020-06-06 22:27:54 -04:00
|
|
|
#include "GbAssembler.h"
|
2020-06-23 18:34:03 -04:00
|
|
|
#include "../Utilities/HexUtilities.h"
|
2020-05-18 16:10:53 -04:00
|
|
|
|
|
|
|
GbDebugger::GbDebugger(Debugger* debugger)
|
|
|
|
{
|
|
|
|
_debugger = debugger;
|
2020-06-06 23:12:03 -04:00
|
|
|
_console = debugger->GetConsole().get();
|
2020-05-18 16:10:53 -04:00
|
|
|
_traceLogger = debugger->GetTraceLogger().get();
|
|
|
|
_disassembler = debugger->GetDisassembler().get();
|
|
|
|
_memoryAccessCounter = debugger->GetMemoryAccessCounter().get();
|
|
|
|
_gameboy = debugger->GetConsole()->GetCartridge()->GetGameboy();
|
|
|
|
_settings = debugger->GetConsole()->GetSettings().get();
|
2020-06-06 16:13:02 -04:00
|
|
|
_codeDataLogger.reset(new CodeDataLogger(_gameboy->DebugGetMemorySize(SnesMemoryType::GbPrgRom), CpuType::Gameboy));
|
2020-05-18 16:10:53 -04:00
|
|
|
|
2020-05-21 20:57:00 -04:00
|
|
|
_eventManager.reset(new GbEventManager(debugger, _gameboy->GetCpu(), _gameboy->GetPpu()));
|
2020-05-18 16:10:53 -04:00
|
|
|
_callstackManager.reset(new CallstackManager(debugger));
|
2020-05-21 20:57:00 -04:00
|
|
|
_breakpointManager.reset(new BreakpointManager(debugger, CpuType::Gameboy, _eventManager.get()));
|
2020-05-18 16:10:53 -04:00
|
|
|
_step.reset(new StepRequest());
|
2020-06-06 22:27:54 -04:00
|
|
|
_assembler.reset(new GbAssembler(debugger->GetLabelManager()));
|
2020-06-06 23:12:03 -04:00
|
|
|
|
|
|
|
if(_gameboy->GetState().MemoryManager.ApuCycleCount == 0) {
|
|
|
|
//Enable breaking on uninit reads when debugger is opened at power on
|
|
|
|
_enableBreakOnUninitRead = true;
|
|
|
|
}
|
2020-06-06 22:27:54 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
GbDebugger::~GbDebugger()
|
|
|
|
{
|
2020-05-18 16:10:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void GbDebugger::Reset()
|
|
|
|
{
|
|
|
|
_callstackManager.reset(new CallstackManager(_debugger));
|
|
|
|
_prevOpCode = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void GbDebugger::ProcessRead(uint16_t addr, uint8_t value, MemoryOperationType type)
|
|
|
|
{
|
|
|
|
AddressInfo addressInfo = _gameboy->GetAbsoluteAddress(addr);
|
|
|
|
MemoryOperationInfo operation { addr, value, type };
|
|
|
|
BreakSource breakSource = BreakSource::Unspecified;
|
|
|
|
|
|
|
|
if(type == MemoryOperationType::ExecOpCode) {
|
|
|
|
GbCpuState gbState = _gameboy->GetState().Cpu;
|
|
|
|
|
|
|
|
if(_traceLogger->IsCpuLogged(CpuType::Gameboy) || _settings->CheckDebuggerFlag(DebuggerFlags::GbDebuggerEnabled)) {
|
|
|
|
if(addressInfo.Address >= 0) {
|
2020-06-06 16:13:02 -04:00
|
|
|
if(addressInfo.Type == SnesMemoryType::GbPrgRom) {
|
|
|
|
_codeDataLogger->SetFlags(addressInfo.Address, CdlFlags::Code);
|
|
|
|
}
|
2020-05-18 16:10:53 -04:00
|
|
|
_disassembler->BuildCache(addressInfo, 0, CpuType::Gameboy);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(_traceLogger->IsCpuLogged(CpuType::Gameboy)) {
|
|
|
|
DebugState debugState;
|
|
|
|
_debugger->GetState(debugState, true);
|
|
|
|
|
|
|
|
DisassemblyInfo disInfo = _disassembler->GetDisassemblyInfo(addressInfo, addr, 0, CpuType::Gameboy);
|
|
|
|
_traceLogger->Log(CpuType::Gameboy, debugState, disInfo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(GameboyDisUtils::IsJumpToSub(_prevOpCode) && gbState.PC != _prevProgramCounter + GameboyDisUtils::GetOpSize(_prevOpCode)) {
|
|
|
|
//CALL and RST, and PC doesn't match the next instruction, so the call was (probably) done
|
|
|
|
uint8_t opSize = DisassemblyInfo::GetOpSize(_prevOpCode, 0, CpuType::Gameboy);
|
|
|
|
uint16_t returnPc = _prevProgramCounter + opSize;
|
|
|
|
AddressInfo src = _gameboy->GetAbsoluteAddress(_prevProgramCounter);
|
|
|
|
AddressInfo ret = _gameboy->GetAbsoluteAddress(returnPc);
|
|
|
|
_callstackManager->Push(src, _prevProgramCounter, addressInfo, gbState.PC, ret, returnPc, StackFrameFlags::None);
|
|
|
|
} else if(GameboyDisUtils::IsReturnInstruction(_prevOpCode) && gbState.PC != _prevProgramCounter + GameboyDisUtils::GetOpSize(_prevOpCode)) {
|
|
|
|
//RET used, and PC doesn't match the next instruction, so the ret was (probably) taken
|
|
|
|
_callstackManager->Pop(addressInfo, gbState.PC);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(_step->BreakAddress == (int32_t)gbState.PC && GameboyDisUtils::IsReturnInstruction(_prevOpCode)) {
|
|
|
|
//RET/RETI found, if we're on the expected return address, break immediately (for step over/step out)
|
|
|
|
_step->StepCount = 0;
|
|
|
|
}
|
|
|
|
|
2020-06-07 16:34:23 -04:00
|
|
|
if(_settings->CheckDebuggerFlag(DebuggerFlags::GbDebuggerEnabled)) {
|
|
|
|
bool needBreak = false;
|
|
|
|
switch(value) {
|
|
|
|
case 0x40:
|
|
|
|
needBreak = _settings->CheckDebuggerFlag(DebuggerFlags::GbBreakOnNopLoad);
|
|
|
|
breakSource = BreakSource::GbNopLoad;
|
|
|
|
break;
|
|
|
|
|
2020-07-03 14:33:47 -04:00
|
|
|
case 0xD3: case 0xDB: case 0xDD: case 0xE3: case 0xE4: case 0xEB: case 0xEC: case 0xED: case 0xF4: case 0xFC: case 0xFD:
|
2020-06-07 16:34:23 -04:00
|
|
|
needBreak = _settings->CheckDebuggerFlag(DebuggerFlags::GbBreakOnInvalidOpCode);
|
|
|
|
breakSource = BreakSource::GbInvalidOpCode;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(needBreak) {
|
|
|
|
_step->StepCount = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-18 16:10:53 -04:00
|
|
|
_prevOpCode = value;
|
|
|
|
_prevProgramCounter = gbState.PC;
|
|
|
|
|
|
|
|
if(_step->StepCount > 0) {
|
|
|
|
_step->StepCount--;
|
|
|
|
}
|
|
|
|
|
2020-06-06 23:12:03 -04:00
|
|
|
_memoryAccessCounter->ProcessMemoryExec(addressInfo, _console->GetMasterClock());
|
2020-05-18 16:10:53 -04:00
|
|
|
} else if(type == MemoryOperationType::ExecOperand) {
|
2020-06-06 16:13:02 -04:00
|
|
|
if(addressInfo.Address >= 0 && addressInfo.Type == SnesMemoryType::GbPrgRom) {
|
|
|
|
_codeDataLogger->SetFlags(addressInfo.Address, CdlFlags::Code);
|
|
|
|
}
|
2020-06-06 23:12:03 -04:00
|
|
|
_memoryAccessCounter->ProcessMemoryExec(addressInfo, _console->GetMasterClock());
|
2020-05-18 16:10:53 -04:00
|
|
|
} else {
|
2020-06-06 16:13:02 -04:00
|
|
|
if(addressInfo.Address >= 0 && addressInfo.Type == SnesMemoryType::GbPrgRom) {
|
|
|
|
_codeDataLogger->SetFlags(addressInfo.Address, CdlFlags::Data);
|
|
|
|
}
|
|
|
|
|
2020-06-06 23:12:03 -04:00
|
|
|
if(addr < 0xFE00 || addr >= 0xFF80) {
|
|
|
|
if(_memoryAccessCounter->ProcessMemoryRead(addressInfo, _console->GetMasterClock())) {
|
|
|
|
//Memory access was a read on an uninitialized memory address
|
2020-06-23 18:34:03 -04:00
|
|
|
if(_enableBreakOnUninitRead) {
|
|
|
|
if(_memoryAccessCounter->GetReadCount(addressInfo) == 1) {
|
|
|
|
//Only warn the first time
|
|
|
|
_debugger->Log("[GB] Uninitialized memory read: $" + HexUtilities::ToHex(addr));
|
|
|
|
}
|
|
|
|
if(_settings->CheckDebuggerFlag(DebuggerFlags::GbDebuggerEnabled) && _settings->CheckDebuggerFlag(DebuggerFlags::BreakOnUninitRead)) {
|
|
|
|
breakSource = BreakSource::BreakOnUninitMemoryRead;
|
|
|
|
_step->StepCount = 0;
|
|
|
|
}
|
2020-06-06 23:12:03 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-21 20:57:00 -04:00
|
|
|
if(addr == 0xFFFF || (addr >= 0xFE00 && addr < 0xFF80) || (addr >= 0x8000 && addr <= 0x9FFF)) {
|
|
|
|
_eventManager->AddEvent(DebugEventType::Register, operation);
|
|
|
|
}
|
2020-05-18 16:10:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
_debugger->ProcessBreakConditions(_step->StepCount == 0, GetBreakpointManager(), operation, addressInfo, breakSource);
|
|
|
|
}
|
|
|
|
|
|
|
|
void GbDebugger::ProcessWrite(uint16_t addr, uint8_t value, MemoryOperationType type)
|
|
|
|
{
|
|
|
|
AddressInfo addressInfo = _gameboy->GetAbsoluteAddress(addr);
|
|
|
|
MemoryOperationInfo operation { addr, value, type };
|
|
|
|
_debugger->ProcessBreakConditions(false, GetBreakpointManager(), operation, addressInfo);
|
|
|
|
|
|
|
|
if(addressInfo.Type == SnesMemoryType::GbWorkRam || addressInfo.Type == SnesMemoryType::GbCartRam || addressInfo.Type == SnesMemoryType::GbHighRam) {
|
|
|
|
_disassembler->InvalidateCache(addressInfo, CpuType::Gameboy);
|
|
|
|
}
|
|
|
|
|
2020-05-21 20:57:00 -04:00
|
|
|
if(addr == 0xFFFF || (addr >= 0xFE00 && addr < 0xFF80) || (addr >= 0x8000 && addr <= 0x9FFF)) {
|
|
|
|
_eventManager->AddEvent(DebugEventType::Register, operation);
|
|
|
|
}
|
|
|
|
|
2020-06-06 23:12:03 -04:00
|
|
|
_memoryAccessCounter->ProcessMemoryWrite(addressInfo, _console->GetMasterClock());
|
2020-05-18 16:10:53 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void GbDebugger::Run()
|
|
|
|
{
|
|
|
|
_step.reset(new StepRequest());
|
|
|
|
}
|
|
|
|
|
|
|
|
void GbDebugger::Step(int32_t stepCount, StepType type)
|
|
|
|
{
|
|
|
|
StepRequest step;
|
|
|
|
|
2020-07-08 21:25:23 -04:00
|
|
|
GbCpuState gbState = _gameboy->GetState().Cpu;
|
|
|
|
if((type == StepType::StepOver || type == StepType::StepOut || type == StepType::Step) && gbState.Halted) {
|
|
|
|
//CPU isn't running - use the PPU to break execution instead
|
|
|
|
step.PpuStepCount = 1;
|
|
|
|
} else {
|
|
|
|
switch(type) {
|
|
|
|
case StepType::Step: step.StepCount = stepCount; break;
|
|
|
|
case StepType::StepOut: step.BreakAddress = _callstackManager->GetReturnAddress(); break;
|
|
|
|
case StepType::StepOver:
|
|
|
|
if(GameboyDisUtils::IsJumpToSub(_prevOpCode)) {
|
|
|
|
step.BreakAddress = _prevProgramCounter + DisassemblyInfo::GetOpSize(_prevOpCode, 0, CpuType::Gameboy);
|
|
|
|
} else {
|
|
|
|
//For any other instruction, step over is the same as step into
|
|
|
|
step.StepCount = 1;
|
|
|
|
}
|
|
|
|
break;
|
2020-05-18 16:10:53 -04:00
|
|
|
|
2020-07-08 21:25:23 -04:00
|
|
|
case StepType::PpuStep: step.PpuStepCount = stepCount; break;
|
|
|
|
case StepType::SpecificScanline: step.BreakScanline = stepCount; break;
|
|
|
|
}
|
2020-05-18 16:10:53 -04:00
|
|
|
}
|
|
|
|
_step.reset(new StepRequest(step));
|
|
|
|
}
|
|
|
|
|
2020-05-21 20:57:00 -04:00
|
|
|
void GbDebugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc)
|
|
|
|
{
|
|
|
|
AddressInfo src = _gameboy->GetAbsoluteAddress(_prevProgramCounter);
|
|
|
|
AddressInfo ret = _gameboy->GetAbsoluteAddress(originalPc);
|
|
|
|
AddressInfo dest = _gameboy->GetAbsoluteAddress(currentPc);
|
|
|
|
_callstackManager->Push(src, _prevProgramCounter, dest, currentPc, ret, originalPc, StackFrameFlags::Irq);
|
|
|
|
_eventManager->AddEvent(DebugEventType::Irq);
|
|
|
|
}
|
|
|
|
|
2020-06-18 00:58:22 -04:00
|
|
|
void GbDebugger::ProcessPpuCycle(uint16_t scanline, uint16_t cycle)
|
|
|
|
{
|
|
|
|
if(_step->PpuStepCount > 0) {
|
|
|
|
_step->PpuStepCount--;
|
|
|
|
if(_step->PpuStepCount == 0) {
|
|
|
|
_debugger->SleepUntilResume(BreakSource::PpuStep);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(cycle == 0 && scanline == _step->BreakScanline) {
|
|
|
|
_step->BreakScanline = -1;
|
|
|
|
_debugger->SleepUntilResume(BreakSource::PpuStep);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-21 20:57:00 -04:00
|
|
|
shared_ptr<GbEventManager> GbDebugger::GetEventManager()
|
|
|
|
{
|
|
|
|
return _eventManager;
|
|
|
|
}
|
|
|
|
|
2020-06-06 22:27:54 -04:00
|
|
|
shared_ptr<GbAssembler> GbDebugger::GetAssembler()
|
|
|
|
{
|
|
|
|
return _assembler;
|
|
|
|
}
|
|
|
|
|
2020-05-18 16:10:53 -04:00
|
|
|
shared_ptr<CallstackManager> GbDebugger::GetCallstackManager()
|
|
|
|
{
|
|
|
|
return _callstackManager;
|
|
|
|
}
|
|
|
|
|
2020-06-06 16:13:02 -04:00
|
|
|
shared_ptr<CodeDataLogger> GbDebugger::GetCodeDataLogger()
|
|
|
|
{
|
|
|
|
return _codeDataLogger;
|
|
|
|
}
|
|
|
|
|
2020-05-18 16:10:53 -04:00
|
|
|
BreakpointManager* GbDebugger::GetBreakpointManager()
|
|
|
|
{
|
|
|
|
return _breakpointManager.get();
|
|
|
|
}
|