Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Several debugger improvements #20

Merged
merged 8 commits into from
Aug 26, 2018
2 changes: 2 additions & 0 deletions core/src/instr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,7 @@ pub const PREFIXED_INSTRUCTIONS: InstrDb<Instr> = InstrDb([
Instr::new(0xff, "SET 7, A", 2, 8, None),
]);

#[macro_export]
macro_rules! opcode {
("NOP") => { 0x00 };
("LD BC, d16") => { 0x01 };
Expand Down Expand Up @@ -907,6 +908,7 @@ macro_rules! opcode {
("RST 38H") => { 0xff };
}

#[macro_export]
macro_rules! prefixed_opcode {
("RLC B") => { 0x00 };
("RLC C") => { 0x01 };
Expand Down
34 changes: 33 additions & 1 deletion desktop/src/args.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use std::path::PathBuf;

use minifb::Scale;
use structopt::StructOpt;
use std::path::PathBuf;

use mahboi::primitives::Word;


#[derive(Debug, StructOpt)]
Expand All @@ -25,6 +28,25 @@ pub(crate) struct Args {
help = "Path to the ROM that should be loaded into the emulator.",
)]
pub(crate) path_to_rom: PathBuf,

#[structopt(
long = "--breakpoints",
parse(try_from_str = "parse_breakpoint"),
requires = "debug",
help = "Breakpoint that is added to the debugger at the very beginning. Breakpoints are \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not clear, if the user can pass one or many breakpoints (and if many, how they have to be separated (e.g. 0x42, 0x21 or 0x42;0x21 or 0x42 0x21 etc.)).

specified in hexadecimal. To add multiple breakpoints, you can either list them after \
one `--breakpoints` flag or specify `--breakpoints` multiple times. Example: \
`--breakpoints 23 FF --breakpoints 10B`.",
)]
pub(crate) breakpoints: Vec<Word>,

#[structopt(
long = "--instant-start",
requires = "debug",
help = "When starting in debugging mode, don't pause at the beginning, but start running \
right ahead (particularly useful in combination with `--breakpoints`)",
)]
pub(crate) instant_start: bool,
}

fn parse_scale(src: &str) -> Result<Scale, &'static str> {
Expand All @@ -39,3 +61,13 @@ fn parse_scale(src: &str) -> Result<Scale, &'static str> {
_ => Err("only '1', '2', '4', '8', '16', '32' or 'fit' are allowed"),
}
}

fn parse_breakpoint(src: &str) -> Result<Word, String> {
u16::from_str_radix(src, 16)
.map(Word::new)
.map_err(|e| format!(
"failed to parse breakpoint: {} (values like '1f' are valid -- no \
leading `0x`!)",
e,
))
}
84 changes: 78 additions & 6 deletions desktop/src/debug/tui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ use lazy_static::lazy_static;
use log::{Log, Record, Level, Metadata};

