diff --git a/Cargo.lock b/Cargo.lock index 19dbfa6..1eeaf53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -180,8 +180,7 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "elf" version = "0.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9c86cae88373e32c872967c296dcdd3b25771dcc7da1c4ba060b4a68fd8db7" +source = "git+https://github.com/cole14/rust-elf?rev=a4a747071316b1a900962dd83cbf2ab4ca4665fa#a4a747071316b1a900962dd83cbf2ab4ca4665fa" [[package]] name = "errno" diff --git a/Cargo.toml b/Cargo.toml index c9808b5..fc8db01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [dependencies] bitvec = "1.0.0" derive-try-from-primitive = "1.0.0" -elf = "0.0.12" +elf = { git = "https://github.com/cole14/rust-elf", rev = "a4a747071316b1a900962dd83cbf2ab4ca4665fa" } human-repr = { version = "1.0.1", features = ["iec", "space"] } inventory = "0.3.1" itertools = "0.10.5" diff --git a/src/main.rs b/src/main.rs index b8d1a26..2879938 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ use crate::{ m68k::{BusError, M68K}, }; use disas::DisassemblyError; -use elf::types::Symbol; +use elf::symbol::Symbol; use itertools::Itertools; use parse_int::parse; use reedline_repl_rs::{ @@ -22,7 +22,7 @@ use reedline_repl_rs::{ Error as ReplError, Repl, }; use serde_yaml::Mapping; -use std::{convert::TryFrom, fmt::Display, fs, num::ParseIntError, process}; +use std::{convert::TryFrom, error, fmt::Display, fs, num::ParseIntError, process}; #[derive(Debug)] enum Error { @@ -33,6 +33,13 @@ enum Error { InvalidPeekSize, Disassembly(DisassemblyError), Misc(&'static str), + MiscDyn(Box), +} + +impl From> for Error { + fn from(v: Box) -> Self { + Self::MiscDyn(v) + } } impl From> for Error { @@ -69,6 +76,7 @@ impl Display for Error { Self::InvalidPeekSize => f.write_str("Invalid peek size"), Self::Disassembly(e) => e.fmt(f), Self::Misc(s) => f.write_str(s), + Self::MiscDyn(e) => e.fmt(f), } } } @@ -184,269 +192,282 @@ fn main() -> Result<(), ReplError> { Err(e) => panic!("{}", e), }; } - Repl::<_, Error>::new(EmuState {cpu: M68K::new(backplane), symbols: Vec::new()}) - .with_name("68KEmu") - .with_version("0.1.0") - .with_banner("68K Backplane Computer Emulator") - .with_description("68K Backplane Computer Emulator") - .with_command( - Command::new("card") - .trailing_var_arg(true) - .arg( - Arg::new("num") - .required(true) - .help("The card number to send the command to"), - ) - .arg( - Arg::new("args") - .required(true) - .multiple_values(true) - .takes_value(true), - ) - .about("Send a command to a card"), - |args, state| { - let num = args.get_one::("num").unwrap().parse::()?; - state.cpu.bus_mut() - .cards_mut() - .get_mut(num as usize) - .ok_or(Error::InvalidCard(num))? - .cmd( - &args - .get_many::("args") - .unwrap() - .map(String::as_str) - .collect_vec(), - ); - Ok(None) - }, - ) - .with_command( - Command::new("ls").about("List the cards in the system"), - |_, state| { - #[allow(unstable_name_collisions)] - Ok(Some( - state.cpu.bus_mut() - .cards() - .iter() - .enumerate() - .map(|(i, card)| format!("Card {i}: {card}")) - .intersperse('\n'.to_string()) - .collect(), - )) - }, - ) - .with_command( - Command::new("regs").about("Show CPU registers"), - |_, state| Ok(Some(format!("{}", state.cpu))), - ) - .with_command( - Command::new("step") - .arg( - Arg::new("count") - .takes_value(true) - .help("Count of instructions to step by. Defaults to 1"), - ) - .arg( - Arg::new("print_ins") - .long("print_ins") - .short('i') - .action(ArgAction::SetTrue) - .help("Print instructions") - ) - .arg( - Arg::new("print_regs") - .long("print_regs") - .short('r') - .action(ArgAction::SetTrue) - .help("Print ending registers") - ) - .about("Step the CPU"), - |args, state| { - let count = - parse::(args.get_one::("count").map_or("1", String::as_str))?; - let mut out = String::new(); - for _ in 0..count { - if state.cpu.stopped { - out += &format!("CPU stopped at PC {:#x}\n", state.cpu.pc()); - break; - } - if args.get_flag("print_ins") { - let pc = state.cpu.pc(); - out += &disas_fmt(&mut state.cpu, pc).0; - } - state.cpu.step(); + Repl::<_, Error>::new(EmuState { + cpu: M68K::new(backplane), + symbols: Vec::new(), + }) + .with_name("68KEmu") + .with_version("0.1.0") + .with_banner("68K Backplane Computer Emulator") + .with_description("68K Backplane Computer Emulator") + .with_command( + Command::new("card") + .trailing_var_arg(true) + .arg( + Arg::new("num") + .required(true) + .help("The card number to send the command to"), + ) + .arg( + Arg::new("args") + .required(true) + .multiple_values(true) + .takes_value(true), + ) + .about("Send a command to a card"), + |args, state| { + let num = args.get_one::("num").unwrap().parse::()?; + state + .cpu + .bus_mut() + .cards_mut() + .get_mut(num as usize) + .ok_or(Error::InvalidCard(num))? + .cmd( + &args + .get_many::("args") + .unwrap() + .map(String::as_str) + .collect_vec(), + ); + Ok(None) + }, + ) + .with_command( + Command::new("ls").about("List the cards in the system"), + |_, state| { + #[allow(unstable_name_collisions)] + Ok(Some( + state + .cpu + .bus_mut() + .cards() + .iter() + .enumerate() + .map(|(i, card)| format!("Card {i}: {card}")) + .intersperse('\n'.to_string()) + .collect(), + )) + }, + ) + .with_command( + Command::new("regs").about("Show CPU registers"), + |_, state| Ok(Some(format!("{}", state.cpu))), + ) + .with_command( + Command::new("step") + .arg( + Arg::new("count") + .takes_value(true) + .help("Count of instructions to step by. Defaults to 1"), + ) + .arg( + Arg::new("print_ins") + .long("print_ins") + .short('i') + .action(ArgAction::SetTrue) + .help("Print instructions"), + ) + .arg( + Arg::new("print_regs") + .long("print_regs") + .short('r') + .action(ArgAction::SetTrue) + .help("Print ending registers"), + ) + .about("Step the CPU"), + |args, state| { + let count = parse::(args.get_one::("count").map_or("1", String::as_str))?; + let mut out = String::new(); + for _ in 0..count { + if state.cpu.stopped { + out += &format!("CPU stopped at PC {:#x}\n", state.cpu.pc()); + break; } - if args.get_flag("print_regs") { - out += &format!("{}\n", state.cpu); - } - if out.is_empty() { - Ok(None) - } else { - out.pop(); // Remove trailing newline - Ok(Some(out)) - } - }, - ) - .with_command( - Command::new("run") - .arg( - Arg::new("stop_addr") - .takes_value(true) - .help("Optional address to stop execution at. Works as a breakpoint only for this run") - ) - .arg( - Arg::new("print_ins") - .long("print_ins") - .short('p') - .action(ArgAction::SetTrue) - .help("Print all executed instructions") - ) - .about("Run the CPU"), - |args, state| { - let mut out = String::new(); - while !state.cpu.stopped { - let stop_addr = args - .get_one::("stop_addr") - .map(|s| parse::(s)) - .transpose()?; - if stop_addr.map(|a| state.cpu.pc() == a).unwrap_or(false) { - break; - } - if args.get_flag("print_ins") { - let pc = state.cpu.pc(); - out += &disas_fmt(&mut state.cpu, pc).0; - } - state.cpu.step(); + if args.get_flag("print_ins") { + let pc = state.cpu.pc(); + out += &disas_fmt(&mut state.cpu, pc).0; } + state.cpu.step(); + } + if args.get_flag("print_regs") { out += &format!("{}\n", state.cpu); - let pc = state.cpu.pc(); - out += &disas_fmt(&mut state.cpu, pc).0; - out.pop(); // Remove trailing newline - Ok(Some(out)) - }, - ) - .with_command( - Command::new("reset").about("Reset the cards and CPU, in that order"), - |_, state| { - for card in state.cpu.bus_mut().cards_mut() { - card.reset(); - } - state.cpu.reset(); + } + if out.is_empty() { Ok(None) - }, - ) - .with_command( - Command::new("peek") - .arg(Arg::new("count").short('c').takes_value(true)) - .arg(Arg::new("fmt").short('f').required(true).takes_value(true)) - .arg(Arg::new("addr").required(true)) - .about("Peek a memory address"), - |args, state| { - let fmt_str = args.get_one::("fmt").unwrap(); - if fmt_str.len() != 2 { - return Err(Error::Misc("Peek format length must be 2")); - } - let fmt = PeekFormat::try_from(fmt_str.chars().next().unwrap())?; - let size = PeekSize::try_from(fmt_str.chars().nth(1).unwrap())?; - let count = - parse::(args.get_one::("count").map_or("1", String::as_str))?; - let addr = parse::(args.get_one::("addr").unwrap())?; - - let mut data = Vec::new(); - let bus = state.cpu.bus_mut(); - for i in 0..count { - match size { - PeekSize::Byte => data.push(bus.read_byte(addr + i)? as u32), - PeekSize::Word => data.push(bus.read_word(addr + (i * 2))? as u32), - PeekSize::LongWord => data.push( - (bus.read_word(addr + (i * 4))? as u32) << 16 - | (bus.read_word(addr + (i * 4) + 2)? as u32), - ), - } - } - #[allow(unstable_name_collisions)] - Ok(Some( - data.chunks(size.chunk_size()) - .enumerate() - .map(|(i, c)| { - format!( - "0x{:x}: ", - addr + (size.chunk_size() * size.byte_count() * i) as u32 - ) + &c - .iter() - .map(|d| fmt.format(*d, size)) - .intersperse(" ".to_string()) - .collect::() - }) - .intersperse("\n".to_string()) - .collect::(), - )) - }, - ) - .with_command( - Command::new("disas") - .arg( - Arg::new("addr") - .help("Address to start disassembly at. Defaults to current PC"), - ) - .arg( - Arg::new("count") - .short('c') - .takes_value(true) - .help("Count of instructions to disassemble. Defaults to 1"), - ) - .about("Disassemble a region of memory"), - |args, state| { - let mut addr = args - .get_one::("addr") - .map_or(Ok(state.cpu.pc()), |s| parse::(s))?; - let count = - parse::(args.get_one::("count").map_or("1", String::as_str))?; - let mut out = String::new(); - for _ in 0..count { - let (fmt, res) = disas_fmt(&mut state.cpu, addr); - out += &fmt; - match res { - Ok(new_addr) => { - addr = new_addr; - } - Err(_) => { - break; - } - } - } + } else { out.pop(); // Remove trailing newline Ok(Some(out)) - }, - ) - .with_command( - Command::new("sym") - .arg( - Arg::new("file") + } + }, + ) + .with_command( + Command::new("run") + .arg(Arg::new("stop_addr").takes_value(true).help( + "Optional address to stop execution at. Works as a breakpoint only for this run", + )) + .arg( + Arg::new("print_ins") + .long("print_ins") + .short('p') + .action(ArgAction::SetTrue) + .help("Print all executed instructions"), + ) + .about("Run the CPU"), + |args, state| { + let mut out = String::new(); + while !state.cpu.stopped { + let stop_addr = args.get_one::("stop_addr"); + + let stop_addr = stop_addr + .map(|s| { + parse::(s).or_else(|_| { + state + .symbols + .iter() + .find(|sym| &sym.name == s) + .map(|sym| sym.value as u32) + .ok_or(Error::Misc("No such symbol")) + }) + }) + .transpose()?; + if stop_addr.map(|a| state.cpu.pc() == a).unwrap_or(false) { + break; + } + if args.get_flag("print_ins") { + let pc = state.cpu.pc(); + out += &disas_fmt(&mut state.cpu, pc).0; + } + state.cpu.step(); + } + out += &format!("{}\n", state.cpu); + let pc = state.cpu.pc(); + out += &disas_fmt(&mut state.cpu, pc).0; + out.pop(); // Remove trailing newline + Ok(Some(out)) + }, + ) + .with_command( + Command::new("reset").about("Reset the cards and CPU, in that order"), + |_, state| { + for card in state.cpu.bus_mut().cards_mut() { + card.reset(); + } + state.cpu.reset(); + Ok(None) + }, + ) + .with_command( + Command::new("peek") + .arg(Arg::new("count").short('c').takes_value(true)) + .arg(Arg::new("fmt").short('f').required(true).takes_value(true)) + .arg(Arg::new("addr").required(true)) + .about("Peek a memory address"), + |args, state| { + let fmt_str = args.get_one::("fmt").unwrap(); + if fmt_str.len() != 2 { + return Err(Error::Misc("Peek format length must be 2")); + } + let fmt = PeekFormat::try_from(fmt_str.chars().next().unwrap())?; + let size = PeekSize::try_from(fmt_str.chars().nth(1).unwrap())?; + let count = parse::(args.get_one::("count").map_or("1", String::as_str))?; + let addr = parse::(args.get_one::("addr").unwrap())?; + + let mut data = Vec::new(); + let bus = state.cpu.bus_mut(); + for i in 0..count { + match size { + PeekSize::Byte => data.push(bus.read_byte(addr + i)? as u32), + PeekSize::Word => data.push(bus.read_word(addr + (i * 2))? as u32), + PeekSize::LongWord => data.push( + (bus.read_word(addr + (i * 4))? as u32) << 16 + | (bus.read_word(addr + (i * 4) + 2)? as u32), + ), + } + } + #[allow(unstable_name_collisions)] + Ok(Some( + data.chunks(size.chunk_size()) + .enumerate() + .map(|(i, c)| { + format!( + "0x{:x}: ", + addr + (size.chunk_size() * size.byte_count() * i) as u32 + ) + &c + .iter() + .map(|d| fmt.format(*d, size)) + .intersperse(" ".to_string()) + .collect::() + }) + .intersperse("\n".to_string()) + .collect::(), + )) + }, + ) + .with_command( + Command::new("disas") + .arg(Arg::new("addr").help("Address to start disassembly at. Defaults to current PC")) + .arg( + Arg::new("count") + .short('c') + .takes_value(true) + .help("Count of instructions to disassemble. Defaults to 1"), + ) + .about("Disassemble a region of memory"), + |args, state| { + let mut addr = args + .get_one::("addr") + .map_or(Ok(state.cpu.pc()), |s| parse::(s))?; + let count = parse::(args.get_one::("count").map_or("1", String::as_str))?; + let mut out = String::new(); + for _ in 0..count { + let (fmt, res) = disas_fmt(&mut state.cpu, addr); + out += &fmt; + match res { + Ok(new_addr) => { + addr = new_addr; + } + Err(_) => { + break; + } + } + } + out.pop(); // Remove trailing newline + Ok(Some(out)) + }, + ) + .with_command( + Command::new("sym") + .arg( + Arg::new("file") .required(true) .help("The ELF file to load symbols from"), - ) - .about("Load symbols from an ELF file"), - |args, state| { - let file = args - .get_one::("file").unwrap(); - let file = elf::File::open_path(file).unwrap(); - let symtab = file.get_section(".symtab").ok_or(Error::Misc("Could not find symbol table section"))?; - let symbols = file.get_symbols(symtab).unwrap(); - state.symbols = symbols; - Ok(None) - }, - ) - .with_command(Command::new("quit") + ) + .about("Load symbols from an ELF file"), + |args, state| { + let file = args.get_one::("file").unwrap(); + let file = elf::File::open_path(file).map_err(>::from)?; + let symtab = file + .get_section(".symtab") + .ok_or(Error::Misc("Could not find symbol table section"))?; + let symbols = file + .get_symbols(&symtab) + .map_err(>::from)?; + state.symbols = symbols; + Ok(None) + }, + ) + .with_command( + Command::new("quit") .visible_alias("q") .visible_alias("exit") .about("Quit"), - |_, _| process::exit(0) - ) - // Visible aliases don't actually work, so fake it with hidden subcommands - .with_command(Command::new("q").hide(true), |_, _| process::exit(0)) - .with_command(Command::new("exit").hide(true), |_, _| process::exit(0)) - .run() + |_, _| process::exit(0), + ) + // Visible aliases don't actually work, so fake it with hidden subcommands + .with_command(Command::new("q").hide(true), |_, _| process::exit(0)) + .with_command(Command::new("exit").hide(true), |_, _| process::exit(0)) + .run() } fn disas_fmt(cpu: &mut M68K, addr: u32) -> (String, Result>) {