Files
bsnes/target-loki/debugger/debugger.cpp
Tim Allen 3016e595f0 Update to v094r06 release.
byuu says:

New terminal is in. Much nicer to use now. Command history makes a major
difference in usability.

The SMP is now fully traceable and debuggable. Basically they act as
separate entities, you can trace both at the same time, but for the most
part running and stepping is performed on the chip you select.

I'm going to put off CPU+SMP interleave support for a while. I don't
actually think it'll be too hard. Will get trickier if/when we support
coprocessor debugging.

Remaining tasks:
- aliases
- hotkeys
- save states
- window geometry

Basically, the debugger's done. Just have to add the UI fluff.

I also removed tracing/memory export from higan. It was always meant to
be temporary until the debugger was remade.
2014-02-09 17:05:58 +11:00

458 lines
14 KiB
C++

#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");
}