Improve the debugger
- Add tracing of opcodes and potentially more stuff - Add option to run a script file at the beginnig (I use it to redirect traces to a file) - Support breakpoints again Former-commit-id: 4e988d6bc1a59456c96547f0320a6d9abedcae00
This commit is contained in:
parent
7e98af80c2
commit
b288625b9a
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -190,6 +190,15 @@ dependencies = [
|
|||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctrlc"
|
||||
version = "3.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "2.0.2"
|
||||
|
@ -658,6 +667,7 @@ dependencies = [
|
|||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"colored 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"enum-primitive-derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hexdump 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"minifb 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -970,6 +980,7 @@ dependencies = [
|
|||
"checksum colored 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6cdb90b60f2927f8d76139c72dbde7e10c3a2bc47c8594c9c7a66529f2687c03"
|
||||
"checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e"
|
||||
"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
|
||||
"checksum ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7dfd2d8b4c82121dfdff120f818e09fc4380b0b7e17a742081a89b94853e87f"
|
||||
"checksum dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"
|
||||
"checksum dirs-sys 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afa0b23de8fd801745c471deffa6e12d248f962c9fd4b4c33787b055599bde7b"
|
||||
"checksum enum-primitive-derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b90e520ec62c1864c8c78d637acbfe8baf5f63240f2fb8165b8325c07812dd"
|
||||
|
|
|
@ -27,4 +27,12 @@ args:
|
|||
required: false
|
||||
- debug:
|
||||
long: debug
|
||||
help: Start with the debugger attached
|
||||
help: Start with the debugger attached
|
||||
- script_file:
|
||||
long: script-file
|
||||
short: f
|
||||
takes_value: true
|
||||
help: Text file with debugger commands to run
|
||||
required: false
|
||||
requires:
|
||||
debug
|
|
@ -69,7 +69,7 @@ fn run_emulator(matches: &ArgMatches) -> GBAResult<()> {
|
|||
gba.cpu.set_verbose(true);
|
||||
let mut debugger = Debugger::new(gba);
|
||||
println!("starting debugger...");
|
||||
debugger.repl()?;
|
||||
debugger.repl(matches.value_of("script_file"))?;
|
||||
println!("ending debugger...");
|
||||
} else {
|
||||
let frame_time = time::Duration::new(0, 1_000_000_000u32 / 60);
|
||||
|
|
|
@ -381,6 +381,22 @@ impl Core {
|
|||
}
|
||||
}
|
||||
|
||||
fn trace_opcode(&self, insn: u32) {
|
||||
if self.trace_opcodes && self.pipeline_state == PipelineState::Execute {
|
||||
print!("[{:08X}] PC=0x{:08x} | ", insn, self.pc);
|
||||
for r in 0..15 {
|
||||
print!("R{}=0x{:08x} ", r, self.gpr[r]);
|
||||
}
|
||||
print!(
|
||||
" N={} Z={} C={} V={} T={}\n",
|
||||
self.cpsr.N() as u8,
|
||||
self.cpsr.Z() as u8,
|
||||
self.cpsr.C() as u8,
|
||||
self.cpsr.V() as u8,
|
||||
self.cpsr.state() as u8,
|
||||
);
|
||||
}
|
||||
}
|
||||
/// Perform a pipeline step
|
||||
/// If an instruction was executed in this step, return it.
|
||||
pub fn step(&mut self, bus: &mut SysBus) -> CpuResult<()> {
|
||||
|
|
|
@ -40,10 +40,31 @@ impl GameBoyAdvance {
|
|||
}
|
||||
}
|
||||
|
||||
fn update_key_state(&mut self) {
|
||||
pub fn update_key_state(&mut self) {
|
||||
self.sysbus.io.keyinput = self.backend.get_key_state();
|
||||
}
|
||||
|
||||
pub fn add_breakpoint(&mut self, addr: u32) -> Option<usize> {
|
||||
if !self.cpu.breakpoints.contains(&addr) {
|
||||
let new_index = self.cpu.breakpoints.len();
|
||||
self.cpu.breakpoints.push(addr);
|
||||
Some(new_index)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_breakpoint(&self) -> Option<u32> {
|
||||
let next_pc = self.cpu.get_next_pc();
|
||||
for bp in &self.cpu.breakpoints {
|
||||
if *bp == next_pc {
|
||||
return Some(next_pc);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn step_new(&mut self) {
|
||||
let mut irqs = IrqBitmask(0);
|
||||
let previous_cycles = self.cpu.cycles;
|
||||
|
|
|
@ -31,10 +31,19 @@ pub enum MemWriteCommandSize {
|
|||
Word,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
pub struct TraceFlags: u32 {
|
||||
const TRACE_SYSBUS = 0b00000001;
|
||||
const TRACE_OPCODE = 0b00000010;
|
||||
const TRACE_DMA = 0b00000100;
|
||||
const TRACE_TIMERS = 0b000001000;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Command {
|
||||
Info,
|
||||
DisplayInfo,
|
||||
GpuInfo,
|
||||
Step(usize),
|
||||
Continue,
|
||||
Frame(usize),
|
||||
|
@ -49,25 +58,31 @@ pub enum Command {
|
|||
ListBreakpoints,
|
||||
Reset,
|
||||
Quit,
|
||||
TraceToggle(TraceFlags),
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub fn run(&self, debugger: &mut Debugger) {
|
||||
impl Debugger {
|
||||
pub fn run_command(&mut self, command: Command) {
|
||||
use Command::*;
|
||||
match *self {
|
||||
Info => println!("{}", debugger.gba.cpu),
|
||||
DisplayInfo => { /*println!("GPU: {:#?}", debugger.gba.sysbus.io.gpu)*/ }
|
||||
match command {
|
||||
Info => {
|
||||
println!("{}", self.gba.cpu);
|
||||
println!("IME={}", self.gba.sysbus.io.intc.interrupt_master_enable);
|
||||
println!("IE={:#?}", self.gba.sysbus.io.intc.interrupt_enable);
|
||||
println!("IF={:#?}", self.gba.sysbus.io.intc.interrupt_flags);
|
||||
}
|
||||
GpuInfo => println!("GPU: {:#?}", self.gba.sysbus.io.gpu),
|
||||
Step(count) => {
|
||||
debugger.ctrlc_flag.store(true, Ordering::SeqCst);
|
||||
self.ctrlc_flag.store(true, Ordering::SeqCst);
|
||||
for _ in 0..count {
|
||||
if !debugger.ctrlc_flag.load(Ordering::SeqCst) {
|
||||
if !self.ctrlc_flag.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
debugger.gba.step_new();
|
||||
while debugger.gba.cpu.last_executed.is_none() {
|
||||
debugger.gba.step_new();
|
||||
self.gba.step_new();
|
||||
while self.gba.cpu.last_executed.is_none() {
|
||||
self.gba.step_new();
|
||||
}
|
||||
let last_executed = debugger.gba.cpu.last_executed.unwrap();
|
||||
let last_executed = self.gba.cpu.last_executed.unwrap();
|
||||
print!(
|
||||
"{}\t{}",
|
||||
Colour::Black
|
||||
|
@ -81,99 +96,48 @@ impl Command {
|
|||
"{}",
|
||||
Colour::Purple.dimmed().italic().paint(format!(
|
||||
"\t\t/// Next instruction at @0x{:08x}",
|
||||
debugger.gba.cpu.get_next_pc()
|
||||
self.gba.cpu.get_next_pc()
|
||||
))
|
||||
);
|
||||
}
|
||||
println!("{}\n", debugger.gba.cpu);
|
||||
// match debugger.gba.step() {
|
||||
// Ok(insn) => {
|
||||
// print!(
|
||||
// "{}\t{}",
|
||||
// Colour::Black
|
||||
// .bold()
|
||||
// .italic()
|
||||
// .on(Colour::White)
|
||||
// .paint(format!("Executed at @0x{:08x}:", insn.get_pc(),)),
|
||||
// insn
|
||||
// );
|
||||
// println!(
|
||||
// "{}",
|
||||
// Colour::Purple.dimmed().italic().paint(format!(
|
||||
// "\t\t/// Next instruction at @0x{:08x}",
|
||||
// debugger.gba.cpu.get_next_pc()
|
||||
// ))
|
||||
// )
|
||||
// }
|
||||
// Err(GBAError::CpuError(e)) => {
|
||||
// println!("{}: {}", "cpu encountered an error".red(), e);
|
||||
// println!("cpu: {:x?}", debugger.gba.cpu)
|
||||
// }
|
||||
// _ => unreachable!(),
|
||||
// }
|
||||
println!("{}\n", self.gba.cpu);
|
||||
}
|
||||
Continue => {
|
||||
let frame_time = time::Duration::new(0, 1_000_000_000u32 / 60);
|
||||
debugger.ctrlc_flag.store(true, Ordering::SeqCst);
|
||||
while debugger.ctrlc_flag.load(Ordering::SeqCst) {
|
||||
self.ctrlc_flag.store(true, Ordering::SeqCst);
|
||||
while self.ctrlc_flag.load(Ordering::SeqCst) {
|
||||
let start_time = time::Instant::now();
|
||||
debugger.gba.frame();
|
||||
|
||||
let time_passed = start_time.elapsed();
|
||||
let delay = frame_time.checked_sub(time_passed);
|
||||
match delay {
|
||||
None => {}
|
||||
Some(delay) => {
|
||||
::std::thread::sleep(delay);
|
||||
self.gba.update_key_state();
|
||||
match self.gba.check_breakpoint() {
|
||||
Some(addr) => {
|
||||
println!("Breakpoint reached! @{:x}", addr);
|
||||
break;
|
||||
}
|
||||
};
|
||||
_ => {
|
||||
self.gba.step_new();
|
||||
}
|
||||
}
|
||||
}
|
||||
// let start_cycles = debugger.gba.cpu.cycles();
|
||||
// loop {
|
||||
// if let Some(bp) = debugger.check_breakpoint() {
|
||||
// match debugger.gba.step() {
|
||||
// Err(GBAError::CpuError(e)) => {
|
||||
// println!("{}: {}", "cpu encountered an error".red(), e);
|
||||
// println!("cpu: {:x?}", debugger.gba.cpu);
|
||||
// break;
|
||||
// }
|
||||
// _ => (),
|
||||
// };
|
||||
// let num_cycles = debugger.gba.cpu.cycles() - start_cycles;
|
||||
// println!("hit breakpoint #0x{:08x} after {} cycles !", bp, num_cycles);
|
||||
// break;
|
||||
// } else {
|
||||
// match debugger.gba.step() {
|
||||
// Err(GBAError::CpuError(e)) => {
|
||||
// println!("{}: {}", "cpu encountered an error".red(), e);
|
||||
// println!("cpu: {:x?}", debugger.gba.cpu);
|
||||
// break;
|
||||
// }
|
||||
// _ => (),
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
}
|
||||
Frame(count) => {
|
||||
use super::time::PreciseTime;
|
||||
let start = PreciseTime::now();
|
||||
for _ in 0..count {
|
||||
debugger.gba.frame();
|
||||
self.gba.frame();
|
||||
}
|
||||
let end = PreciseTime::now();
|
||||
println!("that took {} seconds", start.to(end));
|
||||
}
|
||||
HexDump(addr, nbytes) => {
|
||||
let bytes = debugger.gba.sysbus.get_bytes(addr..addr + nbytes);
|
||||
let bytes = self.gba.sysbus.get_bytes(addr..addr + nbytes);
|
||||
hexdump::hexdump(&bytes);
|
||||
}
|
||||
MemWrite(size, addr, val) => match size {
|
||||
MemWriteCommandSize::Byte => debugger.gba.sysbus.write_8(addr, val as u8),
|
||||
MemWriteCommandSize::Half => debugger.gba.sysbus.write_16(addr, val as u16),
|
||||
MemWriteCommandSize::Word => debugger.gba.sysbus.write_32(addr, val as u32),
|
||||
MemWriteCommandSize::Byte => self.gba.sysbus.write_8(addr, val as u8),
|
||||
MemWriteCommandSize::Half => self.gba.sysbus.write_16(addr, val as u16),
|
||||
MemWriteCommandSize::Word => self.gba.sysbus.write_32(addr, val as u32),
|
||||
},
|
||||
Disass(mode, addr, n) => {
|
||||
let bytes = debugger.gba.sysbus.get_bytes(addr..addr + n);
|
||||
let bytes = self.gba.sysbus.get_bytes(addr..addr + n);
|
||||
match mode {
|
||||
DisassMode::ModeArm => {
|
||||
let disass = Disassembler::<ArmInstruction>::new(addr, &bytes);
|
||||
|
@ -191,37 +155,60 @@ impl Command {
|
|||
}
|
||||
Quit => {
|
||||
print!("Quitting!");
|
||||
debugger.stop();
|
||||
self.stop();
|
||||
}
|
||||
AddBreakpoint(addr) => {
|
||||
if !debugger.gba.cpu.breakpoints.contains(&addr) {
|
||||
let new_index = debugger.gba.cpu.breakpoints.len();
|
||||
debugger.gba.cpu.breakpoints.push(addr);
|
||||
println!("added breakpoint [{}] 0x{:08x}", new_index, addr);
|
||||
} else {
|
||||
println!("breakpoint already exists!")
|
||||
}
|
||||
}
|
||||
DelBreakpoint(addr) => debugger.delete_breakpoint(addr),
|
||||
ClearBreakpoints => debugger.gba.cpu.breakpoints.clear(),
|
||||
AddBreakpoint(addr) => match self.gba.add_breakpoint(addr) {
|
||||
Some(index) => println!("Added breakpoint [{}] 0x{:08x}", index, addr),
|
||||
None => println!("Breakpint already exists."),
|
||||
},
|
||||
DelBreakpoint(addr) => self.delete_breakpoint(addr),
|
||||
ClearBreakpoints => self.gba.cpu.breakpoints.clear(),
|
||||
ListBreakpoints => {
|
||||
println!("breakpoint list:");
|
||||
for (i, b) in debugger.gba.cpu.breakpoints.iter().enumerate() {
|
||||
for (i, b) in self.gba.cpu.breakpoints.iter().enumerate() {
|
||||
println!("[{}] 0x{:08x}", i, b)
|
||||
}
|
||||
}
|
||||
PaletteView => create_palette_view(&debugger.gba.sysbus.palette_ram.mem),
|
||||
// TileView(bg) => create_tile_view(bg, &debugger.gba),
|
||||
PaletteView => create_palette_view(&self.gba.sysbus.palette_ram.mem),
|
||||
// TileView(bg) => create_tile_view(bg, &self.gba),
|
||||
Reset => {
|
||||
println!("resetting cpu...");
|
||||
debugger.gba.cpu.reset(&mut debugger.gba.sysbus);
|
||||
self.gba.cpu.reset(&mut self.gba.sysbus);
|
||||
println!("cpu is restarted!")
|
||||
}
|
||||
TraceToggle(flags) => {
|
||||
if flags.contains(TraceFlags::TRACE_SYSBUS) {
|
||||
self.gba.sysbus.trace_access = !self.gba.sysbus.trace_access;
|
||||
println!(
|
||||
"[*] sysbus tracing {}",
|
||||
if self.gba.sysbus.trace_access {
|
||||
"on"
|
||||
} else {
|
||||
"off"
|
||||
}
|
||||
)
|
||||
}
|
||||
if flags.contains(TraceFlags::TRACE_OPCODE) {
|
||||
self.gba.cpu.trace_opcodes = !self.gba.cpu.trace_opcodes;
|
||||
println!(
|
||||
"[*] opcode tracing {}",
|
||||
if self.gba.cpu.trace_opcodes {
|
||||
"on"
|
||||
} else {
|
||||
"off"
|
||||
}
|
||||
)
|
||||
}
|
||||
if flags.contains(TraceFlags::TRACE_DMA) {
|
||||
println!("[*] dma tracing not implemented");
|
||||
}
|
||||
if flags.contains(TraceFlags::TRACE_TIMERS) {
|
||||
println!("[*] timers tracing not implemented");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debugger {
|
||||
fn get_disassembler_args(&self, args: Vec<Value>) -> DebuggerResult<(Addr, u32)> {
|
||||
match args.len() {
|
||||
2 => {
|
||||
|
@ -260,7 +247,7 @@ impl Debugger {
|
|||
|
||||
match command.as_ref() {
|
||||
"i" | "info" => Ok(Command::Info),
|
||||
"dispinfo" => Ok(Command::DisplayInfo),
|
||||
"gpuinfo" => Ok(Command::GpuInfo),
|
||||
"s" | "step" => {
|
||||
let count = match args.len() {
|
||||
0 => 1,
|
||||
|
@ -424,6 +411,28 @@ impl Debugger {
|
|||
"bl" => Ok(Command::ListBreakpoints),
|
||||
"q" | "quit" => Ok(Command::Quit),
|
||||
"r" | "reset" => Ok(Command::Reset),
|
||||
"trace" => {
|
||||
let usage = DebuggerError::InvalidCommandFormat(String::from(
|
||||
"trace [sysbus|opcode|dma|all]",
|
||||
));
|
||||
if args.len() != 1 {
|
||||
Err(usage)
|
||||
} else {
|
||||
if let Value::Identifier(flag_str) = &args[0] {
|
||||
let flags = match flag_str.as_ref() {
|
||||
"sysbus" => TraceFlags::TRACE_SYSBUS,
|
||||
"opcode" => TraceFlags::TRACE_OPCODE,
|
||||
"dma" => TraceFlags::TRACE_DMA,
|
||||
"timers" => TraceFlags::TRACE_TIMERS,
|
||||
"all" => TraceFlags::all(),
|
||||
_ => return Err(usage),
|
||||
};
|
||||
Ok(Command::TraceToggle(flags))
|
||||
} else {
|
||||
Err(usage)
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Err(DebuggerError::InvalidCommand(command)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
extern crate ctrlc;
|
||||
use std::fs::File;
|
||||
use std::io::{self, prelude::*, BufReader};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -20,13 +22,14 @@ mod palette_view;
|
|||
mod tile_view;
|
||||
extern crate time;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug)]
|
||||
pub enum DebuggerError {
|
||||
ParsingError(String),
|
||||
CpuError(CpuError),
|
||||
InvalidCommand(String),
|
||||
InvalidArgument(String),
|
||||
InvalidCommandFormat(String),
|
||||
IoError(::std::io::Error),
|
||||
}
|
||||
|
||||
impl From<CpuError> for DebuggerError {
|
||||
|
@ -35,6 +38,12 @@ impl From<CpuError> for DebuggerError {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<::std::io::Error> for DebuggerError {
|
||||
fn from(e: ::std::io::Error) -> DebuggerError {
|
||||
DebuggerError::IoError(e)
|
||||
}
|
||||
}
|
||||
|
||||
type DebuggerResult<T> = Result<T, DebuggerError>;
|
||||
|
||||
pub struct Debugger {
|
||||
|
@ -147,7 +156,7 @@ impl Debugger {
|
|||
Expr::Command(c, a) => match self.eval_command(c, a) {
|
||||
Ok(cmd) => {
|
||||
self.previous_command = Some(cmd.clone());
|
||||
cmd.run(self)
|
||||
self.run_command(cmd)
|
||||
}
|
||||
Err(DebuggerError::InvalidCommand(c)) => {
|
||||
println!("{}: {:?}", "invalid command".red(), c)
|
||||
|
@ -174,11 +183,20 @@ impl Debugger {
|
|||
self.running = false;
|
||||
}
|
||||
|
||||
pub fn repl(&mut self) -> DebuggerResult<()> {
|
||||
pub fn repl(&mut self, script_file: Option<&str>) -> DebuggerResult<()> {
|
||||
println!("Welcome to rustboyadvance-NG debugger 😎!\n");
|
||||
self.running = true;
|
||||
let mut rl = Editor::<()>::new();
|
||||
let _ = rl.load_history(".rustboyadvance_history");
|
||||
if let Some(path) = script_file {
|
||||
println!("Executing script file");
|
||||
let file = File::open(path)?;
|
||||
let reader = BufReader::new(file);
|
||||
for line in reader.lines() {
|
||||
let expr = parse_expr(&line.unwrap())?;
|
||||
self.eval_expr(expr);
|
||||
}
|
||||
}
|
||||
while self.running {
|
||||
let readline = rl.readline(&format!("({}) ᐅ ", "rustboyadvance-dbg".bold().cyan()));
|
||||
match readline {
|
||||
|
|
Reference in a new issue