diff --git a/src/about.txt b/src/about.txt index 5491dce..bfb08bc 100644 --- a/src/about.txt +++ b/src/about.txt @@ -1,4 +1,5 @@ =topics + symbols,s - information about symbols, names ,uses.. breakpoint,b - information about break and watch points traps,t - information about traps (invalid writes, reads..) @@ -6,6 +7,7 @@ watch,w - watchpoints ccode,c - working with c source code expressions,ex - expression evaluator stop - what happens when code execution is interrupted +misc - general points about commands etc about accepts the long or short topic name ('about s' for symbols) =symbols,s @@ -119,7 +121,14 @@ example ca65 -g -t sim6502 filetest.s -o filetest.o ld65 --dbgfile filetest.dbg -t sim6502 filetest.o -o filetest sim6502.lib - then +or + + cl65 -t sim6502 -g --ld-args --dbgfile --ld-args argtest.sym argtest.c + + note that this does not keep the intermediate .s file. db65 will complain + about that but it does not matter at the moment, no features use the .s files + +then db65 sim6502 debugger 0.2.1 (16024e9) >> load filetest @@ -159,17 +168,31 @@ filetest.c:17 FILE *foo = fopen("foo.txt", "w"); >> p -s buf yo +Note that globals do not have their names as symbols, they have their +'system' name (toolchain limitation). IE + + char buffer[100]; + +is called '_buffer', not 'buffer' + +Static locals are not visible (toolchain limitation) +locals declared within code blocks are not visible (toolchain limitation) + TODO - no way to use locals in expressions - no 'nice' change variable command, write byte has to be used - watch points set on locals will fire when that stack memory is reused =expression,ex -Expressions can be used anywhere an address is required. Expr command -can be used to test an expression and also to inspect values. +Expressions can be used anywhere an address is required. An expression is +signalled by preceding it with an '=' character. + +The 'expr' command can be used to test an expression and also to inspect values. + + Examples: -expr =0x20 evaluates to 0x20 (redundant) + expr =.xr the xr register expr =.xr+1 the xr register plus 1 dis =.pc-6 disassemble from pc-6 @@ -186,6 +209,7 @@ p -s =@(sreg) print a string pointed to by sreg, sreg+1 Note if there are spaces in the expression, you must quote it: mem '=@(ptr + 0x20)' =stop + When execution stops for any reason other than program exit the current code location is displayed, preceded by the reason for the stop. @@ -202,4 +226,15 @@ Commands to resume execution: TODO - execute n statments or instructions - trace, ie execute one line or instruction, report state and then continue -- resume at a different address (doable via changing pc using reg command) \ No newline at end of file +- resume at a different address (doable via changing pc using reg command) + +=misc,m +The command line supports command recall using up and down arrows. +Reverse search via ctrl-r + +Command history is kept from session to session + +When code is running ctrl-c will interrupt it + +Hex strings can be entered as 0xff, 0Xff or $ff. + diff --git a/src/db/debugdb.rs b/src/db/debugdb.rs index 5e3f341..fbb4ed2 100644 --- a/src/db/debugdb.rs +++ b/src/db/debugdb.rs @@ -34,22 +34,41 @@ pub struct SourceFile { } pub struct DebugData { pub conn: Connection, + name: String, pub cc65_dir: Option, } impl DebugData { - pub fn new() -> Result { - let dbfile = "dbg.db"; - if fs::metadata(dbfile).is_ok() { - std::fs::remove_file("dbg.db")?; + pub fn new(name: &str) -> Result { + if fs::metadata(name).is_ok() { + std::fs::remove_file(name)?; } let mut ret = Self { - conn: Connection::open("dbg.db")?, + conn: Connection::open(name)?, cc65_dir: None, + name: name.to_string(), }; ret.create_tables()?; Ok(ret) } + + pub fn clear(&mut self) -> Result<()> { + let tx = self.conn.transaction()?; + tx.execute("delete from symdef", [])?; + tx.execute("delete from symref", [])?; + tx.execute("delete from line", [])?; + tx.execute("delete from file", [])?; + tx.execute("delete from source", [])?; + tx.execute("delete from source_line", [])?; + tx.execute("delete from segment", [])?; + tx.execute("delete from span", [])?; + tx.execute("delete from scope", [])?; + tx.execute("delete from csymbol", [])?; + tx.execute("delete from module", [])?; + tx.commit()?; + + Ok(()) + } fn convert_symbol_type(s: &str) -> SymbolType { match s { "lab" => SymbolType::Label, @@ -224,7 +243,8 @@ impl DebugData { let full_path = if let Some(p) = self.find_file(file)? { p } else { - bail!("can't find file {}", file.display()) + say(&format!("can't find file {}", file.display())); + return Ok(()); }; let fd = File::open(full_path)?; diff --git a/src/debugger/cpu.rs b/src/debugger/cpu.rs index d7273f2..9e3fab2 100644 --- a/src/debugger/cpu.rs +++ b/src/debugger/cpu.rs @@ -36,12 +36,6 @@ static mut THECPU: Cpu = Cpu { memhits: [(false, 0); 8], memhitcount: 0, paracall: false, - tainted_ac: false, - tainted_xr: false, - tainted_yr: false, - tainted_zr: false, - tainted_sr: false, - tainted_sp: false, }; pub enum MemCheck { @@ -61,12 +55,6 @@ pub struct Cpu { memhits: [(bool, u16); 8], // used for data watches memhitcount: u8, // entry count in hit array for this instruction pub paracall: bool, // we just did a pv call - tainted_ac: bool, // the ac is tainted - tainted_xr: bool, // the xr is tainted - tainted_yr: bool, // the yr is tainted - tainted_zr: bool, // the zr is tainted - tainted_sr: bool, // the sr is tainted - tainted_sp: bool, // the sp is tainted } bitflags! { #[derive(Copy, Clone, Default, Debug)] diff --git a/src/debugger/debugger.rs b/src/debugger/debugger.rs index 8a62779..9051998 100644 --- a/src/debugger/debugger.rs +++ b/src/debugger/debugger.rs @@ -7,6 +7,8 @@ the same functionality as the cli shell. use anyhow::{bail, Result}; use evalexpr::Value; + +use std::cell::RefCell; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::{ @@ -22,7 +24,6 @@ use crate::{ debugger::cpu::{Cpu, ShadowFlags}, debugger::execute::StopReason, debugger::loader, - expr::DB65Context, log::say, }; @@ -72,8 +73,8 @@ pub struct Symbol { } type InterceptFunc = fn(&mut Debugger, bool) -> Result>; pub struct Debugger { - pub break_points: HashMap, - pub(crate) watch_points: HashMap, + pub(crate) break_points: BTreeMap, + pub(crate) watch_points: BTreeMap, pub(crate) source_info: BTreeMap, pub(crate) current_file: Option, pub(crate) next_bp: Option, @@ -87,7 +88,6 @@ pub struct Debugger { pub(crate) enable_mem_check: bool, load_name: String, pub(crate) run_done: bool, - pub(crate) expr_context: DB65Context, pub(crate) dbgdb: DebugData, pub(crate) seg_list: Vec, pub(crate) heap_blocks: HashMap, @@ -97,6 +97,7 @@ pub struct Debugger { pub(crate) regbank_addr: Option, pub(crate) regbank_size: Option, pub(crate) ctrlc: Arc, + pub(crate) expr_value: RefCell, } pub struct HeapBlock { @@ -149,7 +150,6 @@ pub struct StackFrame { pub struct BreakPoint { pub(crate) addr: u16, pub(crate) symbol: String, - pub(crate) number: usize, pub(crate) temp: bool, } #[derive(Debug, Clone)] @@ -158,17 +158,18 @@ pub enum WatchType { Write, ReadWrite, } +#[derive(Debug, Clone)] pub struct WatchPoint { pub(crate) addr: u16, pub(crate) symbol: String, - pub(crate) number: usize, pub(crate) watch: WatchType, } impl Debugger { pub fn new() -> Self { + Cpu::reset(); let s = Self { - break_points: HashMap::new(), - watch_points: HashMap::new(), + break_points: BTreeMap::new(), + watch_points: BTreeMap::new(), source_info: BTreeMap::new(), current_file: None, loader_start: 0, @@ -180,8 +181,7 @@ impl Debugger { next_bp: None, load_name: String::new(), run_done: false, - expr_context: DB65Context::new(), - dbgdb: DebugData::new().unwrap(), + dbgdb: DebugData::new(".db65.db").unwrap(), seg_list: Vec::new(), source_mode: SourceDebugMode::None, call_intercepts: HashMap::new(), @@ -192,6 +192,7 @@ impl Debugger { regbank_addr: None, regbank_size: None, ctrlc: Arc::new(AtomicBool::new(false)), + expr_value: RefCell::new(Value::Int(0)), }; let ctrlc = s.ctrlc.clone(); ctrlc::set_handler(move || { @@ -203,13 +204,7 @@ impl Debugger { pub fn delete_breakpoint(&mut self, id_opt: Option<&String>) -> Result<()> { if let Some(id) = id_opt { if let Ok(num) = id.parse::() { - if let Some(find) = self.break_points.iter().find_map(|bp| { - if bp.1.number == num { - Some(*bp.0) - } else { - None - } - }) { + if let Some(find) = self.break_points.iter().map(|e| *e.0).nth(num - 1) { self.break_points.remove(&find); } } @@ -219,6 +214,19 @@ impl Debugger { }; Ok(()) } + pub fn delete_watchpoint(&mut self, id_opt: Option<&String>) -> Result<()> { + if let Some(id) = id_opt { + if let Ok(num) = id.parse::() { + if let Some(find) = self.watch_points.iter().map(|e| *e.0).nth(num - 1) { + self.watch_points.remove(&find); + } + } + // else lookup symbol? + } else { + self.watch_points.clear(); + }; + Ok(()) + } pub fn set_break(&mut self, addr_str: &str, temp: bool) -> Result<()> { let (bp_addr, save_sym) = self.convert_addr(addr_str)?; self.break_points.insert( @@ -226,7 +234,6 @@ impl Debugger { BreakPoint { addr: bp_addr, symbol: save_sym, - number: self.break_points.len() + 1, temp, }, ); @@ -245,7 +252,6 @@ impl Debugger { WatchPoint { addr: wp_addr, symbol: save_sym, - number: self.watch_points.len() + 1, watch: wt, }, ); @@ -316,26 +322,18 @@ impl Debugger { pub fn load_dbg(&mut self, file: &Path) -> Result<()> { let fd = File::open(file)?; let mut reader = BufReader::new(fd); + self.dbgdb.clear()?; self.dbgdb.parse(&mut reader)?; self.dbgdb.load_seg_list(&mut self.seg_list)?; self.dbgdb.load_all_cfiles()?; self.source_info.clear(); self.dbgdb.load_all_source_files(&mut self.source_info)?; - self.dbgdb - .load_expr_symbols(&mut self.expr_context.symbols)?; self.load_intercepts()?; self.init_shadow()?; self.dbgdb.load_files(&mut self.file_table)?; - // load file lines int expr db - for (x, si) in self.source_info.iter() { - if let Some(name) = self.lookup_file_by_id(si.file_id) { - let str = format!("{}:{}", name.short_name, si.line_no); - self.expr_context.symbols.insert(str, Value::Int(*x as i64)); - } - } let regbank = self.dbgdb.get_symbol("zeropage.regbank")?; if regbank.len() != 0 { self.regbank_addr = Some(regbank[0].1); @@ -359,7 +357,17 @@ impl Debugger { let s = self.dbgdb.get_symbols(filter)?; Ok(s) } + fn reset(&mut self) { + self.stack_frames.clear(); + self.heap_blocks.clear(); + self.run_done = false; + self.next_bp = None; + self.source_mode = SourceDebugMode::None; + self.ticks = 0; + Cpu::reset(); + } pub fn load_code(&mut self, file: &Path) -> Result<(u16, u16)> { + self.reset(); let (sp65_addr, run, _cpu, size) = loader::load_code(file)?; // println!("size={:x}, entry={:x}, cpu={}", size, run, cpu); Cpu::sp65_addr(sp65_addr); @@ -380,12 +388,13 @@ impl Debugger { Ok((size, run)) } - pub fn get_breaks(&self) -> Vec { - self.break_points.iter().map(|bp| bp.1.addr).collect() + pub fn get_breaks(&self) -> Result<&BTreeMap> { + Ok(&self.break_points) } - pub fn get_watches(&self) -> Vec { - self.watch_points.iter().map(|wp| wp.1.addr).collect() + pub fn get_watches(&self) -> Result<&BTreeMap> { + Ok(&self.watch_points) } + pub fn go(&mut self) -> Result { if !self.run_done { self.run(vec![]) diff --git a/src/expr.rs b/src/expr.rs index 2afc996..2523df7 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -27,50 +27,31 @@ mem =@(ptr + (0x20*xr)) // more math use crate::{debugger::cpu::Cpu, debugger::debugger::Debugger}; use anyhow::{anyhow, Result}; use evalexpr::{eval_int_with_context, Context, EvalexprResult, Value}; -use std::{collections::HashMap, ops::RangeInclusive}; +use std::ops::RangeInclusive; -pub struct DB65Context { - pub symbols: HashMap, - ac: Value, - xr: Value, - yr: Value, - sp: Value, - sr: Value, - pc: Value, -} - -impl DB65Context { - pub fn new() -> Self { - Self { - symbols: HashMap::new(), - ac: Value::Int(0), - xr: Value::Int(0), - yr: Value::Int(0), - sp: Value::Int(0), - sr: Value::Int(0), - pc: Value::Int(0), - } - } - pub fn reload(&mut self) { - self.ac = Value::Int(Cpu::read_ac() as i64); - self.xr = Value::Int(Cpu::read_xr() as i64); - self.yr = Value::Int(Cpu::read_yr() as i64); - self.sp = Value::Int(Cpu::read_sp() as i64); - self.sr = Value::Int(Cpu::read_sr() as i64); - self.pc = Value::Int(Cpu::read_pc() as i64); - } -} -impl Context for DB65Context { +impl Context for Debugger { fn get_value(&self, key: &str) -> Option<&Value> { - match key { - ".ac" => Some(&self.ac), - ".xr" => Some(&self.xr), - ".yr" => Some(&self.yr), - ".sp" => Some(&self.sp), - ".pc" => Some(&self.pc), - ".sr" => Some(&self.sr), - _ => self.symbols.get(key), - } + let val = match key { + ".ac" => self.read_ac() as i64, + ".xr" => self.read_xr() as i64, + ".yr" => self.read_yr() as i64, + ".sp" => self.read_sp() as i64, + ".pc" => self.read_pc() as i64, + ".sr" => self.read_sr() as i64, + _ => { + let sym = self.convert_addr(key).ok(); + if let Some(s) = sym { + s.0 as i64 + } else { + return None; + } + } + }; + // horrible hack because we have to return + // a calculated value refernce from a read only context + self.expr_value.replace(Value::Int(val)); + let p = self.expr_value.as_ptr(); + Some(unsafe { &*p }) } fn call_function(&self, key: &str, arg: &Value) -> EvalexprResult { match key { @@ -113,9 +94,7 @@ impl Context for DB65Context { } impl Debugger { pub fn evaluate(&mut self, expr: &str) -> Result { - // reload register values - self.expr_context.reload(); - eval_int_with_context(expr, &self.expr_context) + eval_int_with_context(expr, self) .map_err(|e| anyhow!(e)) .map(|v| v as u16) } diff --git a/src/shell.rs b/src/shell.rs index e6f2028..84c1f87 100644 --- a/src/shell.rs +++ b/src/shell.rs @@ -2,7 +2,7 @@ use crate::about::About; use crate::debugger::cpu::Status; use crate::debugger::debugger::{ - CodeLocation, Debugger, FrameType::*, JsrData, SymbolType, WatchType, + BreakPoint, CodeLocation, Debugger, FrameType::*, JsrData, SymbolType, WatchPoint, WatchType, }; use crate::debugger::execute::{BugType, StopReason}; use crate::syntax; @@ -10,13 +10,13 @@ use anyhow::{anyhow, bail, Result}; //use clap::error::ErrorKind; use clap::ArgMatches; + use rustyline::error::ReadlineError; use rustyline::DefaultEditor; use std::collections::VecDeque; use std::fs::File; use std::io::{ErrorKind, Read}; use std::path::{Path, PathBuf}; - /* TODO list @@ -24,7 +24,7 @@ TODO list assembler listing clean up expr handling -realloc +set cc65 root options - dis len - mem len @@ -38,14 +38,14 @@ load bin from command line stack write check write bugcheck for locals clean bt output -#nice load messages + status command dedup symbols -ctrlc -delete watchpint! + error backtrace display on and off bt should show current frame too + */ pub struct Shell { debugger: Debugger, @@ -54,7 +54,7 @@ pub struct Shell { waw: CodeLocation, about: About, } - +static SHELL_HISTORY_FILE: &str = ".db65_history"; impl Shell { pub fn new() -> Self { Self { @@ -68,7 +68,7 @@ impl Shell { pub fn shell(&mut self, file: Option, _args: &[String]) -> Result { let mut rl = DefaultEditor::new()?; - if let Err(e) = rl.load_history("history.txt") { + if let Err(e) = rl.load_history(SHELL_HISTORY_FILE) { if let ReadlineError::Io(ref re) = e { if re.kind() != std::io::ErrorKind::NotFound { println!("cannot open history {:?}", e); @@ -144,7 +144,7 @@ impl Shell { } } - let _ = rl.save_history("history.txt"); + let _ = rl.save_history(SHELL_HISTORY_FILE); Ok(0) } @@ -177,17 +177,23 @@ impl Shell { self.debugger.set_watch(addr, rw)?; } Some(("list_breakpoints", _)) => { - let blist = self.debugger.get_breaks(); - for bp_addr in &blist { - let bp = self.debugger.get_bp(*bp_addr).unwrap(); - println!("#{} 0x{:04X} ({})", bp.number, bp.addr, bp.symbol); + let blist = self.debugger.get_breaks()?; + + for (i, bp) in blist.values().enumerate() { + println!("#{} 0x{:04X} ({})", i + 1, bp.addr, bp.symbol); } } Some(("list_watchpoints", _)) => { - let wlist = self.debugger.get_watches(); - for wp_addr in &wlist { - let wp = self.debugger.get_watch(*wp_addr).unwrap(); - println!("#{} 0x{:04X} ({})", wp.number, wp.addr, wp.symbol); + let wlist = self.debugger.get_watches()?; + + for (i, wp) in wlist.values().enumerate() { + println!( + "#{} 0x{:04X} ({}) {:?}", + i + 1, + wp.addr, + wp.symbol, + wp.watch + ); } } Some(("load_symbols", args)) => { @@ -214,11 +220,10 @@ impl Shell { let file = args.get_one::("file").unwrap(); self.debugger.load_code(Path::new(file))?; } - Some(("load_source", args)) => { - let file = args.get_one::("file").unwrap(); - self.debugger.load_source(Path::new(file))?; - } - + // Some(("load_source", args)) => { + // let file = args.get_one::("file").unwrap(); + // self.debugger.load_source(Path::new(file))?; + // } Some(("quit", _)) => { println!("quit"); return Ok(true); @@ -270,7 +275,10 @@ impl Shell { let id = args.get_one::("id"); self.debugger.delete_breakpoint(id)?; } - + Some(("delete_watchpoint", args)) => { + let id = args.get_one::("id"); + self.debugger.delete_watchpoint(id)?; + } Some(("back_trace", _)) => { let stack = self.debugger.read_stack(); for i in (0..stack.len()).rev() { @@ -541,7 +549,14 @@ impl Shell { match reason { StopReason::BreakPoint(bp_addr) => { let bp = self.debugger.get_bp(bp_addr).unwrap(); - println!("bp #{} {}", bp.number, bp.symbol); + let bnum = self + .debugger + .get_breaks()? + .iter() + .take_while(|b| b.1.addr != bp_addr) + .count() + + 1; + println!("bp #{} {}", bnum, bp.symbol); } StopReason::Exit(_) => { println!("Exit"); @@ -571,7 +586,14 @@ impl Shell { }, StopReason::WatchPoint(addr) => { let wp = self.debugger.get_watch(addr).unwrap(); - println!("Watch #{} 0x{:04x} ({}) ", wp.number, wp.addr, wp.symbol); + let wnum = self + .debugger + .get_watches()? + .iter() + .take_while(|w| w.1.addr != addr) + .count() + + 1; + println!("Watch #{} 0x{:04x} ({}) ", wnum, wp.addr, wp.symbol); } StopReason::Finish => { println!("Finish"); diff --git a/src/syntax.rs b/src/syntax.rs index 3744a1d..21ebf2e 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -155,6 +155,13 @@ pub fn syntax() -> Command { .about("Delete breakpoint") .help_template(APPLET_TEMPLATE), ) + .subcommand( + Command::new("delete_watchpoint") + .visible_alias("dwp") + .arg(Arg::new("id").required(false)) + .about("Delete watchpoint") + .help_template(APPLET_TEMPLATE), + ) .subcommand( Command::new("print") .visible_alias("p")