diff options
author | Ben Bridle <bridle.benjamin@gmail.com> | 2024-10-29 14:03:04 +1300 |
---|---|---|
committer | Ben Bridle <bridle.benjamin@gmail.com> | 2024-10-30 15:42:10 +1300 |
commit | 748974ef2c0e969e95cccc9cb061436d5a1d1b35 (patch) | |
tree | c9d2022669df04cdfd3d775497048222422c59c2 | |
parent | c42e2154d88c23a28f83fe96f4153e821ef00c0e (diff) | |
download | bedrock-pc-748974ef2c0e969e95cccc9cb061436d5a1d1b35.zip |
Load and display symbols in debug information
The assembler saves out symbols files, which are loaded automatically
by the emulator when present. The name and location of the most recent
label is displayed with the debug information when symbols are loaded.
-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()), } } |