#include "../loki.hpp" Debugger* debugger = nullptr; Debugger::Debugger() { debugger = this; SFC::cpu.debugger.op_exec = {&Debugger::cpuExec, this}; SFC::cpu.debugger.op_read = {&Debugger::cpuRead, this}; SFC::cpu.debugger.op_write = {&Debugger::cpuWrite, this}; SFC::smp.debugger.op_exec = {&Debugger::smpExec, this}; SFC::smp.debugger.op_read = {&Debugger::smpRead, this}; SFC::smp.debugger.op_write = {&Debugger::smpWrite, this}; SFC::ppu.debugger.vram_read = {&Debugger::ppuVramRead, this}; SFC::ppu.debugger.vram_write = {&Debugger::ppuVramWrite, this}; SFC::ppu.debugger.oam_read = {&Debugger::ppuOamRead, this}; SFC::ppu.debugger.oam_write = {&Debugger::ppuOamWrite, this}; SFC::ppu.debugger.cgram_read = {&Debugger::ppuCgramRead, this}; SFC::ppu.debugger.cgram_write = {&Debugger::ppuCgramWrite, this}; } void Debugger::load() { directory::create({interface->pathname, "loki/"}); cpuUsage = new uint8[0x1000000](); apuUsage = new uint8[0x10000](); file fp; if(fp.open({interface->pathname, "loki/cpu.usage.map"}, file::mode::read)) { if(fp.size() == 0x1000000) fp.read(cpuUsage, 0x1000000); fp.close(); } if(fp.open({interface->pathname, "loki/apu.usage.map"}, file::mode::read)) { if(fp.size() == 0x10000) fp.read(apuUsage, 0x10000); fp.close(); } } void Debugger::unload() { if(cpuTracerFile.open()) cpuTracerFile.close(); if(smpTracerFile.open()) smpTracerFile.close(); file::write({interface->pathname, "loki/cpu.usage.map"}, cpuUsage, 0x1000000); file::write({interface->pathname, "loki/apu.usage.map"}, apuUsage, 0x10000); delete[] cpuUsage; delete[] apuUsage; cpuUsage = nullptr; apuUsage = nullptr; } void Debugger::main() { if(running == false) { usleep(20 * 1000); return; } emulator->run(); } void Debugger::run() { running = true; } void Debugger::stop() { running = false; cpuRunFor = nothing; cpuRunTo = nothing; cpuStepFor = nothing; cpuStepTo = nothing; smpRunFor = nothing; smpRunTo = nothing; smpStepFor = nothing; smpStepTo = nothing; } void Debugger::leave() { stop(); SFC::scheduler.debug(); } bool Debugger::breakpointTest(Source source, Breakpoint::Mode mode, unsigned addr, uint8 data) { for(unsigned n = 0; n < breakpoints.size(); n++) { auto& bp = breakpoints[n]; if(bp.source != source) continue; if(bp.mode != mode) continue; if(bp.addr != addr) continue; if(bp.mode != Breakpoint::Mode::Execute && bp.data && bp.data() != data) continue; string output = {"Breakpoint #", n, " hit"}; if(bp.mode == Breakpoint::Mode::Read ) output.append("; read ", hex<2>(data)); if(bp.mode == Breakpoint::Mode::Write) output.append("; wrote ", hex<2>(data)); output.append("; triggered: ", ++bp.triggered); echo(output, "\n"); return true; } return false; } string Debugger::cpuDisassemble() { char text[4096]; SFC::cpu.disassemble_opcode(text); return {text, " F:", (unsigned)SFC::cpu.field(), " V:", format<3>(SFC::cpu.vcounter()), " H:", format<4>(SFC::cpu.hcounter())}; } string Debugger::cpuDisassemble(unsigned addr, bool e, bool m, bool x) { char text[4096]; SFC::cpu.disassemble_opcode(text, addr, e, m, x); return {text, " F:", (unsigned)SFC::cpu.field(), " V:", format<3>(SFC::cpu.vcounter()), " H:", format<4>(SFC::cpu.hcounter())}; } void Debugger::cpuExec(uint24 addr) { cpuUsage[addr] |= Usage::Execute; if(SFC::cpu.regs.e == 0) cpuUsage[addr] &= ~Usage::FlagE; if(SFC::cpu.regs.p.m == 0) cpuUsage[addr] &= ~Usage::FlagM; if(SFC::cpu.regs.p.x == 0) cpuUsage[addr] &= ~Usage::FlagX; if(SFC::cpu.regs.e == 1) cpuUsage[addr] |= Usage::FlagE; if(SFC::cpu.regs.p.m == 1) cpuUsage[addr] |= Usage::FlagM; if(SFC::cpu.regs.p.x == 1) cpuUsage[addr] |= Usage::FlagX; cpuInstructionCounter++; if(cpuTracerFile.open()) { if(!cpuTracerMask || cpuTracerMask[addr] == false) { if(cpuTracerMask) cpuTracerMask[addr] = true; cpuTracerFile.print(cpuDisassemble(), "\n"); } } if(breakpointTest(Source::CPU, Breakpoint::Mode::Execute, addr)) { echo(cpuDisassemble(), "\n"); return leave(); } if(cpuRunFor) { if(--cpuRunFor() == 0) { echo(cpuDisassemble(), "\n"); return leave(); } } if(cpuRunTo) { if(addr == cpuRunTo()) { echo(cpuDisassemble(), "\n"); return leave(); } } if(cpuStepFor) { echo(cpuDisassemble(), "\n"); if(--cpuStepFor() == 0) return leave(); } if(cpuStepTo) { echo(cpuDisassemble(), "\n"); if(addr == cpuStepTo()) return leave(); } } void Debugger::cpuRead(uint24 addr, uint8 data) { cpuUsage[addr] |= Usage::Read; if(breakpointTest(Source::CPU, Breakpoint::Mode::Read, addr, data)) leave(); } void Debugger::cpuWrite(uint24 addr, uint8 data) { cpuUsage[addr] |= Usage::Write; if(breakpointTest(Source::CPU, Breakpoint::Mode::Write, addr, data)) leave(); } void Debugger::echoBreakpoints() { if(breakpoints.size() == 0) return; echo("# source type addr data triggered\n"); echo("--- ------ -------- ------ ---- ---------\n"); for(unsigned n = 0; n < breakpoints.size(); n++) { auto& bp = breakpoints[n]; string output = {format<-3>(n), " "}; output.append(format<-6>(sourceName(bp.source)), " "); if(bp.mode == Breakpoint::Mode::Disabled) output.append("disabled "); if(bp.mode == Breakpoint::Mode::Read ) output.append("read "); if(bp.mode == Breakpoint::Mode::Write ) output.append("write "); if(bp.mode == Breakpoint::Mode::Execute ) output.append("execute "); output.append(hex<6>(bp.addr), " "); output.append(bp.data ? hex<2>(bp.data()) : " ", " "); output.append(format<-9>(bp.triggered)); echo(output, "\n"); } } void Debugger::echoDisassemble(Source source, unsigned addr, signed size) { if(source != Source::CPU && source != Source::SMP) return; const unsigned maximumDisplacement = (source == Source::CPU ? 5 : 4); //maximum opcode length uint8* usage = (source == Source::CPU ? cpuUsage : apuUsage); if(!(usage[addr] & Usage::Execute)) return echo("No usage data available for ", sourceName(source), "/", hex<6>(addr), "\n"); while(size > 0) { string text; if(source == Source::CPU) { text = cpuDisassemble(addr, usage[addr] & Usage::FlagE, usage[addr] & Usage::FlagM, usage[addr] & Usage::FlagX); } if(source == Source::SMP) { text = smpDisassemble(addr, usage[addr] & Usage::FlagP); } text.resize(20); //remove register information echo(text, "\n"); if(--size <= 0) break; unsigned displacement = 1; while(displacement < maximumDisplacement) { //maximum opcode length is four bytes if(usage[addr + displacement] & Usage::Execute) break; displacement++; } if(displacement >= maximumDisplacement) { echo("...\n"); return; } addr += displacement; } } void Debugger::echoHex(Source source, unsigned addr, signed size) { if(memorySize(source) == 0) return; //not a valid memory pool while(size > 0) { string hexdata, asciidata; for(unsigned n = 0; n < 16; n++) { unsigned offset = addr; if(source == Source::CPU && ((offset & 0x40e000) == 0x002000 || (offset & 0x40e000) == 0x004000)) { //$00-3f,80-bf:2000-5fff //reading MMIO registers can negatively impact emulation, so disallow these reads hexdata.append("?? "); asciidata.append("?"); } else { uint8 byte = memoryRead(source, addr + n); hexdata.append(hex<2>(byte), " "); asciidata.append(byte >= 0x20 && byte <= 0x7e ? (char)byte : '.'); } } echo(hex<6>(addr % memorySize(source)), " [ ", hexdata, "] ", asciidata, "\n"); addr += 16, size -= 16; } } void Debugger::memoryExport(Source source, string filename) { file fp; if(fp.open(filename, file::mode::write)) { unsigned size = memorySize(source); for(unsigned addr = 0; addr < size; addr++) { fp.write(memoryRead(source, addr)); } echo("Exported memory to ", notdir(filename), "\n"); } } uint8 Debugger::memoryRead(Source source, unsigned addr) { if(source == Source::CPU) { return SFC::bus.read(addr & 0xffffff); } if(source == Source::APU) { return SFC::smp.apuram[addr & 0xffff]; } if(source == Source::WRAM) { return SFC::cpu.wram[addr & 0x1ffff]; } if(source == Source::VRAM) { return SFC::ppu.vram[addr & 0xffff]; } if(source == Source::OAM) { return SFC::ppu.oam[addr % 544]; } if(source == Source::CGRAM) { return SFC::ppu.cgram[addr & 511]; } return 0x00; } unsigned Debugger::memorySize(Source source) { switch(source) { case Source::CPU: return 0x1000000; case Source::APU: return 0x10000; case Source::WRAM: return 0x20000; case Source::VRAM: return 0x10000; case Source::OAM: return 544; case Source::CGRAM: return 512; } return 0; } void Debugger::memoryWrite(Source source, unsigned addr, uint8 data) { if(source == Source::CPU) { SFC::bus.write(addr & 0xffffff, data); return; } if(source == Source::APU) { SFC::smp.apuram[addr & 0xffff] = data; return; } if(source == Source::WRAM) { SFC::cpu.wram[addr & 0x1ffff] = data; return; } if(source == Source::VRAM) { SFC::ppu.vram[addr & 0xffff] = data; return; } if(source == Source::OAM) { SFC::ppu.oam[addr % 544] = data; SFC::ppu.sprite.update(addr % 544, data); return; } if(source == Source::CGRAM) { if(addr & 1) data &= 0x7f; SFC::ppu.cgram[addr] = data; return; } } void Debugger::ppuCgramRead(uint16 addr, uint8 data) { if(breakpointTest(Source::CGRAM, Breakpoint::Mode::Read, addr, data)) leave(); } void Debugger::ppuCgramWrite(uint16 addr, uint8 data) { if(breakpointTest(Source::CGRAM, Breakpoint::Mode::Write, addr, data)) leave(); } void Debugger::ppuOamRead(uint16 addr, uint8 data) { if(breakpointTest(Source::OAM, Breakpoint::Mode::Read, addr, data)) leave(); } void Debugger::ppuOamWrite(uint16 addr, uint8 data) { if(breakpointTest(Source::OAM, Breakpoint::Mode::Write, addr, data)) leave(); } void Debugger::ppuVramRead(uint16 addr, uint8 data) { if(breakpointTest(Source::VRAM, Breakpoint::Mode::Read, addr, data)) leave(); } void Debugger::ppuVramWrite(uint16 addr, uint8 data) { if(breakpointTest(Source::VRAM, Breakpoint::Mode::Write, addr, data)) leave(); } string Debugger::smpDisassemble() { return SFC::smp.disassemble_opcode(SFC::smp.regs.pc, SFC::smp.regs.p.p); } string Debugger::smpDisassemble(uint16 addr, bool p) { return SFC::smp.disassemble_opcode(addr, p); } void Debugger::smpExec(uint16 addr) { apuUsage[addr] |= Usage::Execute; if(SFC::smp.regs.p.p == 0) apuUsage[addr] &= ~Usage::FlagP; if(SFC::smp.regs.p.p == 1) apuUsage[addr] |= Usage::FlagP; smpInstructionCounter++; if(smpTracerFile.open()) { if(!smpTracerMask || smpTracerMask[addr] == false) { if(smpTracerMask) smpTracerMask[addr] = true; smpTracerFile.print(smpDisassemble(), "\n"); } } if(breakpointTest(Source::SMP, Breakpoint::Mode::Execute, addr)) { echo(smpDisassemble(), "\n"); return leave(); } if(smpRunFor) { if(--smpRunFor() == 0) { echo(smpDisassemble(), "\n"); return leave(); } } if(smpRunTo) { if(addr == smpRunTo()) { echo(smpDisassemble(), "\n"); return leave(); } } if(smpStepFor) { echo(smpDisassemble(), "\n"); if(--smpStepFor() == 0) return leave(); } if(smpStepTo) { echo(smpDisassemble(), "\n"); if(addr == smpStepTo()) return leave(); } } void Debugger::smpRead(uint16 addr, uint8 data) { apuUsage[addr] |= Usage::Read; if(breakpointTest(Source::SMP, Breakpoint::Mode::Read, addr, data)) leave(); if(breakpointTest(Source::APU, Breakpoint::Mode::Read, addr, data)) leave(); } void Debugger::smpWrite(uint16 addr, uint8 data) { apuUsage[addr] |= Usage::Write; if(breakpointTest(Source::SMP, Breakpoint::Mode::Write, addr, data)) leave(); if(breakpointTest(Source::APU, Breakpoint::Mode::Write, addr, data)) leave(); } string Debugger::sourceName(Source source) { switch(source) { case Source::CPU: return "cpu"; case Source::SMP: return "smp"; case Source::PPU: return "ppu"; case Source::DSP: return "dsp"; case Source::APU: return "apu"; case Source::WRAM: return "wram"; case Source::VRAM: return "vram"; case Source::OAM: return "oam"; case Source::CGRAM: return "cgram"; } return "none"; } void Debugger::tracerDisable(Source source) { if(source != Source::CPU && source != Source::SMP) return; file& tracerFile = (source == Source::CPU ? cpuTracerFile : smpTracerFile); if(tracerFile.open() == false) return; tracerFile.close(); echo(sourceName(source).upper(), " tracer disabled\n"); } void Debugger::tracerEnable(Source source, string filename) { if(source != Source::CPU && source != Source::SMP) return; file& tracerFile = (source == Source::CPU ? cpuTracerFile : smpTracerFile); if(tracerFile.open() == true) return; if(tracerFile.open(filename, file::mode::write)) { echo(sourceName(source).upper(), " tracer enabled\n"); } } void Debugger::tracerMaskDisable(Source source) { if(source != Source::CPU && source != Source::SMP) return; bitvector& tracerMask = (source == Source::CPU ? cpuTracerMask : smpTracerMask); tracerMask.reset(); echo(sourceName(source).upper(), " tracer mask disabled\n"); } void Debugger::tracerMaskEnable(Source source) { if(source != Source::CPU && source != Source::SMP) return; bitvector& tracerMask = (source == Source::CPU ? cpuTracerMask : smpTracerMask); unsigned size = (source == Source::CPU ? 0x1000000 : 0x10000); tracerMask.resize(size); tracerMask.clear(); echo(sourceName(source).upper(), " tracer mask enabled\n"); }