diff options
-rw-r--r-- | Cargo.lock | 4 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/bin/br.rs | 85 | ||||
-rw-r--r-- | src/debug.rs | 79 | ||||
-rw-r--r-- | src/emulators.rs | 4 | ||||
-rw-r--r-- | src/emulators/graphical_emulator.rs | 2 | ||||
-rw-r--r-- | src/emulators/headless_emulator.rs | 2 |
7 files changed, 143 insertions, 35 deletions
@@ -62,8 +62,8 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bedrock-asm" -version = "4.0.0" -source = "git+git://benbridle.com/bedrock_asm?tag=v4.0.0#db2c08220e95729c1880953d00aaded693dc43c7" +version = "4.0.1" +source = "git+git://benbridle.com/bedrock_asm?tag=v4.0.1#2cd0c86659479774d092de727e0f0c31e27e49f2" dependencies = [ "vagabond", "xflags", @@ -6,7 +6,7 @@ edition = "2021" description = "Emulator for running Bedrock programs" [dependencies] -bedrock-asm = { git = "git://benbridle.com/bedrock_asm", tag = "v4.0.0" } +bedrock-asm = { git = "git://benbridle.com/bedrock_asm", tag = "v4.0.1" } bedrock-core = { git = "git://benbridle.com/bedrock_core", tag = "v5.0.0" } phosphor = { git = "git://benbridle.com/phosphor", tag = "v3.1.0" } geometry = { git = "git://benbridle.com/geometry", tag = "v1.0.0" } diff --git a/src/bin/br.rs b/src/bin/br.rs index bf3befe..e96df54 100644 --- a/src/bin/br.rs +++ b/src/bin/br.rs @@ -43,13 +43,18 @@ fn main_run(args: Run) { unsafe { VERBOSE = true; } } - let bytecode = load_bytecode(args.program.as_ref().map(|p| p.as_path())); + let program_path = args.program.as_ref().map(|p| p.as_path()); + let Bytecode { bytes: bytecode, path } = load_bytecode(program_path); + let symbols_path = path.as_ref().map(|p| { + let mut path = p.to_path_buf(); + path.set_extension("br.sym"); + path + }); + let metadata = parse_metadata(&bytecode); if metadata.is_none() { verbose!("Could not read program metadata"); } - // TODO: Load and parse symbols file into debug state, use nearest symbol - // path when debugging. let mut config = EmulatorConfig { dimensions: ScreenDimensions::ZERO, @@ -60,6 +65,7 @@ fn main_run(args: Run) { initial_transmission: None, decode_stdin: args.decode_stdin, encode_stdout: args.encode_stdout, + symbols_path, }; let phosphor = Phosphor::new(); @@ -106,14 +112,18 @@ fn main_run(args: Run) { std::process::exit(0); } -fn load_bytecode(path: Option<&Path>) -> Vec<u8> { +fn load_bytecode(path: Option<&Path>) -> Bytecode { // TODO: Etch file location into bytecode. if let Some(path) = path { if let Ok(bytecode) = load_bytecode_from_file(path) { - verbose!("Loaded program from {path:?} ({} bytes)", bytecode.len()); + let length = bytecode.bytes.len(); + let path = bytecode.path(); + verbose!("Loaded program from {path:?} ({length} bytes)"); return bytecode; - } else if let Some((bytecode, path)) = load_bytecode_from_bedrock_path(path) { - verbose!("Loaded program from {path:?} ({} bytes)", bytecode.len()); + } else if let Some(bytecode) = load_bytecode_from_bedrock_path(path) { + let length = bytecode.bytes.len(); + let path = bytecode.path(); + verbose!("Loaded program from {path:?} ({length} bytes)"); return bytecode; } else { eprintln!("Could not read program from {path:?}, exiting"); @@ -122,7 +132,8 @@ fn load_bytecode(path: Option<&Path>) -> Vec<u8> { } else { verbose!("Reading program from standard input..."); if let Ok(bytecode) = load_bytecode_from_stdin() { - verbose!("Loaded program from standard input ({} bytes)", bytecode.len()); + let length = bytecode.bytes.len(); + verbose!("Loaded program from standard input ({length} bytes)"); return bytecode; } else { eprintln!("Could not read program from standard input, exiting"); @@ -131,13 +142,8 @@ fn load_bytecode(path: Option<&Path>) -> Vec<u8> { } } -/// Attempt to load bytecode from a file path. -fn load_bytecode_from_file(path: &Path) -> Result<Vec<u8>, std::io::Error> { - load_bytecode_from_readable_source(std::fs::File::open(path)?) -} - /// Attempt to load bytecode from a directory in the BEDROCK_PATH environment variable. -fn load_bytecode_from_bedrock_path(path: &Path) -> Option<(Vec<u8>, PathBuf)> { +fn load_bytecode_from_bedrock_path(path: &Path) -> Option<Bytecode> { if path.is_relative() && path.components().count() == 1 { for base_path in std::env::var("BEDROCK_PATH").ok()?.split(':') { let mut base_path = PathBuf::from(base_path); @@ -145,29 +151,48 @@ fn load_bytecode_from_bedrock_path(path: &Path) -> Option<(Vec<u8>, PathBuf)> { base_path.push(path); verbose!("Attempting to load program from {base_path:?}"); if let Ok(bytecode) = load_bytecode_from_file(&base_path) { - return Some((bytecode, base_path)); + return Some(bytecode); } if path.extension().is_some() { continue; } base_path.set_extension("br"); verbose!("Attempting to load program from {base_path:?}"); if let Ok(bytecode) = load_bytecode_from_file(&base_path) { - return Some((bytecode, base_path)); + return Some(bytecode); } } } return None; } +/// Attempt to load bytecode from a file path. +fn load_bytecode_from_file(path: &Path) -> Result<Bytecode, std::io::Error> { + load_bytecode_from_readable_source(std::fs::File::open(path)?, Some(path)) +} + /// Attempt to load bytecode from standard input. -fn load_bytecode_from_stdin() -> Result<Vec<u8>, std::io::Error> { - load_bytecode_from_readable_source(std::io::stdin()) +fn load_bytecode_from_stdin() -> Result<Bytecode, std::io::Error> { + load_bytecode_from_readable_source(std::io::stdin(), None) } /// Attempt to load bytecode from a source that implements std::io::Read. -fn load_bytecode_from_readable_source(source: impl Read) -> Result<Vec<u8>, std::io::Error> { - let mut bytecode = Vec::<u8>::new(); - source.take(65536).read_to_end(&mut bytecode)?; - return Ok(bytecode); +fn load_bytecode_from_readable_source(source: impl Read, path: Option<&Path>) -> Result<Bytecode, std::io::Error> { + let mut bytes = Vec::<u8>::new(); + source.take(65536).read_to_end(&mut bytes)?; + return Ok(Bytecode { bytes, path: path.map(|p| p.to_path_buf()) }); +} + +struct Bytecode { + bytes: Vec<u8>, + path: Option<PathBuf>, +} + +impl Bytecode { + fn path(&self) -> String { + match &self.path { + Some(path) => path.as_os_str().to_string_lossy().to_string(), + None => String::from("<unknown>"), + } + } } @@ -248,7 +273,19 @@ fn main_asm(args: Asm) { // ----------------------------------------------------------------------- // GENERATE symbols file and bytecode let bytecode = generate_bytecode(&mut semantic_tokens); - // let symbols = generate_symbols_file(&semantic_tokens); + + if args.symbols && !args.check { + if let Some(path) = &args.output { + let mut symbols_path = path.to_path_buf(); + symbols_path.set_extension("br.sym"); + let symbols = generate_symbols_file(&semantic_tokens); + if let Err(err) = std::fs::write(&symbols_path, symbols) { + verbose!("Could not write to symbols path {symbols_path:?}: ({err:?})."); + } else { + verbose!("Saved debug symbols to {symbols_path:?}"); + } + } + } let length = bytecode.len(); let percentage = (length as f32 / 65536.0 * 100.0).round() as u16; @@ -361,6 +398,8 @@ xflags::xflags! { optional --check /// Only return resolved source code. optional --resolve + /// Generate debug symbols with file extension '.br.sym' + optional --symbols } } } diff --git a/src/debug.rs b/src/debug.rs index d19dbec..1593d9d 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,23 +1,28 @@ use bedrock_core::*; +use std::path::Path; use std::time::Instant; -macro_rules! yellow { () => { eprint!("\x1b[33m") };} -macro_rules! normal { () => { eprint!("\x1b[0m" ) };} + +const NORMAL: &str = "\x1b[0m"; +const YELLOW: &str = "\x1b[33m"; +const BLUE: &str = "\x1b[34m"; pub struct DebugState { pub enabled: bool, last_cycle: usize, last_mark: Instant, + symbols: DebugSymbols, } impl DebugState { - pub fn new(enabled: bool) -> Self { + pub fn new<P: AsRef<Path>>(enabled: bool, symbols_path: Option<P>) -> Self { Self { enabled, last_cycle: 0, last_mark: Instant::now(), + symbols: DebugSymbols::from_path_opt(symbols_path), } } @@ -37,7 +42,12 @@ impl DebugState { debug_stack(&core.wst, 0x10); eprint!("RST: "); debug_stack(&core.rst, 0x10); - + // Print information about the current symbol. + if let Some(symbol) = self.symbols.for_address(core.mem.pc) { + eprint!("SYM: {BLUE}@{}{NORMAL}", symbol.name); + if let Some(location) = &symbol.location { eprint!(" {location}"); } + eprintln!(); + } self.last_cycle = core.cycle; self.last_mark = Instant::now(); } @@ -46,9 +56,64 @@ impl DebugState { fn debug_stack(stack: &Stack, len: usize) { for i in 0..len { - if i == stack.sp as usize { yellow!(); } + if i == stack.sp as usize { eprint!("{YELLOW}"); } eprint!("{:02x} ", stack.mem[i]); } - normal!(); - eprintln!(); + eprintln!("{NORMAL}"); +} + + +struct DebugSymbols { + symbols: Vec<DebugSymbol> +} + +impl DebugSymbols { + /// Load debug symbols from a symbols file. + pub fn from_path_opt<P: AsRef<Path>>(path: Option<P>) -> Self { + let mut symbols = Vec::new(); + if let Some(path) = path { + if let Ok(string) = std::fs::read_to_string(path) { + for line in string.lines() { + if let Some(symbol) = DebugSymbol::from_line(line) { + symbols.push(symbol); + } + } + } + } + symbols.sort_by_key(|s| s.address); + Self { symbols } + } + + pub fn for_address(&self, address: u16) -> Option<&DebugSymbol> { + if self.symbols.is_empty() { return None; } + let symbol = match self.symbols.binary_search_by_key(&address, |s| s.address) { + Ok(index) => self.symbols.get(index)?, + Err(index) => self.symbols.get(index.checked_sub(1)?)?, + }; + Some(&symbol) + } +} + +struct DebugSymbol { + address: u16, + name: String, + location: Option<String>, +} + +impl DebugSymbol { + pub fn from_line(line: &str) -> Option<Self> { + if let Some((address, line)) = line.split_once(' ') { + let address = u16::from_str_radix(address, 16).ok()?; + if let Some((name, location)) = line.split_once(' ') { + let name = name.to_string(); + let location = Some(location.to_string()); + Some( DebugSymbol { address, name, location } ) + } else { + let name = line.to_string(); + Some( DebugSymbol { address, name, location: None } ) + } + } else { + None + } + } } diff --git a/src/emulators.rs b/src/emulators.rs index fcebe1d..56f7181 100644 --- a/src/emulators.rs +++ b/src/emulators.rs @@ -8,6 +8,8 @@ use crate::*; use phosphor::Colour; +use std::path::PathBuf; + pub enum EmulatorSignal { Promote, @@ -25,4 +27,6 @@ pub struct EmulatorConfig { pub initial_transmission: Option<Vec<u8>>, pub decode_stdin: bool, pub encode_stdout: bool, + + pub symbols_path: Option<PathBuf>, } diff --git a/src/emulators/graphical_emulator.rs b/src/emulators/graphical_emulator.rs index a183262..bc6aaeb 100644 --- a/src/emulators/graphical_emulator.rs +++ b/src/emulators/graphical_emulator.rs @@ -115,7 +115,7 @@ impl GraphicalEmulator { let devices = GraphicalDeviceBus::new(config); Self { br: BedrockEmulator::new(devices), - debug: DebugState::new(debug), + debug: DebugState::new(debug, config.symbols_path.as_ref()), dimensions: config.dimensions, fullscreen: config.fullscreen, scale: config.scale, diff --git a/src/emulators/headless_emulator.rs b/src/emulators/headless_emulator.rs index 418ea9c..3f54f6a 100644 --- a/src/emulators/headless_emulator.rs +++ b/src/emulators/headless_emulator.rs @@ -89,7 +89,7 @@ impl HeadlessEmulator { pub fn new(config: &EmulatorConfig, debug: bool) -> Self { Self { br: BedrockEmulator::new(HeadlessDeviceBus::new(config)), - debug: DebugState::new(debug), + debug: DebugState::new(debug, config.symbols_path.as_ref()), } } |