From b288625b9ab854668bf18f2dde39fe50c867a16b Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Sat, 16 Nov 2019 17:58:44 +0200 Subject: [PATCH] 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 --- Cargo.lock | 11 ++ src/bin/cli.yml | 10 +- src/bin/main.rs | 2 +- src/core/arm7tdmi/cpu.rs | 16 +++ src/core/gba.rs | 23 ++++- src/debugger/command.rs | 211 ++++++++++++++++++++------------------- src/debugger/mod.rs | 24 ++++- 7 files changed, 190 insertions(+), 107 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4179c3..7e1f9eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/src/bin/cli.yml b/src/bin/cli.yml index 30d4ae8..3214dcf 100644 --- a/src/bin/cli.yml +++ b/src/bin/cli.yml @@ -27,4 +27,12 @@ args: required: false - debug: long: debug - help: Start with the debugger attached \ No newline at end of file + 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 \ No newline at end of file diff --git a/src/bin/main.rs b/src/bin/main.rs index 888e291..5fe3ea5 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -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); diff --git a/src/core/arm7tdmi/cpu.rs b/src/core/arm7tdmi/cpu.rs index 74350e9..bda63b7 100644 --- a/src/core/arm7tdmi/cpu.rs +++ b/src/core/arm7tdmi/cpu.rs @@ -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<()> { diff --git a/src/core/gba.rs b/src/core/gba.rs index d71552e..f742618 100644 --- a/src/core/gba.rs +++ b/src/core/gba.rs @@ -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 { + 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 { + 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; diff --git a/src/debugger/command.rs b/src/debugger/command.rs index 29b62a7..b4f83d8 100644 --- a/src/debugger/command.rs +++ b/src/debugger/command.rs @@ -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::::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) -> 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)), } } diff --git a/src/debugger/mod.rs b/src/debugger/mod.rs index 008f97e..54d978e 100644 --- a/src/debugger/mod.rs +++ b/src/debugger/mod.rs @@ -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 for DebuggerError { @@ -35,6 +38,12 @@ impl From for DebuggerError { } } +impl From<::std::io::Error> for DebuggerError { + fn from(e: ::std::io::Error) -> DebuggerError { + DebuggerError::IoError(e) + } +} + type DebuggerResult = Result; 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 {