From 98336aad299f5d6e2315b2532cf58eb81668fe48 Mon Sep 17 00:00:00 2001 From: paul moore Date: Fri, 29 Dec 2023 22:08:27 -0800 Subject: [PATCH] start print and data watch --- src/cpu.rs | 74 +++++++++--------- src/debugger.rs | 37 ++++++--- src/dis.rs | 2 + src/execute.rs | 18 ++++- src/loader.rs | 4 + src/main.rs | 1 + src/paravirt.rs | 42 +++++++---- src/shell.rs | 196 ++++++++++++++++-------------------------------- src/syntax.rs | 124 ++++++++++++++++++++++++++++++ 9 files changed, 301 insertions(+), 197 deletions(-) create mode 100644 src/syntax.rs diff --git a/src/cpu.rs b/src/cpu.rs index 3827292..f6b42ce 100644 --- a/src/cpu.rs +++ b/src/cpu.rs @@ -2,24 +2,28 @@ Wrapper around the sim65 emulator. Provides the calls from db65 to 6502.c - - execute_insn to execute one instruction - - read_registers to get a pointer to the register block. + - ExecuteInsn to execute one instruction + - ReadRegisters to get a pointer to the register block. + - Reset to reset the cpu Provides the service routines that 6502.c needs - read and write ram - paravirt call backs + - runtime warnings and errors - Lots of unsafe code here becuase + Lots of unsafe code here because - we are doing c calls - we have a read / write singleton - The unsafe is limited to this file + */ use crate::paravirt::ParaVirt; +use bitflags::bitflags; use std::{fmt, os::raw::c_char}; // the one cpu instance +// this is because the calls to us are 'naked' c calls static mut THECPU: Cpu = Cpu { ram: [0; 65536], shadow: [0; 65536], @@ -31,15 +35,14 @@ static mut THECPU: Cpu = Cpu { arg_array: Vec::new(), }; pub struct Cpu { - ram: [u8; 65536], - shadow: [u8; 65536], - // registers - regs: *mut CPURegs, - exit: bool, - exit_code: u8, - sp65_addr: u8, - memcheck: Option, - arg_array: Vec, + ram: [u8; 65536], // the actual 6502 ram + shadow: [u8; 65536], // a shadow of the ram, used for memcheck + regs: *mut CPURegs, // a pointer to the register block + exit: bool, // set to true when the 6502 wants to exit + exit_code: u8, // the exit code + sp65_addr: u8, // the location of the cc65 'stack' pointer + memcheck: Option, // the address of the last memcheck failure + arg_array: Vec, // the command line arguments } // our callable functions into sim65 @@ -61,7 +64,6 @@ extern "C" fn MemWriteByte(_addr: u32, _val: u8) { THECPU.shadow[_addr as usize] = 1; } } - #[no_mangle] extern "C" fn MemReadWord(addr: u32) -> u32 { unsafe { @@ -71,7 +73,6 @@ extern "C" fn MemReadWord(addr: u32) -> u32 { } else if THECPU.shadow[(addr + 1) as usize] == 0 { THECPU.memcheck = Some(addr as u16 + 1); } - w } } @@ -87,7 +88,6 @@ extern "C" fn MemReadByte(addr: u32) -> u8 { } #[no_mangle] extern "C" fn MemReadZPWord(mut addr: u8) -> u16 { - //println!("MemReadZPWord"); unsafe { let b1 = THECPU.inner_read_byte(addr as u16) as u16; addr = addr.wrapping_add(1); @@ -106,11 +106,10 @@ extern "C" fn Error(_format: *const c_char, _x: u32, _y: u32) -> u32 { return 0; } #[no_mangle] - extern "C" fn ParaVirtHooks(_regs: *mut CPURegs) { - let _pc = Cpu::read_pc(); ParaVirt::pv_hooks(); } +// the structure we gtr a pointer to with ReadRegisters #[repr(C)] pub struct CPURegs { pub ac: u32, /* Accumulator */ @@ -269,26 +268,7 @@ impl Cpu { self.ram[(addr + 1) as usize] = (val >> 8) as u8; } } -#[test] -fn regreadwrite() { - Cpu::reset(); - Cpu::write_ac(1); - Cpu::write_xr(2); - Cpu::write_yr(3); - Cpu::write_zr(4); - Cpu::write_sr(5); - Cpu::write_sp(6); - Cpu::write_pc(0x7777); - assert_eq!(Cpu::read_ac(), 1); - assert_eq!(Cpu::read_xr(), 2); - assert_eq!(Cpu::read_yr(), 3); - assert_eq!(Cpu::read_zr(), 4); - assert_eq!(Cpu::read_sr(), 5); - assert_eq!(Cpu::read_sp(), 6); - assert_eq!(Cpu::read_pc(), 0x7777); -} -use bitflags::bitflags; bitflags! { #[derive(Copy, Clone, Default)] pub(crate) struct Status:u8{ @@ -347,3 +327,23 @@ impl fmt::Debug for Status { write!(f, "{}", str) } } + +#[test] +fn regreadwrite() { + Cpu::reset(); + Cpu::write_ac(1); + Cpu::write_xr(2); + Cpu::write_yr(3); + Cpu::write_zr(4); + Cpu::write_sr(5); + Cpu::write_sp(6); + Cpu::write_pc(0x7777); + + assert_eq!(Cpu::read_ac(), 1); + assert_eq!(Cpu::read_xr(), 2); + assert_eq!(Cpu::read_yr(), 3); + assert_eq!(Cpu::read_zr(), 4); + assert_eq!(Cpu::read_sr(), 5); + assert_eq!(Cpu::read_sp(), 6); + assert_eq!(Cpu::read_pc(), 0x7777); +} diff --git a/src/debugger.rs b/src/debugger.rs index 9b505ee..6478352 100644 --- a/src/debugger.rs +++ b/src/debugger.rs @@ -17,9 +17,8 @@ use crate::{cpu::Cpu, execute::StopReason, loader}; pub struct Debugger { symbols: HashMap, pub break_points: HashMap, - //pub break_points: HashMap, + // pub(crate) watch_points: HashMap, pub(crate) next_bp: Option, - _loader_sp: u8, loader_start: u16, pub(crate) dis_line: String, pub(crate) ticks: usize, @@ -45,13 +44,24 @@ pub struct BreakPoint { pub(crate) number: usize, pub(crate) temp: bool, } +pub enum WatchType { + Read, + Write, + ReadWrite, +} +pub struct WatchPoint { + pub(crate) addr: u16, + pub(crate) symbol: String, + pub(crate) number: usize, + pub(crate) Watch: bool, +} impl Debugger { pub fn new() -> Self { Cpu::reset(); Self { symbols: HashMap::new(), break_points: HashMap::new(), - _loader_sp: 0, + loader_start: 0, dis_line: String::new(), ticks: 0, @@ -145,11 +155,9 @@ impl Debugger { self.break_points.iter().map(|bp| bp.1.addr).collect() } pub fn go(&mut self) -> Result { - self.core_run() - } - fn state(&self) { - println!("pc={:04x}", Cpu::read_pc()); + self.execute(0) // 0 = forever } + pub fn next(&mut self) -> Result { let next_inst = Cpu::read_byte(Cpu::read_pc()); let reason = if next_inst == 0x20 { @@ -170,9 +178,7 @@ impl Debugger { pub fn step(&mut self) -> Result { self.execute(1) } - fn core_run(&mut self) -> Result { - self.execute(0) // 0 = forever - } + pub fn run(&mut self, cmd_args: Vec<&String>) -> Result { Cpu::write_word(0xFFFC, self.loader_start); Cpu::reset(); @@ -181,7 +187,7 @@ impl Debugger { Cpu::push_arg(&cmd_args[i]) } self.stack_frames.clear(); - self.core_run() + self.execute(0) // 0 = forever } pub fn get_chunk(&self, addr: u16, mut len: u16) -> Result> { let mut v = Vec::new(); @@ -192,6 +198,11 @@ impl Debugger { } Ok(v) } + + // converts a string representing an address into an address + // if string starts with '.' it is a symbol lookup + // if string starts with '$' it is a hex number + // else it is a decimal number pub fn convert_addr(&self, addr_str: &str) -> Result { if let Some(sym) = self.symbols.get(addr_str) { return Ok(*sym); @@ -203,6 +214,10 @@ impl Debugger { } Ok(u16::from_str_radix(addr_str, 10)?) } + + // reverse of convert_addr. + // tried to find a symbol matching an address + // if not found it returns a numberic string pub fn symbol_lookup(&self, addr: u16) -> String { for (name, sym_addr) in &self.symbols { if *sym_addr == addr { diff --git a/src/dis.rs b/src/dis.rs index e77943c..1774c44 100644 --- a/src/dis.rs +++ b/src/dis.rs @@ -1,5 +1,7 @@ /* Disassembler portion of db65. Placed in a separate file for clarity + +liberally copied from pm100/v65 */ use crate::debugger::Debugger; diff --git a/src/execute.rs b/src/execute.rs index 45df704..6337dda 100644 --- a/src/execute.rs +++ b/src/execute.rs @@ -32,7 +32,17 @@ impl Debugger { let counting = count > 0; let reason = loop { let pc = Cpu::read_pc(); - // is this a stack manipulation instruction? + /* + Stack tracking code + if we hit a jsr, we push the return address and the stack pointer + onto our own tracking stack. If we hit a rts, we pop the frame + + Also tracks push and pulls + + Does not deal with interrupts since sim65 does not support them + + Includes stack balance check logic + */ let inst = Cpu::read_byte(pc); match inst { 0x20 => { @@ -88,7 +98,11 @@ impl Debugger { } _ => {} }; + + // Now execute the instruction self.ticks += Cpu::execute_insn() as usize; + + // invalid memory read check if self.enable_mem_check { if let Some(addr) = Cpu::get_memcheck() { Cpu::clear_memcheck(); @@ -116,7 +130,7 @@ impl Debugger { } break StopReason::BreakPoint(pc); } - + // PVExit called? if let Some(exit_code) = Cpu::exit_done() { break StopReason::Exit(exit_code); } diff --git a/src/loader.rs b/src/loader.rs index 4f8c6ae..2c7cc72 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -1,3 +1,7 @@ +/* + reads binary and loads it into RAM +*/ + use anyhow::{bail, Result}; use std::fs::File; use std::io::{BufReader, Bytes, Read}; diff --git a/src/main.rs b/src/main.rs index 69ab292..dd54c3f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ mod execute; mod loader; mod paravirt; mod shell; +mod syntax; #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { diff --git a/src/paravirt.rs b/src/paravirt.rs index 62e938a..16b508b 100644 --- a/src/paravirt.rs +++ b/src/paravirt.rs @@ -1,15 +1,26 @@ +/* +Reimplementation of the PV callbacks in sim65 + +Works the same except +- the file io is high level rather than calling into the base raw open, read,write.. +- we dont need a PVInit +- stdin,stdout and stderr are explicitly dealt with + +*/ +use crate::cpu::Cpu; use core::panic; +use once_cell::sync::Lazy; use std::{ collections::HashMap, fs::{File, OpenOptions}, io::{stderr, stdout, Read, Write}, }; -use once_cell::sync::Lazy; +// map of filenum to rust file handle +// static r/w global - so it needs unsafe code -use crate::cpu::Cpu; static mut PV_FILES: Lazy> = Lazy::new(|| HashMap::new()); - +const PARAVIRT_BASE: u16 = 0xFFF4; static PV_HOOKS: [fn(); 6] = [ ParaVirt::pv_open, ParaVirt::pv_close, @@ -18,10 +29,9 @@ static PV_HOOKS: [fn(); 6] = [ ParaVirt::pv_args, ParaVirt::pv_exit, ]; -const PARAVIRT_BASE: u16 = 0xFFF4; + pub struct ParaVirt; impl ParaVirt { - pub fn _pv_init() {} fn pop_arg(incr: u16) -> u16 { let sp65_addr = Cpu::get_sp65_addr(); let sp65 = Cpu::read_word(sp65_addr as u16); @@ -29,6 +39,7 @@ impl ParaVirt { Cpu::write_word(sp65_addr as u16, sp65 + incr); val } + fn pop() -> u8 { let sp = Cpu::read_sp(); let newsp = sp.wrapping_add(1); @@ -36,27 +47,30 @@ impl ParaVirt { Cpu::write_sp(newsp); val } + fn set_ax(val: u16) { Cpu::write_ac(val as u8); Cpu::write_xr(((val >> 8) & 0xff) as u8); } + fn get_ax() -> u16 { let ac = Cpu::read_ac() as u16; let xr = Cpu::read_xr() as u16; ac | (xr << 8) } + fn pv_open() { - let mut mode = Self::pop_arg(Cpu::read_yr() as u16 - 4); + let mut _mode = Self::pop_arg(Cpu::read_yr() as u16 - 4); let flags = Self::pop_arg(2); let mut name = Self::pop_arg(2); if Cpu::read_yr() - 4 < 2 { /* If the caller didn't supply the mode ** argument, use a reasonable default. */ - mode = 0x01 | 0x02; + _mode = 0x01 | 0x02; } + // mode atually ignored at the moment let mut name_buf = Vec::new(); - loop { let c = Cpu::read_byte(name); if c == 0 { @@ -69,9 +83,7 @@ impl ParaVirt { let mut opt = OpenOptions::new(); match flags & 0x03 { 0x01 => opt.read(true), - 0x02 => opt.write(true), - 0x03 => opt.read(true).write(true), _ => panic!("invalid flags"), }; @@ -98,14 +110,10 @@ impl ParaVirt { } } fn pv_close() { - // Regs->AC = close (Regs->AC); - // Regs->XR = errno; let fd = ParaVirt::pop_arg(2); - let res = unsafe { if let Some(_file) = PV_FILES.get(&fd) { PV_FILES.remove(&fd); - 0 } else { -1 @@ -116,8 +124,8 @@ impl ParaVirt { fn pv_read() { let addr = ParaVirt::pop_arg(2); let fd = ParaVirt::pop_arg(2); - let count = ParaVirt::get_ax(); + let mut buf = vec![0; count as usize]; let res = if fd == 0 { std::io::stdin().read(&mut buf).unwrap() as u16 @@ -172,11 +180,15 @@ impl ParaVirt { let sp65_addr = Cpu::get_sp65_addr() as u16; let mut sp65 = Cpu::read_word(sp65_addr); let argcount = Cpu::get_arg_count() as u16; + // points to array of pointers to argv[n] let mut arg_ptr_storage = sp65 - ((Cpu::get_arg_count() + 1) * 2) as u16; + // store that address of argv table where caller asked for it Cpu::write_word(caller_arg_addr, arg_ptr_storage); + sp65 = arg_ptr_storage; + // copy the host os arguments contents over // sp65 is decremented for each one for i in 0..Cpu::get_arg_count() { diff --git a/src/shell.rs b/src/shell.rs index fe3c33a..e130da0 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -1,12 +1,10 @@ use crate::cpu::Status; use crate::debugger::{Debugger, FrameType::*}; use crate::execute::StopReason; +use crate::syntax; use anyhow::{anyhow, Result}; -use clap::Arg; -use clap::Command; - +use clap::ArgMatches; use rustyline::error::ReadlineError; - use rustyline::DefaultEditor; use std::collections::VecDeque; use std::fs::File; @@ -78,17 +76,16 @@ impl Shell { lastinput = line.clone(); }; match self.dispatch(&line) { - Err(e) => println!("{}", e), - Ok(true) => break, - Ok(false) => {} + Err(e) => println!("{}", e), // display error + Ok(true) => break, // quit was typed + Ok(false) => {} // continue } } Err(ReadlineError::Interrupted) => { - println!("CTRL-C"); - // break; + println!("CTRL-C"); // pass on to running program? } Err(ReadlineError::Eof) => { - println!("CTRL-D"); + println!("quit"); // treat eof as quit break; } Err(err) => { @@ -103,16 +100,18 @@ impl Shell { } fn dispatch(&mut self, line: &str) -> Result { + // split the line up into args let args = shlex::split(line).ok_or(anyhow!("error: Invalid quoting"))?; - let matches = self.syntax().try_get_matches_from(args)?; - //.map_err(|e| e.to_string())?; + // parse with clap + let matches = syntax::syntax().try_get_matches_from(args)?; + // execute the command match matches.subcommand() { Some(("break", args)) => { let addr = args.get_one::("address").unwrap(); self.debugger.set_break(&addr, false)?; } - Some(("list_bp", args)) => { + Some(("list_bp", _)) => { let blist = self.debugger.get_breaks(); for i in 0..blist.len() { let bp = self.debugger.get_bp(blist[i]).unwrap(); @@ -124,20 +123,24 @@ impl Shell { let file = args.get_one::("file").unwrap(); self.debugger.load_ll(Path::new(file))?; } + Some(("load_code", args)) => { let file = args.get_one::("file").unwrap(); self.debugger.load_code(Path::new(file))?; } - Some(("quit", _matches)) => { + + Some(("quit", _)) => { println!("quit"); return Ok(true); } + Some(("memory", args)) => { let addr_str = args.get_one::("address").unwrap(); let addr = self.debugger.convert_addr(&addr_str)?; let chunk = self.debugger.get_chunk(addr, 48)?; self.mem_dump(addr, &chunk); } + Some(("run", args)) => { let cmd_args = args .get_many::("args") @@ -147,23 +150,28 @@ impl Shell { let reason = self.debugger.run(cmd_args)?; self.stop(reason); } - Some(("go", _args)) => { + + Some(("go", _)) => { let reason = self.debugger.go()?; self.stop(reason); } - Some(("next", _args)) => { + + Some(("next", _)) => { let reason = self.debugger.next()?; self.stop(reason); } - Some(("step", _args)) => { + + Some(("step", _)) => { let reason = self.debugger.step()?; self.stop(reason); } + Some(("delete_breakpoint", args)) => { let id = args.get_one::("id"); self.debugger.delete_breakpoint(id)?; } - Some(("back_trace", _args)) => { + + Some(("back_trace", _)) => { let stack = self.debugger.read_stack(); for i in (0..stack.len()).rev() { let frame = &stack[i]; @@ -176,6 +184,7 @@ impl Shell { } } } + Some(("dis", args)) => { let mut addr = if let Some(addr_str) = args.get_one::("address") { self.debugger.convert_addr(&addr_str)? @@ -200,15 +209,45 @@ impl Shell { self.current_dis_addr = addr; } } - + Some(("print", args)) => { + let addr_str = args.get_one::("address").unwrap(); + let addr = self.debugger.convert_addr(&addr_str)?; + self.print(addr, args)?; + } Some((name, _matches)) => unimplemented!("{name}"), None => unreachable!("subcommand required"), } Ok(false) } + + fn print(&self, addr: u16, args: &ArgMatches) -> Result<()> { + if args.contains_id("asstring") { + let mut addr = addr; + loop { + let chunk = self.debugger.get_chunk(addr, 1)?; + if chunk[0] == 0 { + break; + } + print!("{}", chunk[0] as char); + addr += 1; + } + println!(); + } else if args.contains_id("aspointer") { + let chunk = self.debugger.get_chunk(addr, 2)?; + println!("{:02x}{:02x} ", chunk[0], chunk[1]); + } else { + // asint + + let lo = self.debugger.get_chunk(addr, 1)?; + let hi = self.debugger.get_chunk(addr + 1, 1)?; + println!("{} ", lo[0] as u16 | ((hi[0] as u16) << 8)); + } + + Ok(()) + } fn mem_dump(&mut self, mut addr: u16, chunk: &[u8]) { - //let mut addr = 0; + // pretty memory dump let mut line = String::new(); for i in 0..chunk.len() { if i % 16 == 0 { @@ -229,9 +268,9 @@ impl Shell { println!("{}", line); } fn stop(&mut self, reason: StopReason) { + // common handler for when execution is interrupted match reason { StopReason::BreakPoint(bp_addr) => { - //println!("breakpoint"); let bp = self.debugger.get_bp(bp_addr).unwrap(); println!("bp #{} {}", bp.number, bp.symbol); } @@ -239,18 +278,18 @@ impl Shell { println!("exit"); return; } - StopReason::Count | StopReason::Next => { - // println!("count"); - } + StopReason::Count | StopReason::Next => {} StopReason::Bug(_) => { println!("bug {:?}", reason); } } + // disassemble the current instruction let inst_addr = self.debugger.read_pc(); let mem = self.debugger.get_chunk(self.debugger.read_pc(), 3).unwrap(); self.debugger.dis(&mem, inst_addr); - let stat = Status::from_bits_truncate(self.debugger.read_sr()); + // print pc, dissasembled instruction and registers + let stat = Status::from_bits_truncate(self.debugger.read_sr()); println!( "{:04x}: {:<15} A={:02x} X={:02x} Y={:02x} SP={:02x} SR={:?}", self.debugger.read_pc(), @@ -262,111 +301,4 @@ impl Shell { stat ); } - fn syntax(&self) -> Command { - // strip out usage - const PARSER_TEMPLATE: &str = "\ - {all-args} - "; - // strip out name/version - const APPLET_TEMPLATE: &str = "\ - {about-with-newline}\n\ - {usage-heading}\n {usage}\n\ - \n\ - {all-args}{after-help}\ - "; - - Command::new("db65") - .multicall(true) - .arg_required_else_help(true) - .subcommand_required(true) - .subcommand_value_name("Command") - .subcommand_help_heading("Commands") - .help_template(PARSER_TEMPLATE) - .subcommand( - Command::new("break") - .about("set break points") - .alias("b") - .arg(Arg::new("address").required(true)) - .help_template(APPLET_TEMPLATE), - ) - .subcommand( - Command::new("list_bp") - .about("set break points") - .alias("bl") - .help_template(APPLET_TEMPLATE), - ) - .subcommand( - Command::new("symbols") - .alias("ll") - .about("load symbol file") - .arg(Arg::new("file").required(true)) - .arg_required_else_help(true) - .help_template(APPLET_TEMPLATE), - ) - .subcommand( - Command::new("load_code") - .alias("load") - .about("load binary file") - .arg(Arg::new("file").required(true)) - .arg_required_else_help(true) - .help_template(APPLET_TEMPLATE), - ) - .subcommand( - Command::new("run") - .about("run code") - .arg(Arg::new("address")) - .arg(Arg::new("args").last(true).num_args(0..)) - .help_template(APPLET_TEMPLATE), - ) - .subcommand( - Command::new("dis") - .about("disassemble") - .arg(Arg::new("address")) - .help_template(APPLET_TEMPLATE), - ) - .subcommand( - Command::new("quit") - .aliases(["exit", "q"]) - .about("Quit db65") - .help_template(APPLET_TEMPLATE), - ) - .subcommand( - Command::new("next") - .alias("n") - .about("next instruction (step over)") - .help_template(APPLET_TEMPLATE), - ) - .subcommand( - Command::new("go") - .alias("g") - .about("resume execution") - .help_template(APPLET_TEMPLATE), - ) - .subcommand( - Command::new("step") - .alias("s") - .about("next instruction (step into)") - .help_template(APPLET_TEMPLATE), - ) - .subcommand( - Command::new("memory") - .aliases(["mem", "m"]) - .about("display memory") - .arg(Arg::new("address").required(true)) - .help_template(APPLET_TEMPLATE), - ) - .subcommand( - Command::new("back_trace") - .alias("bt") - .about("display call stack") - .help_template(APPLET_TEMPLATE), - ) - .subcommand( - Command::new("delete_breakpoint") - .alias("bd") - .arg(Arg::new("id").required(false)) - .about("delete breakpoint") - .help_template(APPLET_TEMPLATE), - ) - } } diff --git a/src/syntax.rs b/src/syntax.rs new file mode 100644 index 0000000..bc60d92 --- /dev/null +++ b/src/syntax.rs @@ -0,0 +1,124 @@ +use clap::{arg, Arg}; +use clap::{ArgGroup, Command}; + +// Clap sub command syntax defintions +pub fn syntax() -> Command { + // strip out usage + const PARSER_TEMPLATE: &str = "\ + {all-args} + "; + // strip out name/version + const APPLET_TEMPLATE: &str = "\ + {about-with-newline}\n\ + {usage-heading}\n {usage}\n\ + \n\ + {all-args}{after-help}\ + "; + + Command::new("db65") + .multicall(true) + .arg_required_else_help(true) + .subcommand_required(true) + .subcommand_value_name("Command") + .subcommand_help_heading("Commands") + .help_template(PARSER_TEMPLATE) + .subcommand( + Command::new("break") + .about("set break points") + .alias("b") + .arg(Arg::new("address").required(true)) + .help_template(APPLET_TEMPLATE), + ) + .subcommand( + Command::new("list_bp") + .about("set break points") + .alias("bl") + .help_template(APPLET_TEMPLATE), + ) + .subcommand( + Command::new("symbols") + .alias("ll") + .about("load symbol file") + .arg(Arg::new("file").required(true)) + .arg_required_else_help(true) + .help_template(APPLET_TEMPLATE), + ) + .subcommand( + Command::new("load_code") + .alias("load") + .about("load binary file") + .arg(Arg::new("file").required(true)) + .arg_required_else_help(true) + .help_template(APPLET_TEMPLATE), + ) + .subcommand( + Command::new("run") + .about("run code") + .arg(Arg::new("address")) + .arg(Arg::new("args").last(true).num_args(0..)) + .help_template(APPLET_TEMPLATE), + ) + .subcommand( + Command::new("dis") + .about("disassemble") + .arg(Arg::new("address")) + .help_template(APPLET_TEMPLATE), + ) + .subcommand( + Command::new("quit") + .aliases(["exit", "q"]) + .about("Quit db65") + .help_template(APPLET_TEMPLATE), + ) + .subcommand( + Command::new("next") + .alias("n") + .about("next instruction (step over)") + .help_template(APPLET_TEMPLATE), + ) + .subcommand( + Command::new("go") + .alias("g") + .about("resume execution") + .help_template(APPLET_TEMPLATE), + ) + .subcommand( + Command::new("step") + .alias("s") + .about("next instruction (step into)") + .help_template(APPLET_TEMPLATE), + ) + .subcommand( + Command::new("memory") + .aliases(["mem", "m"]) + .about("display memory") + .arg(Arg::new("address").required(true)) + .help_template(APPLET_TEMPLATE), + ) + .subcommand( + Command::new("back_trace") + .alias("bt") + .about("display call stack") + .help_template(APPLET_TEMPLATE), + ) + .subcommand( + Command::new("delete_breakpoint") + .alias("bd") + .arg(Arg::new("id").required(false)) + .about("delete breakpoint") + .help_template(APPLET_TEMPLATE), + ) + .subcommand( + Command::new("print") + .alias("p") + .arg(arg!(
"address of value to print")) + .arg(arg!(asint: -i "int")) + .arg(arg!(aspointer: -p "pointer")) + .arg(arg!(asstring: -s "string")) + .group( + ArgGroup::new("format").args(["asint", "aspointer", "asstring"]), //.required(true), // default to int + ) + .about("delete breakpoint") + .help_template(APPLET_TEMPLATE), + ) +}