use mahboi::{
opcode,
log::*,
machine::{Cpu, Machine},
primitives::Word,
};
use crate::args::Args;
use super::{Action};
use self::{
asm_view::AsmView,
Expand Down Expand Up @@ -128,10 +130,14 @@ pub(crate) struct TuiDebugger {

/// A set of addresses at which we will pause execution
breakpoints: Breakpoints,

/// Flag that is set when the user requested to run until the next RET
/// instruction.
pause_on_ret: bool,
}

impl TuiDebugger {
pub(crate) fn new() -> Result<Self, Error> {
pub(crate) fn new(args: &Args) -> Result<Self, Error> {
// Create a handle to the terminal (with the correct backend).
let mut siv = Cursive::ncurses();

Expand Down Expand Up @@ -173,8 +179,14 @@ impl TuiDebugger {
event_sink,
step_over: None,
breakpoints: Breakpoints::new(),
pause_on_ret: false,
};

// Add all breakpoints specified by CLI
for &bp in &args.breakpoints {
out.breakpoints.add(bp);
}

// Build the TUI view
out.setup_tui();

Expand Down Expand Up @@ -204,6 +216,7 @@ impl TuiDebugger {
if is_paused {
self.siv.find_id::<AsmView>("asm_view").unwrap().update(machine);
self.update_cpu_data(&machine.cpu);
self.update_stack_data(machine);
}

// Append all log messages that were pushed to the global buffer into
Expand Down Expand Up @@ -243,6 +256,14 @@ impl TuiDebugger {
return Ok(Action::Continue);
}
}
'f' => {
if self.pause_mode {
self.step_over = Some(machine.cpu.pc);
self.pause_on_ret = true;
self.resume();
return Ok(Action::Continue);
}
}
_ => panic!("internal error: unexpected event"),
}
}
Expand Down Expand Up @@ -304,6 +325,25 @@ impl TuiDebugger {
return true;
}

// If we are supposed to pause on a RET instruction...
if self.pause_on_ret {
// ... check if the next instruction is an RET-like instruction
let opcode = machine.load_byte(machine.cpu.pc);
match opcode.get() {
opcode!("RET")
| opcode!("RETI")
| opcode!("RET NZ")
| opcode!("RET NC")
| opcode!("RET Z")
| opcode!("RET C") => {
// Reset the flag
self.pause_on_ret = false;
return true;
}
_ => {}
}
}

false
}

Expand All @@ -315,7 +355,7 @@ impl TuiDebugger {

// Other global events are just forwarded to be handled in the next
// `update()` call.
for &c in &['p', 'r', 's'] {
for &c in &['p', 'r', 's', 'f'] {
let tx = self.event_sink.clone();
self.siv.add_global_callback(c, move |_| tx.send(c).unwrap());
}
Expand Down Expand Up @@ -366,6 +406,31 @@ impl TuiDebugger {
)
}

fn update_stack_data(&mut self, machine: &Machine) {
let mut body = StyledString::new();

let start = machine.cpu.sp.get();
let end = start.saturating_add(10);

for addr in start..end {
let addr = Word::new(addr);
body.append_styled(addr.to_string(), Color::Light(BaseColor::Blue));
body.append_styled(" │ ", Color::Light(BaseColor::Blue));
body.append_styled(
machine.load_byte(addr).to_string(),
Color::Dark(BaseColor::Yellow),
);

if addr == start {
body.append_plain(" ← SP");
}

body.append_plain("\n");
}

self.siv.find_id::<TextView>("stack_view").unwrap().set_content(body);
}

fn update_cpu_data(&mut self, cpu: &Cpu) {
let reg_style = Color::Light(BaseColor::Magenta);

Expand Down Expand Up @@ -435,6 +500,8 @@ impl TuiDebugger {
let cpu_body = TextView::new("no data yet").center().with_id("cpu_data");
let cpu_view = Dialog::around(cpu_body).title("CPU registers");

let stack_body = TextView::new("no data yet").with_id("stack_view");
let stack_view = Dialog::around(stack_body).title("Stack");

// Setup Buttons
let button_breakpoints = {
Expand All @@ -444,23 +511,28 @@ impl TuiDebugger {
})
};

// Buttons for the 'r' and 's' actions
// Buttons for the 'r', 's' and 'f' actions
let tx = self.event_sink.clone();
let run_button = Button::new("Continue [r]", move |_| tx.send('r').unwrap());
let tx = self.event_sink.clone();
let step_button = Button::new("Single step [s]", move |_| tx.send('s').unwrap());
let tx = self.event_sink.clone();
let fun_end_button = Button::new("Run to RET-like [f]", move |_| tx.send('f').unwrap());

// Wrap all buttons
let debug_buttons = LinearLayout::vertical()
.child(button_breakpoints)
.child(run_button)
.child(step_button);
.child(step_button)
.child(fun_end_button);
let debug_buttons = Dialog::around(debug_buttons).title("Actions");

// Build the complete right side
let right_panel = LinearLayout::vertical()
.child(cpu_view)
.child(DummyView)
.child(stack_view)
.child(DummyView)
.child(debug_buttons)
.fixed_width(30);

Expand Down Expand Up @@ -554,7 +626,7 @@ impl TuiDebugger {
/// easily usable from everywhere. Just `clone()` this to get another owned
/// reference.
#[derive(Clone)]
struct Breakpoints(Rc<RefCell<BTreeSet<Word>>>);
pub(crate) struct Breakpoints(Rc<RefCell<BTreeSet<Word>>>);

impl Breakpoints {
fn new() -> Self {
Expand All @@ -563,7 +635,7 @@ impl Breakpoints {

/// Add a breakpoint to the collection. If it's already inside, nothing
/// happens.
fn add(&self, addr: Word) {
pub(crate) fn add(&self, addr: Word) {
self.0.borrow_mut().insert(addr);
}

Expand Down
4 changes: 2 additions & 2 deletions desktop/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ fn run() -> Result<(), Error> {

// Create the TUI debugger if we're in debug mode.
let mut tui_debugger = if args.debug {
Some(TuiDebugger::new()?)
Some(TuiDebugger::new(&args)?)
} else {
None
};
Expand All @@ -62,7 +62,7 @@ fn run() -> Result<(), Error> {
let mut window = NativeWindow::open(&args).context("failed to open window")?;
info!("Opened window");

let mut is_paused = args.debug;
let mut is_paused = args.debug && !args.instant_start;
while !window.should_stop() {
// Update window buffer and read input.
window.update()?;
Expand Down