mirror of https://github.com/bsnes-emu/bsnes.git
Update to v094r05 release.
byuu says: Commands can be prefixed with: (cpu|smp|ppu|dsp|apu|vram|oam|cgram)/ to set their source. Eg "vram/hex 0800" or "smp/breakpoints.append execute ffc0"; default is cpu. These overlap a little bit in odd ways, but that's just the way the SNES works: it's not a very orthogonal system. CPU is both a processor and the main bus (ROM, RAM, WRAM, etc), APU is the shared memory by the SMP+DSP (eg use it to catch writes from either chip); PPU probably won't ever be used since it's broken down into three separate buses (VRAM, OAM, CGRAM), but DSP could be useful for tracking bugs like we found in Koushien 2 with the DSP echo buffer corrupting SMP opcodes. Technically the PPU memory pools are only ever tripped by the CPU poking at them, as the PPU doesn't ever write. I now have run.for, run.to, step.for, step.to. The difference is that run only prints the next instruction after running, whereas step prints all of the instructions along the way as well. run.to acts the same as "step over" here. Although it's not quite as nice, since you have to specify the address of the next instruction. Logging the Field/Vcounter/Hcounter on instruction listings now, good for timing information. Added in the tracer mask, as well as memory export, as well as VRAM/OAM/CGRAM/SMP read/write/execute breakpoints, as well as an APU usage map (it tracks DSP reads/writes separately, although I don't currently have debugger callbacks on DSP accesses just yet.) Have not hooked up actual SMP debugging just yet, but I plan to soon. Still thinking about how I want to allow / block interleaving of instructions (terminal output and tracing.) So ... remaining tasks at this point: - full SMP debugging - CPU+SMP interleave support - aliases - hotkeys - save states (will be kind of tricky ... will have to suppress breakpoints during synchronization, or abort a save in a break event.) - keep track of window geometry between runs
This commit is contained in:
parent
10e2a6d497
commit
423a6c6bf8
|
@ -3,7 +3,7 @@
|
|||
|
||||
namespace Emulator {
|
||||
static const char Name[] = "higan";
|
||||
static const char Version[] = "094.04";
|
||||
static const char Version[] = "094.05";
|
||||
static const char Author[] = "byuu";
|
||||
static const char License[] = "GPLv3";
|
||||
static const char Website[] = "http://byuu.org/";
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
#ifndef NALL_BITVECTOR_HPP
|
||||
#define NALL_BITVECTOR_HPP
|
||||
|
||||
namespace nall {
|
||||
|
||||
struct bitvector {
|
||||
protected:
|
||||
uint8_t* pool = nullptr;
|
||||
unsigned bits = 0;
|
||||
|
||||
public:
|
||||
bitvector() {}
|
||||
bitvector(unsigned size) { resize(size); }
|
||||
bitvector(const bitvector& source) { operator=(source); }
|
||||
bitvector(bitvector&& source) { operator=(std::move(source)); }
|
||||
~bitvector() { reset(); }
|
||||
|
||||
bitvector& operator=(const bitvector& source) {
|
||||
bits = source.bits;
|
||||
pool = (uint8_t*)realloc(pool, bytes());
|
||||
memcpy(pool, source.pool, bytes());
|
||||
return *this;
|
||||
}
|
||||
|
||||
bitvector& operator=(bitvector&& source) {
|
||||
pool = source.pool;
|
||||
bits = source.bits;
|
||||
source.pool = nullptr;
|
||||
source.bits = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
explicit operator bool() const { return bits > 0; }
|
||||
bool empty() const { return bits == 0; }
|
||||
unsigned size() const { return bits; }
|
||||
unsigned bytes() const { return (bits + 7) / 8; }
|
||||
uint8_t* data() { return pool; }
|
||||
const uint8_t* data() const { return pool; }
|
||||
|
||||
void reset() {
|
||||
if(pool) free(pool);
|
||||
pool = nullptr;
|
||||
bits = 0;
|
||||
}
|
||||
|
||||
void resize(unsigned size) {
|
||||
unsigned from = bits;
|
||||
bits = size;
|
||||
for(unsigned n = size; n < from; n++) clear(n); //on reduce
|
||||
pool = (uint8_t*)realloc(pool, bytes());
|
||||
for(unsigned n = from; n < size; n++) clear(n); //on expand
|
||||
}
|
||||
|
||||
bool get(unsigned position) const {
|
||||
return pool[position >> 3] & (0x80 >> (position & 7));
|
||||
}
|
||||
|
||||
void clear() {
|
||||
memset(pool, 0x00, bytes());
|
||||
}
|
||||
|
||||
void set() {
|
||||
memset(pool, 0xff, bytes());
|
||||
for(unsigned n = bits; n < bytes() * 8; n++) clear(n);
|
||||
}
|
||||
|
||||
void clear(unsigned position) {
|
||||
pool[position >> 3] &= ~(0x80 >> (position & 7));
|
||||
}
|
||||
|
||||
void set(unsigned position) {
|
||||
pool[position >> 3] |= (0x80 >> (position & 7));
|
||||
}
|
||||
|
||||
void invert(unsigned position) {
|
||||
get(position) ? clear(position) : set(position);
|
||||
}
|
||||
|
||||
void set(unsigned position, bool value) {
|
||||
value ? set(position) : clear(position);
|
||||
}
|
||||
|
||||
struct reference {
|
||||
reference(bitvector& self, unsigned position) : self(self), position(position) {}
|
||||
operator bool() const { return self.get(position); }
|
||||
void operator=(bool value) { self.set(position, value); }
|
||||
|
||||
protected:
|
||||
bitvector& self;
|
||||
unsigned position;
|
||||
};
|
||||
|
||||
reference operator[](unsigned position) {
|
||||
return reference(*this, position);
|
||||
}
|
||||
|
||||
bool operator[](unsigned position) const {
|
||||
return get(position);
|
||||
}
|
||||
|
||||
struct iterator {
|
||||
iterator(bitvector& self, unsigned position) : self(self), position(position) {}
|
||||
bool operator!=(const iterator& source) const { return position != source.position; }
|
||||
iterator& operator++() { position++; return *this; }
|
||||
reference operator*() { return self.operator[](position); }
|
||||
|
||||
protected:
|
||||
bitvector& self;
|
||||
unsigned position;
|
||||
};
|
||||
|
||||
iterator begin() { return iterator(*this, 0); }
|
||||
iterator end() { return iterator(*this, bits); }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -11,6 +11,7 @@
|
|||
#include <nall/atoi.hpp>
|
||||
#include <nall/base64.hpp>
|
||||
#include <nall/bit.hpp>
|
||||
#include <nall/bitvector.hpp>
|
||||
#include <nall/bmp.hpp>
|
||||
#include <nall/config.hpp>
|
||||
#include <nall/crc16.hpp>
|
||||
|
|
|
@ -31,45 +31,42 @@ uint16 PPU::get_vram_address() {
|
|||
}
|
||||
|
||||
uint8 PPU::vram_read(unsigned addr) {
|
||||
debugger.vram_read(addr);
|
||||
|
||||
uint8 data = 0x00;
|
||||
if(regs.display_disable || vcounter() >= (!regs.overscan ? 225 : 240)) {
|
||||
return vram[addr];
|
||||
data = vram[addr];
|
||||
debugger.vram_read(addr, data);
|
||||
}
|
||||
return 0x00;
|
||||
return data;
|
||||
}
|
||||
|
||||
void PPU::vram_write(unsigned addr, uint8 data) {
|
||||
debugger.vram_write(addr, data);
|
||||
|
||||
if(regs.display_disable || vcounter() >= (!regs.overscan ? 225 : 240)) {
|
||||
vram[addr] = data;
|
||||
debugger.vram_write(addr, data);
|
||||
}
|
||||
}
|
||||
|
||||
uint8 PPU::oam_read(unsigned addr) {
|
||||
debugger.oam_read(addr);
|
||||
|
||||
return oam[addr];
|
||||
uint8 data = oam[addr];
|
||||
debugger.oam_read(addr, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
void PPU::oam_write(unsigned addr, uint8 data) {
|
||||
debugger.oam_write(addr, data);
|
||||
|
||||
oam[addr] = data;
|
||||
sprite.update(addr, data);
|
||||
debugger.oam_write(addr, data);
|
||||
}
|
||||
|
||||
uint8 PPU::cgram_read(unsigned addr) {
|
||||
debugger.cgram_read(addr);
|
||||
|
||||
return cgram[addr];
|
||||
uint8 data = cgram[addr];
|
||||
debugger.cgram_read(addr, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
void PPU::cgram_write(unsigned addr, uint8 data) {
|
||||
debugger.cgram_write(addr, data);
|
||||
|
||||
cgram[addr] = data;
|
||||
debugger.cgram_write(addr, data);
|
||||
}
|
||||
|
||||
void PPU::mmio_update_video_mode() {
|
||||
|
|
|
@ -60,9 +60,9 @@ privileged:
|
|||
friend class Video;
|
||||
|
||||
struct Debugger {
|
||||
hook<void (uint16)> vram_read;
|
||||
hook<void (uint16)> oam_read;
|
||||
hook<void (uint16)> cgram_read;
|
||||
hook<void (uint16, uint8)> vram_read;
|
||||
hook<void (uint16, uint8)> oam_read;
|
||||
hook<void (uint16, uint8)> cgram_read;
|
||||
hook<void (uint16, uint8)> vram_write;
|
||||
hook<void (uint16, uint8)> oam_write;
|
||||
hook<void (uint16, uint8)> cgram_write;
|
||||
|
|
|
@ -181,21 +181,19 @@ void SMP::op_io() {
|
|||
}
|
||||
|
||||
uint8 SMP::op_read(uint16 addr) {
|
||||
debugger.op_read(addr);
|
||||
|
||||
add_clocks(12);
|
||||
uint8 r = op_busread(addr);
|
||||
uint8 data = op_busread(addr);
|
||||
add_clocks(12);
|
||||
cycle_edge();
|
||||
return r;
|
||||
debugger.op_read(addr, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
void SMP::op_write(uint16 addr, uint8 data) {
|
||||
debugger.op_write(addr, data);
|
||||
|
||||
add_clocks(24);
|
||||
op_buswrite(addr, data);
|
||||
cycle_edge();
|
||||
debugger.op_write(addr, data);
|
||||
}
|
||||
|
||||
uint8 SMP::disassembler_read(uint16 addr) {
|
||||
|
|
|
@ -50,7 +50,7 @@ privileged:
|
|||
|
||||
struct Debugger {
|
||||
hook<void (uint16)> op_exec;
|
||||
hook<void (uint16)> op_read;
|
||||
hook<void (uint16, uint8)> op_read;
|
||||
hook<void (uint16, uint8)> op_write;
|
||||
} debugger;
|
||||
|
||||
|
|
|
@ -6,15 +6,31 @@ Debugger::Debugger() {
|
|||
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() {
|
||||
usageCPU = new uint8_t[0x1000000]();
|
||||
directory::create({interface->pathname, "loki/"});
|
||||
|
||||
usageCPU = new uint8[0x1000000]();
|
||||
usageAPU = new uint8[0x10000]();
|
||||
file fp;
|
||||
|
||||
if(fp.open({interface->pathname, "loki/usage.cpu.bin"}, file::mode::read)) {
|
||||
if(fp.size() == 0x1000000) {
|
||||
fp.read(usageCPU, 0x1000000);
|
||||
}
|
||||
if(fp.size() == 0x1000000) fp.read(usageCPU, 0x1000000);
|
||||
fp.close();
|
||||
}
|
||||
|
||||
if(fp.open({interface->pathname, "loki/usage.apu.bin"}, file::mode::read)) {
|
||||
if(fp.size() == 0x10000) fp.read(usageAPU, 0x10000);
|
||||
fp.close();
|
||||
}
|
||||
}
|
||||
|
@ -27,42 +43,68 @@ void Debugger::unload() {
|
|||
fp.write(usageCPU, 0x1000000);
|
||||
fp.close();
|
||||
}
|
||||
if(fp.open({interface->pathname, "loki/usage.apu.bin"}, file::mode::write)) {
|
||||
fp.write(usageAPU, 0x10000);
|
||||
fp.close();
|
||||
}
|
||||
delete[] usageCPU;
|
||||
delete[] usageAPU;
|
||||
usageCPU = nullptr;
|
||||
usageAPU = nullptr;
|
||||
}
|
||||
|
||||
void Debugger::main() {
|
||||
if(running == false) {
|
||||
usleep(20 * 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
emulator->run();
|
||||
}
|
||||
|
||||
void Debugger::run() {
|
||||
switch(mode) {
|
||||
case Mode::Break:
|
||||
usleep(20 * 1000);
|
||||
break;
|
||||
|
||||
case Mode::Run:
|
||||
emulator->run();
|
||||
break;
|
||||
|
||||
case Mode::Step:
|
||||
emulator->run();
|
||||
break;
|
||||
}
|
||||
running = true;
|
||||
}
|
||||
|
||||
void Debugger::stop() {
|
||||
mode = Mode::Break;
|
||||
runFor = false;
|
||||
runTo = false;
|
||||
running = false;
|
||||
cpuRunFor.reset();
|
||||
cpuRunTo.reset();
|
||||
cpuStepFor.reset();
|
||||
cpuStepTo.reset();
|
||||
}
|
||||
|
||||
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;
|
||||
echo("Breakpoint #", n, " hit");
|
||||
if(bp.mode == Breakpoint::Mode::Read ) echo("; read ", hex<2>(data));
|
||||
if(bp.mode == Breakpoint::Mode::Write) echo("; wrote ", hex<2>(data));
|
||||
echo("; triggered: ", ++bp.triggered, "\n");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
string Debugger::cpuDisassemble() {
|
||||
char text[4096];
|
||||
SFC::cpu.disassemble_opcode(text);
|
||||
return text;
|
||||
return {text, " I:", (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;
|
||||
return {text, " I:", (unsigned)SFC::cpu.field(), " V:", format<3>(SFC::cpu.vcounter()), " H:", format<4>(SFC::cpu.hcounter())};
|
||||
}
|
||||
|
||||
void Debugger::cpuExec(uint24 addr) {
|
||||
|
@ -75,88 +117,60 @@ void Debugger::cpuExec(uint24 addr) {
|
|||
if(SFC::cpu.regs.p.x == 1) usageCPU[addr] |= Usage::FlagX;
|
||||
|
||||
if(tracerFile.open()) {
|
||||
tracerFile.print(cpuDisassemble(), "\n");
|
||||
if(!tracerMask || tracerMask[addr] == false) {
|
||||
if(tracerMask) tracerMask[addr] = true;
|
||||
tracerFile.print(cpuDisassemble(), "\n");
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned n = 0; n < breakpoints.size(); n++) {
|
||||
auto& bp = breakpoints[n];
|
||||
if(bp.mode != Breakpoint::Mode::Execute) continue;
|
||||
if(bp.addr != addr) continue;
|
||||
echo("Breakpoint #", n, " triggered (", ++bp.triggered, " time(s))\n");
|
||||
if(breakpointTest(Source::CPU, Breakpoint::Mode::Execute, addr)) {
|
||||
echo(cpuDisassemble(), "\n");
|
||||
stop();
|
||||
SFC::scheduler.debug();
|
||||
return leave();
|
||||
}
|
||||
|
||||
if(mode == Mode::Run) {
|
||||
if(runFor) {
|
||||
if(--runFor() == 0) {
|
||||
echo(cpuDisassemble(), "\n");
|
||||
stop();
|
||||
SFC::scheduler.debug();
|
||||
return;
|
||||
}
|
||||
if(cpuRunFor) {
|
||||
if(--cpuRunFor() == 0) {
|
||||
echo(cpuDisassemble(), "\n");
|
||||
return leave();
|
||||
}
|
||||
|
||||
if(runTo) {
|
||||
if(addr == runTo()) {
|
||||
echo(cpuDisassemble(), "\n");
|
||||
stop();
|
||||
SFC::scheduler.debug();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(mode == Mode::Step) {
|
||||
if(cpuRunTo) {
|
||||
if(addr == cpuRunTo()) {
|
||||
echo(cpuDisassemble(), "\n");
|
||||
return leave();
|
||||
}
|
||||
}
|
||||
|
||||
if(cpuStepFor) {
|
||||
echo(cpuDisassemble(), "\n");
|
||||
if(--stepDuration == 0) {
|
||||
stop();
|
||||
SFC::scheduler.debug();
|
||||
}
|
||||
return;
|
||||
if(--cpuStepFor() == 0) return leave();
|
||||
}
|
||||
|
||||
if(cpuStepTo) {
|
||||
echo(cpuDisassemble(), "\n");
|
||||
if(addr == cpuStepTo()) return leave();
|
||||
}
|
||||
}
|
||||
|
||||
void Debugger::cpuRead(uint24 addr, uint8 data) {
|
||||
usageCPU[addr] |= Usage::Read;
|
||||
|
||||
for(unsigned n = 0; n < breakpoints.size(); n++) {
|
||||
auto& bp = breakpoints[n];
|
||||
if(bp.mode != Breakpoint::Mode::Read) continue;
|
||||
if(bp.addr != addr) continue;
|
||||
if(bp.data && bp.data() != data) continue;
|
||||
echo("Breakpoint #", n, " triggered (", ++bp.triggered, " time(s))\n");
|
||||
echo("Read ", hex<2>(data), "\n");
|
||||
stop();
|
||||
SFC::scheduler.debug();
|
||||
}
|
||||
if(breakpointTest(Source::CPU, Breakpoint::Mode::Read, addr, data)) leave();
|
||||
}
|
||||
|
||||
void Debugger::cpuWrite(uint24 addr, uint8 data) {
|
||||
usageCPU[addr] |= Usage::Write;
|
||||
|
||||
for(unsigned n = 0; n < breakpoints.size(); n++) {
|
||||
auto& bp = breakpoints[n];
|
||||
if(bp.mode != Breakpoint::Mode::Write) continue;
|
||||
if(bp.addr != addr) continue;
|
||||
if(bp.data && bp.data() != data) continue;
|
||||
echo("Breakpoint #", n, " triggered (", ++bp.triggered, " time(s))\n");
|
||||
echo("Wrote ", hex<2>(data), "\n");
|
||||
stop();
|
||||
SFC::scheduler.debug();
|
||||
}
|
||||
if(breakpointTest(Source::CPU, Breakpoint::Mode::Write, addr, data)) leave();
|
||||
}
|
||||
|
||||
void Debugger::echoBreakpoints() {
|
||||
if(breakpoints.size() == 0) return;
|
||||
echo("# type addr data triggered\n");
|
||||
echo("--- -------- ------ ---- ---------\n");
|
||||
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 ");
|
||||
|
@ -169,13 +183,11 @@ void Debugger::echoBreakpoints() {
|
|||
}
|
||||
|
||||
void Debugger::echoDisassemble(unsigned addr, signed size) {
|
||||
if(!(usageCPU[addr] & Usage::Execute)) {
|
||||
echo("No usage data available for this address\n");
|
||||
return;
|
||||
}
|
||||
if(!(usageCPU[addr] & Usage::Execute)) return echo("No usage data available for cpu/", hex<6>(addr), "\n");
|
||||
|
||||
while(size > 0) {
|
||||
string text = cpuDisassemble(addr, usageCPU[addr] & Usage::FlagE, usageCPU[addr] & Usage::FlagM, usageCPU[addr] & Usage::FlagX);
|
||||
text.resize(20); //remove register information
|
||||
echo(text, "\n");
|
||||
if(--size <= 0) break;
|
||||
|
||||
|
@ -192,31 +204,156 @@ void Debugger::echoDisassemble(unsigned addr, signed size) {
|
|||
}
|
||||
}
|
||||
|
||||
void Debugger::echoHex(unsigned addr, signed size) {
|
||||
void Debugger::echoHex(Source source, unsigned addr, signed size) {
|
||||
while(size > 0) {
|
||||
string hexdata, asciidata;
|
||||
for(unsigned n = 0; n < 16; n++) {
|
||||
unsigned offset = addr;
|
||||
if((offset & 0x40e000) == 0x002000 || (offset & 0x40e000) == 0x004000) {
|
||||
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_t byte = SFC::bus.read(addr + n);
|
||||
uint8 byte = memoryRead(source, addr + n);
|
||||
hexdata.append(hex<2>(byte), " ");
|
||||
asciidata.append(byte >= 0x20 && byte <= 0x7e ? (char)byte : '.');
|
||||
}
|
||||
}
|
||||
echo(hex<6>(addr), " [ ", hexdata, "] ", asciidata, "\n");
|
||||
echo(hex<6>(addr % memorySize(source)), " [ ", hexdata, "] ", asciidata, "\n");
|
||||
addr += 16, size -= 16;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Debugger::read(unsigned addr) {
|
||||
return SFC::bus.read(addr);
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
void Debugger::write(unsigned addr, uint8_t data) {
|
||||
return SFC::bus.write(addr, data);
|
||||
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::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::VRAM: return 0x10000;
|
||||
case Source::OAM: return 544;
|
||||
case Source::CGRAM: return 512;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
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::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();
|
||||
}
|
||||
|
||||
void Debugger::smpExec(uint16 addr) {
|
||||
usageAPU[addr] |= Usage::Execute;
|
||||
|
||||
if(breakpointTest(Source::SMP, Breakpoint::Mode::Execute, addr)) {
|
||||
leave();
|
||||
}
|
||||
}
|
||||
|
||||
void Debugger::smpRead(uint16 addr, uint8 data) {
|
||||
usageAPU[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) {
|
||||
usageAPU[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::VRAM: return "vram";
|
||||
case Source::OAM: return "oam";
|
||||
case Source::CGRAM: return "cgram";
|
||||
}
|
||||
return "none";
|
||||
}
|
||||
|
|
|
@ -1,12 +1,40 @@
|
|||
struct Debugger {
|
||||
enum class Source : unsigned { CPU, SMP, PPU, DSP, APU, VRAM, OAM, CGRAM };
|
||||
|
||||
struct Breakpoint {
|
||||
Source source = Source::CPU;
|
||||
enum class Mode : unsigned { Disabled, Read, Write, Execute } mode = Mode::Disabled;
|
||||
unsigned addr = 0;
|
||||
optional<uint8> data = false;
|
||||
unsigned triggered = 0; //counter for number of times breakpoint was hit
|
||||
};
|
||||
|
||||
struct Usage {
|
||||
enum : unsigned {
|
||||
Read = 0x01,
|
||||
Write = 0x02,
|
||||
Execute = 0x04,
|
||||
//CPU
|
||||
FlagE = 0x08,
|
||||
FlagM = 0x10,
|
||||
FlagX = 0x20,
|
||||
//APU
|
||||
DspRead = 0x08,
|
||||
DspWrite = 0x10,
|
||||
};
|
||||
};
|
||||
|
||||
Debugger();
|
||||
|
||||
void load();
|
||||
void unload();
|
||||
void main();
|
||||
|
||||
void run();
|
||||
void stop();
|
||||
void leave();
|
||||
|
||||
bool breakpointTest(Source source, Breakpoint::Mode mode, unsigned addr, uint8 data = 0x00);
|
||||
string cpuDisassemble();
|
||||
string cpuDisassemble(unsigned addr, bool e, bool m, bool x);
|
||||
void cpuExec(uint24 addr);
|
||||
|
@ -14,37 +42,33 @@ struct Debugger {
|
|||
void cpuWrite(uint24 addr, uint8 data);
|
||||
void echoBreakpoints();
|
||||
void echoDisassemble(unsigned addr, signed size);
|
||||
void echoHex(unsigned addr, signed size);
|
||||
uint8_t read(unsigned addr);
|
||||
void write(unsigned addr, uint8_t data);
|
||||
void echoHex(Source source, unsigned addr, signed size);
|
||||
void memoryExport(Source source, string filename);
|
||||
uint8 memoryRead(Source source, unsigned addr);
|
||||
unsigned memorySize(Source source);
|
||||
void memoryWrite(Source source, unsigned addr, uint8 data);
|
||||
void ppuCgramRead(uint16 addr, uint8 data);
|
||||
void ppuCgramWrite(uint16 addr, uint8 data);
|
||||
void ppuOamRead(uint16 addr, uint8 data);
|
||||
void ppuOamWrite(uint16 addr, uint8 data);
|
||||
void ppuVramRead(uint16 addr, uint8 data);
|
||||
void ppuVramWrite(uint16 addr, uint8 data);
|
||||
void smpExec(uint16 addr);
|
||||
void smpRead(uint16 addr, uint8 data);
|
||||
void smpWrite(uint16 addr, uint8 data);
|
||||
string sourceName(Source source);
|
||||
|
||||
enum class Mode : unsigned { Break, Run, Step } mode = Mode::Break;
|
||||
|
||||
struct Breakpoint {
|
||||
enum class Mode : unsigned { Disabled, Read, Write, Execute } mode = Mode::Disabled;
|
||||
unsigned addr = 0;
|
||||
optional<uint8_t> data = false;
|
||||
unsigned triggered = 0; //counter for number of times breakpoint was hit
|
||||
};
|
||||
|
||||
struct Usage {
|
||||
enum : unsigned {
|
||||
Read = 0x01,
|
||||
Write = 0x02,
|
||||
Execute = 0x04,
|
||||
FlagE = 0x08,
|
||||
FlagM = 0x10,
|
||||
FlagX = 0x20,
|
||||
};
|
||||
};
|
||||
bool running = false;
|
||||
|
||||
vector<Breakpoint> breakpoints;
|
||||
optional<unsigned> runFor = false;
|
||||
optional<unsigned> runTo = false;
|
||||
unsigned stepDuration = 0;
|
||||
optional<unsigned> cpuRunFor = false;
|
||||
optional<unsigned> cpuRunTo = false;
|
||||
optional<unsigned> cpuStepFor = false;
|
||||
optional<unsigned> cpuStepTo = false;
|
||||
file tracerFile;
|
||||
bool tracerMask = false;
|
||||
uint8_t* usageCPU = nullptr;
|
||||
bitvector tracerMask;
|
||||
uint8* usageCPU = nullptr;
|
||||
uint8* usageAPU = nullptr;
|
||||
};
|
||||
|
||||
extern Debugger* debugger;
|
||||
|
|
|
@ -15,7 +15,7 @@ string Program::path(string name) {
|
|||
}
|
||||
|
||||
void Program::main() {
|
||||
debugger->run();
|
||||
debugger->main();
|
||||
}
|
||||
|
||||
Program::Program(string pathname) {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace SFC = SuperFamicom;
|
||||
|
||||
#include <nall/platform.hpp>
|
||||
#include <nall/bitvector.hpp>
|
||||
#include <nall/config.hpp>
|
||||
#include <nall/directory.hpp>
|
||||
#include <nall/dsp.hpp>
|
||||
|
|
|
@ -18,7 +18,20 @@ Terminal::Terminal() {
|
|||
}
|
||||
|
||||
void Terminal::command(string t) {
|
||||
lstring args = t.qsplit(" ");
|
||||
auto source = Debugger::Source::CPU;
|
||||
if(t.beginsWith("cpu/" )) { source = Debugger::Source::CPU; t.ltrim<1>("cpu/" ); }
|
||||
if(t.beginsWith("smp/" )) { source = Debugger::Source::SMP; t.ltrim<1>("smp/" ); }
|
||||
if(t.beginsWith("ppu/" )) { source = Debugger::Source::PPU; t.ltrim<1>("ppu/" ); }
|
||||
if(t.beginsWith("dsp/" )) { source = Debugger::Source::DSP; t.ltrim<1>("dsp/" ); }
|
||||
if(t.beginsWith("apu/" )) { source = Debugger::Source::APU; t.ltrim<1>("apu/" ); }
|
||||
if(t.beginsWith("vram/" )) { source = Debugger::Source::VRAM; t.ltrim<1>("vram/" ); }
|
||||
if(t.beginsWith("oam/" )) { source = Debugger::Source::OAM; t.ltrim<1>("oam/" ); }
|
||||
if(t.beginsWith("cgram/")) { source = Debugger::Source::CGRAM; t.ltrim<1>("cgram/"); }
|
||||
|
||||
if(source == Debugger::Source::CPU) t.replace("$", hex(SFC::cpu.regs.pc));
|
||||
if(source == Debugger::Source::SMP) t.replace("$", hex(SFC::smp.regs.pc));
|
||||
|
||||
lstring args = t.strip().qsplit(" ").strip();
|
||||
string s = args.takeFirst();
|
||||
unsigned argc = args.size();
|
||||
|
||||
|
@ -38,6 +51,7 @@ void Terminal::command(string t) {
|
|||
|
||||
if(s == "breakpoints.append" && argc >= 2 && argc <= 3) {
|
||||
Debugger::Breakpoint bp;
|
||||
bp.source = source;
|
||||
if(args[0] == "read" ) bp.mode = Debugger::Breakpoint::Mode::Read;
|
||||
if(args[0] == "write" ) bp.mode = Debugger::Breakpoint::Mode::Write;
|
||||
if(args[0] == "execute") bp.mode = Debugger::Breakpoint::Mode::Execute;
|
||||
|
@ -72,8 +86,16 @@ void Terminal::command(string t) {
|
|||
return;
|
||||
}
|
||||
|
||||
if(s == "export" && argc <= 1) {
|
||||
string filename = {debugger->sourceName(source), "-", string::datetime().transform(" :", "--"), ".bin"};
|
||||
if(argc >= 1) filename = args[0];
|
||||
string pathname = {interface->pathname, "loki/", filename};
|
||||
debugger->memoryExport(source, pathname);
|
||||
return;
|
||||
}
|
||||
|
||||
if(s == "hex" && argc >= 1 && argc <= 2) {
|
||||
debugger->echoHex(hex(args[0]), argc == 2 ? decimal(args[1]) : 256);
|
||||
debugger->echoHex(source, hex(args[0]), argc == 2 ? decimal(args[1]) : 256);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -84,35 +106,43 @@ void Terminal::command(string t) {
|
|||
|
||||
if(s == "read" && argc == 1) {
|
||||
unsigned addr = hex(args[0]);
|
||||
uint8_t data = debugger->read(addr);
|
||||
echo(hex<6>(addr), " = ", hex<2>(data), "\n");
|
||||
uint8_t data = debugger->memoryRead(source, addr);
|
||||
echo(debugger->sourceName(source), "/", hex<6>(addr), " = ", hex<2>(data), "\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if(s == "run") {
|
||||
debugger->mode = Debugger::Mode::Run;
|
||||
if(s == "run" && argc == 0) {
|
||||
debugger->run();
|
||||
return;
|
||||
}
|
||||
|
||||
if(s == "run.for" && argc == 1) {
|
||||
debugger->mode = Debugger::Mode::Run;
|
||||
debugger->runFor = {true, (unsigned)decimal(args[0])};
|
||||
debugger->run();
|
||||
debugger->cpuRunFor = {true, (unsigned)decimal(args[0])};
|
||||
return;
|
||||
}
|
||||
|
||||
if(s == "run.to" && argc == 1) {
|
||||
debugger->mode == Debugger::Mode::Run;
|
||||
debugger->runTo = {true, (unsigned)hex(args[0])};
|
||||
debugger->run();
|
||||
debugger->cpuRunTo = {true, (unsigned)hex(args[0])};
|
||||
return;
|
||||
}
|
||||
|
||||
if(s == "step" && argc <= 1) {
|
||||
if(debugger->mode != Debugger::Mode::Break) {
|
||||
echo("Error: must break before stepping\n");
|
||||
return;
|
||||
}
|
||||
debugger->mode = Debugger::Mode::Step;
|
||||
debugger->stepDuration = (argc == 1 ? decimal(args[0]) : 1);
|
||||
if(s == "step" && argc == 0) {
|
||||
debugger->run();
|
||||
debugger->cpuStepFor = {true, 1u};
|
||||
return;
|
||||
}
|
||||
|
||||
if(s == "step.for" && argc == 1) {
|
||||
debugger->run();
|
||||
debugger->cpuStepFor = {true, (unsigned)decimal(args[0])};
|
||||
return;
|
||||
}
|
||||
|
||||
if(s == "step.to" && argc == 1) {
|
||||
debugger->run();
|
||||
debugger->cpuStepTo = {true, (unsigned)hex(args[0])};
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -120,9 +150,7 @@ void Terminal::command(string t) {
|
|||
if(debugger->tracerFile.open() == false) {
|
||||
string filename = {"trace-", string::datetime().transform(" :", "--"), ".log"};
|
||||
if(argc >= 1) filename = args[0];
|
||||
string pathname = {interface->pathname, "loki/"};
|
||||
directory::create(pathname);
|
||||
pathname.append(filename);
|
||||
string pathname = {interface->pathname, "loki/", filename};
|
||||
if(debugger->tracerFile.open(pathname, file::mode::write)) {
|
||||
echo("Tracer enabled\n");
|
||||
}
|
||||
|
@ -139,7 +167,14 @@ void Terminal::command(string t) {
|
|||
}
|
||||
|
||||
if(s == "tracer.mask" && argc == 1) {
|
||||
debugger->tracerMask = args[0] != "false";
|
||||
if(args[0] != "false") {
|
||||
debugger->tracerMask.resize(0x1000000);
|
||||
debugger->tracerMask.clear();
|
||||
echo("Tracer mask enabled\n");
|
||||
} else {
|
||||
debugger->tracerMask.reset();
|
||||
echo("Tracer mask disabled\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -150,8 +185,9 @@ void Terminal::command(string t) {
|
|||
|
||||
if(s == "write" && argc == 2) {
|
||||
unsigned addr = hex(args[0]);
|
||||
uint8_t data = hex(args[1]);
|
||||
debugger->write(addr, data);
|
||||
uint8 data = hex(args[1]);
|
||||
debugger->memoryWrite(source, addr, data);
|
||||
echo(debugger->sourceName(source), "/", hex<6>(addr), " = ", hex<2>(data), "\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue