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)",
]
[[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"

View file

@ -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

View file

@ -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);

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
/// If an instruction was executed in this step, return it.
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();
}
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;

View file

@ -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)),
}
}

View file

@ -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 {