#include "stdafx.h" #include "Debugger/Debugger.h" #include "Debugger/DebugTypes.h" #include "Debugger/DisassemblyInfo.h" #include "Debugger/TraceLogger.h" #include "Debugger/MemoryDumper.h" #include "Debugger/MemoryAccessCounter.h" #include "Debugger/CodeDataLogger.h" #include "Debugger/Disassembler.h" #include "Debugger/BreakpointManager.h" #include "Debugger/PpuTools.h" #include "Debugger/DebugBreakHelper.h" #include "Debugger/LabelManager.h" #include "Debugger/ScriptManager.h" #include "Debugger/CallstackManager.h" #include "Debugger/ExpressionEvaluator.h" #include "Debugger/IEventManager.h" #include "SNES/Cpu.h" #include "SNES/Ppu.h" #include "SNES/Spc.h" #include "SNES/Coprocessors/SA1/Sa1.h" #include "SNES/Coprocessors/GSU/Gsu.h" #include "SNES/Coprocessors/CX4/Cx4.h" #include "SNES/Coprocessors/DSP/NecDsp.h" #include "SNES/BaseCartridge.h" #include "SNES/MemoryManager.h" #include "SNES/MemoryMappings.h" #include "SNES/Debugger/CpuDebugger.h" #include "SNES/Debugger/SpcDebugger.h" #include "SNES/Debugger/GsuDebugger.h" #include "SNES/Debugger/NecDspDebugger.h" #include "SNES/Debugger/Cx4Debugger.h" #include "SNES/InternalRegisters.h" #include "SNES/AluMulDiv.h" #include "SNES/DmaController.h" #include "SNES/CpuTypes.h" #include "NES/Debugger/NesDebugger.h" #include "NES/NesTypes.h" #include "Gameboy/Debugger/GbDebugger.h" #include "Gameboy/Gameboy.h" #include "Gameboy/Gameboy.h" #include "Gameboy/GbPpu.h" #include "Gameboy/GameboyHeader.h" #include "Shared/EmuSettings.h" #include "Shared/Audio/SoundMixer.h" #include "Shared/NotificationManager.h" #include "Shared/BaseState.h" #include "Shared/Emulator.h" #include "Shared/Interfaces/IConsole.h" #include "Utilities/HexUtilities.h" #include "Utilities/FolderUtilities.h" #include "Utilities/Patches/IpsPatcher.h" #include "MemoryOperationType.h" #include "EventType.h" Debugger::Debugger(Emulator* emu, IConsole* console) { _executionStopped = true; _emu = emu; _console = console; _settings = _emu->GetSettings(); _consoleType = _emu->GetConsoleType(); _labelManager.reset(new LabelManager(this)); _memoryDumper.reset(new MemoryDumper(this)); _disassembler.reset(new Disassembler(console, this)); _traceLogger.reset(new TraceLogger(this)); _memoryAccessCounter.reset(new MemoryAccessCounter(this)); _ppuTools.reset(new PpuTools(_emu)); _scriptManager.reset(new ScriptManager(this)); vector cpuTypes = _emu->GetCpuTypes(); _mainCpuType = cpuTypes[0]; for(CpuType type : cpuTypes) { unique_ptr &debugger = _debuggers[(int)type].Debugger; switch(type) { case CpuType::Cpu: debugger.reset(new CpuDebugger(this, CpuType::Cpu)); break; case CpuType::Spc: debugger.reset(new SpcDebugger(this)); break; case CpuType::NecDsp: debugger.reset(new NecDspDebugger(this)); break; case CpuType::Sa1: debugger.reset(new CpuDebugger(this, CpuType::Sa1)); break; case CpuType::Gsu: debugger.reset(new GsuDebugger(this)); break; case CpuType::Cx4: debugger.reset(new Cx4Debugger(this)); break; case CpuType::Gameboy: debugger.reset(new GbDebugger(this)); break; case CpuType::Nes: debugger.reset(new NesDebugger(this)); break; default: throw std::runtime_error("Unsupported CPU type"); } _debuggers[(int)type].Evaluator.reset(new ExpressionEvaluator(this, type)); } _breakRequestCount = 0; _suspendRequestCount = 0; //TODO /*string cdlFile = FolderUtilities::CombinePath(FolderUtilities::GetDebuggerFolder(), FolderUtilities::GetFilename(_emu->GetRomInfo().RomFile.GetFileName(), false) + ".cdl"); GetCodeDataLogger(CpuType::Cpu)->LoadCdlFile(cdlFile, _settings->CheckDebuggerFlag(DebuggerFlags::AutoResetCdl), _emu->GetCrc32());*/ RefreshCodeCache(); if(_emu->IsPaused()) { Step(_mainCpuType, 1, StepType::Step); } _executionStopped = false; } Debugger::~Debugger() { Release(); } void Debugger::Release() { //TODO /*bool hasGameboy = _debuggers[(int)CpuType::Gameboy].Debugger != nullptr; CpuType cpuType = hasGameboy ? CpuType::Gameboy : CpuType::Cpu; string cdlFile = FolderUtilities::CombinePath(FolderUtilities::GetDebuggerFolder(), FolderUtilities::GetFilename(_emu->GetRomInfo().RomFile.GetFileName(), false) + ".cdl"); GetCodeDataLogger(cpuType)->SaveCdlFile(cdlFile, _emu->GetCrc32());*/ while(_executionStopped) { Run(); } } void Debugger::Reset() { _memoryAccessCounter->ResetCounts(); for(int i = 0; i <= (int)DebugUtilities::GetLastCpuType(); i++) { if(_debuggers[i].Debugger) { _debuggers[i].Debugger->Reset(); } } } template void Debugger::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType) { _debuggers[(int)type].Debugger->ProcessRead(addr, value, opType); if(_scriptManager->HasScript()) { _scriptManager->ProcessMemoryOperation(addr, value, opType, type); } } template void Debugger::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType) { _debuggers[(int)type].Debugger->ProcessWrite(addr, value, opType); if(_scriptManager->HasScript()) { _scriptManager->ProcessMemoryOperation(addr, value, opType, type); } } template void Debugger::ProcessPpuRead(uint16_t addr, uint8_t value, SnesMemoryType memoryType) { AddressInfo addressInfo { addr, memoryType }; MemoryOperationInfo operation { addr, value, MemoryOperationType::Read }; BreakpointManager* bpManager = _debuggers[(int)type].Debugger->GetBreakpointManager(); ProcessBreakConditions(false, bpManager, operation, addressInfo); _memoryAccessCounter->ProcessMemoryRead(addressInfo, _emu->GetMasterClock()); } template void Debugger::ProcessPpuWrite(uint16_t addr, uint8_t value, SnesMemoryType memoryType) { AddressInfo addressInfo { addr, memoryType }; MemoryOperationInfo operation { addr, value, MemoryOperationType::Write }; BreakpointManager* bpManager = _debuggers[(int)type].Debugger->GetBreakpointManager(); ProcessBreakConditions(false, bpManager, operation, addressInfo); _memoryAccessCounter->ProcessMemoryWrite(addressInfo, _emu->GetMasterClock()); } template void Debugger::ProcessPpuCycle() { uint16_t scanline = 0; uint16_t cycle = 0; switch(type) { case CpuType::Cpu: case CpuType::Gameboy: case CpuType::Nes: _debuggers[(int)type].Debugger->ProcessPpuCycle(scanline, cycle); break; } _ppuTools->UpdateViewers(scanline, cycle, type); if(_breakRequestCount > 0) { SleepUntilResume(BreakSource::Unspecified); } } void Debugger::SleepUntilResume(BreakSource source, MemoryOperationInfo *operation, int breakpointId) { if(_suspendRequestCount) { return; } _emu->GetSoundMixer()->StopAudio(); _executionStopped = true; if(source != BreakSource::Unspecified || _breakRequestCount == 0) { //Only trigger code break event if the pause was caused by user action BreakEvent evt = {}; evt.BreakpointId = breakpointId; evt.Source = source; if(operation) { evt.Operation = *operation; } _waitForBreakResume = true; _emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::CodeBreak, &evt); } while((_waitForBreakResume && !_suspendRequestCount) || _breakRequestCount) { std::this_thread::sleep_for(std::chrono::duration(10)); } _executionStopped = false; } void Debugger::ProcessBreakConditions(bool needBreak, BreakpointManager* bpManager, MemoryOperationInfo &operation, AddressInfo &addressInfo, BreakSource source) { if(needBreak || _breakRequestCount || _waitForBreakResume) { SleepUntilResume(source); } else { int breakpointId = bpManager->CheckBreakpoint(operation, addressInfo); if(breakpointId >= 0) { SleepUntilResume(BreakSource::Breakpoint, &operation, breakpointId); } } } template void Debugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi) { _debuggers[(int)type].Debugger->ProcessInterrupt(originalPc, currentPc, forNmi); ProcessEvent(forNmi ? EventType::Nmi : EventType::Irq); } void Debugger::ProcessEvent(EventType type) { _scriptManager->ProcessEvent(type); switch(type) { default: break; case EventType::StartFrame: { _emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::EventViewerRefresh, (void*)_mainCpuType); shared_ptr evtMgr = GetEventManager(_mainCpuType); if(evtMgr) { evtMgr->ClearFrameEvents(); } break; } case EventType::GbStartFrame: if(_consoleType == ConsoleType::Gameboy || _consoleType == ConsoleType::GameboyColor) { _scriptManager->ProcessEvent(EventType::StartFrame); } _emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::EventViewerRefresh, (void*)CpuType::Gameboy); GetEventManager(CpuType::Gameboy)->ClearFrameEvents(); break; case EventType::GbEndFrame: if(_consoleType == ConsoleType::Gameboy || _consoleType == ConsoleType::GameboyColor) { _scriptManager->ProcessEvent(EventType::EndFrame); } break; case EventType::Reset: Reset(); break; case EventType::StateLoaded: _memoryAccessCounter->ResetCounts(); break; } } int32_t Debugger::EvaluateExpression(string expression, CpuType cpuType, EvalResultType &resultType, bool useCache) { MemoryOperationInfo operationInfo { 0, 0, MemoryOperationType::Read }; BaseState& state = _debuggers[(int)cpuType].Debugger->GetState(); if(useCache) { return _debuggers[(int)cpuType].Evaluator->Evaluate(expression, state, resultType, operationInfo); } else { ExpressionEvaluator expEval(this, cpuType); return expEval.Evaluate(expression, state, resultType, operationInfo); } } void Debugger::Run() { for(int i = 0; i <= (int)DebugUtilities::GetLastCpuType(); i++) { if(_debuggers[i].Debugger) { _debuggers[i].Debugger->Run(); } } _waitForBreakResume = false; } void Debugger::Step(CpuType cpuType, int32_t stepCount, StepType type) { DebugBreakHelper helper(this); IDebugger* debugger = _debuggers[(int)cpuType].Debugger.get(); if(debugger) { debugger->Step(stepCount, type); } for(int i = 0; i <= (int)DebugUtilities::GetLastCpuType(); i++) { if(_debuggers[i].Debugger && _debuggers[i].Debugger.get() != debugger) { _debuggers[i].Debugger->Run(); } } _waitForBreakResume = false; } bool Debugger::IsExecutionStopped() { return _executionStopped || _emu->IsThreadPaused(); } bool Debugger::HasBreakRequest() { return _breakRequestCount > 0; } void Debugger::BreakRequest(bool release) { if(release) { _breakRequestCount--; } else { _breakRequestCount++; } } void Debugger::SuspendDebugger(bool release) { if(release) { if(_suspendRequestCount > 0) { _suspendRequestCount--; } else { #ifdef _DEBUG //throw std::runtime_error("unexpected debugger suspend::release call"); #endif } } else { _suspendRequestCount++; } } void Debugger::BreakImmediately(BreakSource source) { bool gbDebugger = _settings->CheckDebuggerFlag(DebuggerFlags::GbDebuggerEnabled); if(source == BreakSource::GbDisableLcdOutsideVblank && (!gbDebugger || !_settings->CheckDebuggerFlag(DebuggerFlags::GbBreakOnDisableLcdOutsideVblank))) { return; } else if(source == BreakSource::GbInvalidVramAccess && (!gbDebugger || !_settings->CheckDebuggerFlag(DebuggerFlags::GbBreakOnInvalidVramAccess))) { return; } else if(source == BreakSource::GbInvalidOamAccess && (!gbDebugger || !_settings->CheckDebuggerFlag(DebuggerFlags::GbBreakOnInvalidOamAccess))) { return; } SleepUntilResume(source); } void Debugger::GetState(BaseState &dstState, CpuType cpuType) { BaseState& srcState = GetStateRef(cpuType); switch(cpuType) { case CpuType::Cpu: memcpy(&dstState, &srcState, sizeof(CpuState)); break; case CpuType::Spc: memcpy(&dstState, &srcState, sizeof(SpcState)); break; case CpuType::NecDsp: memcpy(&dstState, &srcState, sizeof(NecDspState)); break; case CpuType::Sa1: memcpy(&dstState, &srcState, sizeof(CpuState)); break; case CpuType::Gsu: memcpy(&dstState, &srcState, sizeof(GsuState)); break; case CpuType::Cx4: memcpy(&dstState, &srcState, sizeof(Cx4State)); break; case CpuType::Gameboy: memcpy(&dstState, &srcState, sizeof(GbCpuState)); break; case CpuType::Nes: memcpy(&dstState, &srcState, sizeof(NesCpuState)); break; } } BaseState& Debugger::GetStateRef(CpuType cpuType) { return _debuggers[(int)cpuType].Debugger->GetState(); } AddressInfo Debugger::GetAbsoluteAddress(AddressInfo relAddress) { return _console->GetAbsoluteAddress(relAddress); } AddressInfo Debugger::GetRelativeAddress(AddressInfo absAddress, CpuType cpuType) { return _console->GetRelativeAddress(absAddress, cpuType); } void Debugger::SetCdlData(CpuType cpuType, uint8_t *cdlData, uint32_t length) { DebugBreakHelper helper(this); GetCodeDataLogger(cpuType)->SetCdlData(cdlData, length); RefreshCodeCache(); } void Debugger::MarkBytesAs(CpuType cpuType, uint32_t start, uint32_t end, uint8_t flags) { DebugBreakHelper helper(this); GetCodeDataLogger(cpuType)->MarkBytesAs(start, end, flags); RefreshCodeCache(); } void Debugger::RefreshCodeCache() { _disassembler->ResetPrgCache(); vector cpuTypes = _emu->GetCpuTypes(); for(CpuType type : _emu->GetCpuTypes()) { RebuildPrgCache(type); } } void Debugger::RebuildPrgCache(CpuType cpuType) { shared_ptr cdl = GetCodeDataLogger(cpuType); if(!cdl) { return; } uint32_t prgRomSize = cdl->GetPrgSize(); AddressInfo addrInfo; addrInfo.Type = cpuType == CpuType::Gameboy ? SnesMemoryType::GbPrgRom : SnesMemoryType::PrgRom; for(uint32_t i = 0; i < prgRomSize; i++) { if(cdl->IsCode(i)) { addrInfo.Address = (int32_t)i; i += _disassembler->BuildCache(addrInfo, cdl->GetCpuFlags(i), cdl->GetCpuType(i)) - 1; } } } void Debugger::GetCdlData(uint32_t offset, uint32_t length, SnesMemoryType memoryType, uint8_t* cdlData) { CpuType cpuType = DebugUtilities::ToCpuType(memoryType); shared_ptr cdl = GetCodeDataLogger(cpuType); if(memoryType == SnesMemoryType::PrgRom || memoryType == SnesMemoryType::GbPrgRom) { cdl->GetCdlData(offset, length, cdlData); } else { bool hasGameboy = _debuggers[(int)CpuType::Gameboy].Debugger != nullptr; SnesMemoryType prgType = hasGameboy ? SnesMemoryType::GbPrgRom : SnesMemoryType::PrgRom; AddressInfo relAddress; relAddress.Type = memoryType; for(uint32_t i = 0; i < length; i++) { relAddress.Address = offset + i; AddressInfo info = GetAbsoluteAddress(relAddress); cdlData[i] = info.Type == prgType ? cdl->GetFlags(info.Address) : 0; } } } void Debugger::SetBreakpoints(Breakpoint breakpoints[], uint32_t length) { for(int i = 0; i <= (int)DebugUtilities::GetLastCpuType(); i++) { if(_debuggers[i].Debugger) { _debuggers[i].Debugger->GetBreakpointManager()->SetBreakpoints(breakpoints, length); } } } void Debugger::Log(string message) { auto lock = _logLock.AcquireSafe(); if(_debuggerLog.size() >= 1000) { _debuggerLog.pop_front(); } _debuggerLog.push_back(message); } string Debugger::GetLog() { auto lock = _logLock.AcquireSafe(); stringstream ss; for(string& msg : _debuggerLog) { ss << msg << "\n"; } return ss.str(); } void Debugger::SaveRomToDisk(string filename, bool saveAsIps, CdlStripOption stripOption) { //TODO /*vector output; RomInfo romInfo = _emu->GetRomInfo(); Gameboy* gb = _cart->GetGameboy(); vector rom; if(gb) { uint8_t* prgRom = _memoryDumper->GetMemoryBuffer(SnesMemoryType::GbPrgRom); uint32_t prgRomSize = _memoryDumper->GetMemorySize(SnesMemoryType::GbPrgRom); rom = vector(prgRom, prgRom+prgRomSize); } else { rom = vector(_cart->DebugGetPrgRom(), _cart->DebugGetPrgRom() + _cart->DebugGetPrgRomSize()); } if(saveAsIps) { output = IpsPatcher::CreatePatch(_cart->GetOriginalPrgRom(), rom); } else { if(stripOption != CdlStripOption::StripNone) { GetCodeDataLogger(gb ? CpuType::Gameboy : CpuType::Cpu)->StripData(rom.data(), stripOption); //Preserve rom header regardless of CDL file contents if(gb) { GameboyHeader header = gb->GetHeader(); memcpy(rom.data() + romInfo.HeaderOffset, &header, sizeof(GameboyHeader)); } else { memcpy(rom.data() + romInfo.HeaderOffset, &romInfo.Header, sizeof(SnesCartInformation)); } } output = rom; } ofstream file(filename, ios::out | ios::binary); if(file) { file.write((char*)output.data(), output.size()); file.close(); }*/ } shared_ptr Debugger::GetTraceLogger() { return _traceLogger; } MemoryDumper* Debugger::GetMemoryDumper() { return _memoryDumper.get(); } shared_ptr Debugger::GetMemoryAccessCounter() { return _memoryAccessCounter; } shared_ptr Debugger::GetCodeDataLogger(CpuType cpuType) { if(_debuggers[(int)cpuType].Debugger) { return _debuggers[(int)cpuType].Debugger->GetCodeDataLogger(); } throw std::runtime_error("GetCodeDataLogger() - Unsupported CPU type"); } shared_ptr Debugger::GetDisassembler() { return _disassembler; } shared_ptr Debugger::GetPpuTools() { return _ppuTools; } shared_ptr Debugger::GetEventManager(CpuType cpuType) { if(_debuggers[(int)cpuType].Debugger) { return _debuggers[(int)cpuType].Debugger->GetEventManager(); } return nullptr; } shared_ptr Debugger::GetLabelManager() { return _labelManager; } shared_ptr Debugger::GetScriptManager() { return _scriptManager; } shared_ptr Debugger::GetCallstackManager(CpuType cpuType) { if(_debuggers[(int)cpuType].Debugger) { return _debuggers[(int)cpuType].Debugger->GetCallstackManager(); } throw std::runtime_error("GetCallstackManager() - Unsupported CPU type"); } IConsole* Debugger::GetConsole() { return _console; } Emulator* Debugger::GetEmulator() { return _emu; } shared_ptr Debugger::GetAssembler(CpuType cpuType) { if(_debuggers[(int)cpuType].Debugger) { return _debuggers[(int)cpuType].Debugger->GetAssembler(); } throw std::runtime_error("GetAssembler() - Unsupported CPU type"); } template void Debugger::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryRead(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessMemoryWrite(uint32_t addr, uint8_t value, MemoryOperationType opType); template void Debugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi); template void Debugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi); template void Debugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi); template void Debugger::ProcessInterrupt(uint32_t originalPc, uint32_t currentPc, bool forNmi); template void Debugger::ProcessPpuRead(uint16_t addr, uint8_t value, SnesMemoryType memoryType); template void Debugger::ProcessPpuRead(uint16_t addr, uint8_t value, SnesMemoryType memoryType); template void Debugger::ProcessPpuRead(uint16_t addr, uint8_t value, SnesMemoryType memoryType); template void Debugger::ProcessPpuWrite(uint16_t addr, uint8_t value, SnesMemoryType memoryType); template void Debugger::ProcessPpuWrite(uint16_t addr, uint8_t value, SnesMemoryType memoryType); template void Debugger::ProcessPpuWrite(uint16_t addr, uint8_t value, SnesMemoryType memoryType); template void Debugger::ProcessPpuCycle(); template void Debugger::ProcessPpuCycle(); template void Debugger::ProcessPpuCycle();