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:
Michel Heily 2019-11-16 17:58:44 +02:00
parent 7e98af80c2
commit b288625b9a
7 changed files with 190 additions and 107 deletions

11
Cargo.lock generated
View file

@ -190,6 +190,15 @@ dependencies = [
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "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]] [[package]]
name = "dirs" name = "dirs"
version = "2.0.2" version = "2.0.2"
@ -658,6 +667,7 @@ dependencies = [
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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)", "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)", "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)", "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 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 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 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 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 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" "checksum enum-primitive-derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b90e520ec62c1864c8c78d637acbfe8baf5f63240f2fb8165b8325c07812dd"

View file

@ -28,3 +28,11 @@ args:
- debug: - debug:
long: 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

View file

@ -69,7 +69,7 @@ fn run_emulator(matches: &ArgMatches) -> GBAResult<()> {
gba.cpu.set_verbose(true); gba.cpu.set_verbose(true);
let mut debugger = Debugger::new(gba); let mut debugger = Debugger::new(gba);
println!("starting debugger..."); println!("starting debugger...");
debugger.repl()?; debugger.repl(matches.value_of("script_file"))?;
println!("ending debugger..."); println!("ending debugger...");
} else { } else {
let frame_time = time::Duration::new(0, 1_000_000_000u32 / 60); let frame_time = time::Duration::new(0, 1_000_000_000u32 / 60);

View file

@ -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 /// Perform a pipeline step
/// If an instruction was executed in this step, return it. /// If an instruction was executed in this step, return it.
pub fn step(&mut self, bus: &mut SysBus) -> CpuResult<()> { pub fn step(&mut self, bus: &mut SysBus) -> CpuResult<()> {

View file

@ -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(); 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) { pub fn step_new(&mut self) {
let mut irqs = IrqBitmask(0); let mut irqs = IrqBitmask(0);
let previous_cycles = self.cpu.cycles; let previous_cycles = self.cpu.cycles;

View file

@ -31,10 +31,19 @@ pub enum MemWriteCommandSize {
Word, 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)] #[derive(Debug, PartialEq, Clone)]
pub enum Command { pub enum Command {
Info, Info,
DisplayInfo, GpuInfo,
Step(usize), Step(usize),
Continue, Continue,
Frame(usize), Frame(usize),
@ -49,25 +58,31 @@ pub enum Command {
ListBreakpoints, ListBreakpoints,
Reset, Reset,
Quit, Quit,
TraceToggle(TraceFlags),
} }
impl Command { impl Debugger {
pub fn run(&self, debugger: &mut Debugger) { pub fn run_command(&mut self, command: Command) {
use Command::*; use Command::*;
match *self { match command {
Info => println!("{}", debugger.gba.cpu), Info => {
DisplayInfo => { /*println!("GPU: {:#?}", debugger.gba.sysbus.io.gpu)*/ } 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) => { Step(count) => {
debugger.ctrlc_flag.store(true, Ordering::SeqCst); self.ctrlc_flag.store(true, Ordering::SeqCst);
for _ in 0..count { for _ in 0..count {
if !debugger.ctrlc_flag.load(Ordering::SeqCst) { if !self.ctrlc_flag.load(Ordering::SeqCst) {
break; break;
} }
debugger.gba.step_new(); self.gba.step_new();
while debugger.gba.cpu.last_executed.is_none() { while self.gba.cpu.last_executed.is_none() {
debugger.gba.step_new(); self.gba.step_new();
} }
let last_executed = debugger.gba.cpu.last_executed.unwrap(); let last_executed = self.gba.cpu.last_executed.unwrap();
print!( print!(
"{}\t{}", "{}\t{}",
Colour::Black Colour::Black
@ -81,99 +96,48 @@ impl Command {
"{}", "{}",
Colour::Purple.dimmed().italic().paint(format!( Colour::Purple.dimmed().italic().paint(format!(
"\t\t/// Next instruction at @0x{:08x}", "\t\t/// Next instruction at @0x{:08x}",
debugger.gba.cpu.get_next_pc() self.gba.cpu.get_next_pc()
)) ))
); );
} }
println!("{}\n", debugger.gba.cpu); println!("{}\n", self.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!(),
// }
} }
Continue => { Continue => {
let frame_time = time::Duration::new(0, 1_000_000_000u32 / 60); self.ctrlc_flag.store(true, Ordering::SeqCst);
debugger.ctrlc_flag.store(true, Ordering::SeqCst); while self.ctrlc_flag.load(Ordering::SeqCst) {
while debugger.ctrlc_flag.load(Ordering::SeqCst) {
let start_time = time::Instant::now(); let start_time = time::Instant::now();
debugger.gba.frame(); self.gba.update_key_state();
match self.gba.check_breakpoint() {
let time_passed = start_time.elapsed(); Some(addr) => {
let delay = frame_time.checked_sub(time_passed); println!("Breakpoint reached! @{:x}", addr);
match delay { break;
None => {} }
Some(delay) => { _ => {
::std::thread::sleep(delay); 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) => { Frame(count) => {
use super::time::PreciseTime; use super::time::PreciseTime;
let start = PreciseTime::now(); let start = PreciseTime::now();
for _ in 0..count { for _ in 0..count {
debugger.gba.frame(); self.gba.frame();
} }
let end = PreciseTime::now(); let end = PreciseTime::now();
println!("that took {} seconds", start.to(end)); println!("that took {} seconds", start.to(end));
} }
HexDump(addr, nbytes) => { 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); hexdump::hexdump(&bytes);
} }
MemWrite(size, addr, val) => match size { MemWrite(size, addr, val) => match size {
MemWriteCommandSize::Byte => debugger.gba.sysbus.write_8(addr, val as u8), MemWriteCommandSize::Byte => self.gba.sysbus.write_8(addr, val as u8),
MemWriteCommandSize::Half => debugger.gba.sysbus.write_16(addr, val as u16), MemWriteCommandSize::Half => self.gba.sysbus.write_16(addr, val as u16),
MemWriteCommandSize::Word => debugger.gba.sysbus.write_32(addr, val as u32), MemWriteCommandSize::Word => self.gba.sysbus.write_32(addr, val as u32),
}, },
Disass(mode, addr, n) => { 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 { match mode {
DisassMode::ModeArm => { DisassMode::ModeArm => {
let disass = Disassembler::<ArmInstruction>::new(addr, &bytes); let disass = Disassembler::<ArmInstruction>::new(addr, &bytes);
@ -191,37 +155,60 @@ impl Command {
} }
Quit => { Quit => {
print!("Quitting!"); print!("Quitting!");
debugger.stop(); self.stop();
} }
AddBreakpoint(addr) => { AddBreakpoint(addr) => match self.gba.add_breakpoint(addr) {
if !debugger.gba.cpu.breakpoints.contains(&addr) { Some(index) => println!("Added breakpoint [{}] 0x{:08x}", index, addr),
let new_index = debugger.gba.cpu.breakpoints.len(); None => println!("Breakpint already exists."),
debugger.gba.cpu.breakpoints.push(addr); },
println!("added breakpoint [{}] 0x{:08x}", new_index, addr); DelBreakpoint(addr) => self.delete_breakpoint(addr),
} else { ClearBreakpoints => self.gba.cpu.breakpoints.clear(),
println!("breakpoint already exists!")
}
}
DelBreakpoint(addr) => debugger.delete_breakpoint(addr),
ClearBreakpoints => debugger.gba.cpu.breakpoints.clear(),
ListBreakpoints => { ListBreakpoints => {
println!("breakpoint list:"); 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) println!("[{}] 0x{:08x}", i, b)
} }
} }
PaletteView => create_palette_view(&debugger.gba.sysbus.palette_ram.mem), PaletteView => create_palette_view(&self.gba.sysbus.palette_ram.mem),
// TileView(bg) => create_tile_view(bg, &debugger.gba), // TileView(bg) => create_tile_view(bg, &self.gba),
Reset => { Reset => {
println!("resetting cpu..."); println!("resetting cpu...");
debugger.gba.cpu.reset(&mut debugger.gba.sysbus); self.gba.cpu.reset(&mut self.gba.sysbus);
println!("cpu is restarted!") 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)> { fn get_disassembler_args(&self, args: Vec<Value>) -> DebuggerResult<(Addr, u32)> {
match args.len() { match args.len() {
2 => { 2 => {
@ -260,7 +247,7 @@ impl Debugger {
match command.as_ref() { match command.as_ref() {
"i" | "info" => Ok(Command::Info), "i" | "info" => Ok(Command::Info),
"dispinfo" => Ok(Command::DisplayInfo), "gpuinfo" => Ok(Command::GpuInfo),
"s" | "step" => { "s" | "step" => {
let count = match args.len() { let count = match args.len() {
0 => 1, 0 => 1,
@ -424,6 +411,28 @@ impl Debugger {
"bl" => Ok(Command::ListBreakpoints), "bl" => Ok(Command::ListBreakpoints),
"q" | "quit" => Ok(Command::Quit), "q" | "quit" => Ok(Command::Quit),
"r" | "reset" => Ok(Command::Reset), "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)), _ => Err(DebuggerError::InvalidCommand(command)),
} }
} }

View file

@ -1,4 +1,6 @@
extern crate ctrlc; extern crate ctrlc;
use std::fs::File;
use std::io::{self, prelude::*, BufReader};
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
@ -20,13 +22,14 @@ mod palette_view;
mod tile_view; mod tile_view;
extern crate time; extern crate time;
#[derive(Debug, PartialEq)] #[derive(Debug)]
pub enum DebuggerError { pub enum DebuggerError {
ParsingError(String), ParsingError(String),
CpuError(CpuError), CpuError(CpuError),
InvalidCommand(String), InvalidCommand(String),
InvalidArgument(String), InvalidArgument(String),
InvalidCommandFormat(String), InvalidCommandFormat(String),
IoError(::std::io::Error),
} }
impl From<CpuError> for DebuggerError { 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>; type DebuggerResult<T> = Result<T, DebuggerError>;
pub struct Debugger { pub struct Debugger {
@ -147,7 +156,7 @@ impl Debugger {
Expr::Command(c, a) => match self.eval_command(c, a) { Expr::Command(c, a) => match self.eval_command(c, a) {
Ok(cmd) => { Ok(cmd) => {
self.previous_command = Some(cmd.clone()); self.previous_command = Some(cmd.clone());
cmd.run(self) self.run_command(cmd)
} }
Err(DebuggerError::InvalidCommand(c)) => { Err(DebuggerError::InvalidCommand(c)) => {
println!("{}: {:?}", "invalid command".red(), c) println!("{}: {:?}", "invalid command".red(), c)
@ -174,11 +183,20 @@ impl Debugger {
self.running = false; 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"); println!("Welcome to rustboyadvance-NG debugger 😎!\n");
self.running = true; self.running = true;
let mut rl = Editor::<()>::new(); let mut rl = Editor::<()>::new();
let _ = rl.load_history(".rustboyadvance_history"); 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 { while self.running {
let readline = rl.readline(&format!("({}) ᐅ ", "rustboyadvance-dbg".bold().cyan())); let readline = rl.readline(&format!("({}) ᐅ ", "rustboyadvance-dbg".bold().cyan()));
match readline { match readline {