diff --git a/Cargo.lock b/Cargo.lock index 49a8b90e..a260f9fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,11 +28,20 @@ name = "addr2line" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "addr2line" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ca9b76e919fd83ccfb509f51b28c333c0e03f2221616e347a129215cec4e4a9" dependencies = [ "cpp_demangle", "fallible-iterator", "gimli", - "object 0.27.1", + "object", "rustc-demangle", "smallvec 1.10.0", ] @@ -95,12 +104,12 @@ version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" dependencies = [ - "addr2line", + "addr2line 0.17.0", "cc", "cfg-if", "libc", "miniz_oxide", - "object 0.29.0", + "object", "rustc-demangle", ] @@ -212,7 +221,7 @@ version = "2.0.0-dev" dependencies = [ "cargo_metadata", "cargo_toml", - "clap 4.0.8", + "clap", "env_logger", "espflash", "log", @@ -275,31 +284,14 @@ checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" [[package]] name = "clap" -version = "3.2.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" -dependencies = [ - "atty", - "bitflags", - "clap_derive 3.2.18", - "clap_lex 0.2.4", - "indexmap", - "once_cell", - "strsim", - "termcolor", - "textwrap", -] - -[[package]] -name = "clap" -version = "4.0.8" +version = "4.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5840cd9093aabeabf7fd932754c435b7674520fc3ddc935c397837050f0f1e4b" +checksum = "30607dd93c420c6f1f80b544be522a0238a7db35e6a12968d28910983fee0df0" dependencies = [ "atty", "bitflags", - "clap_derive 4.0.8", - "clap_lex 0.3.0", + "clap_derive", + "clap_lex", "once_cell", "strsim", "termcolor", @@ -307,22 +299,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_derive" -version = "4.0.8" +version = "4.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92289ffc6fb4a85d85c246ddb874c05a87a2e540fb6ad52f7ca07c8c1e1840b1" +checksum = "a4a307492e1a34939f79d3b6b9650bd2b971513cd775436bf2b78defeb5af00b" dependencies = [ "heck", "proc-macro-error", @@ -331,15 +310,6 @@ dependencies = [ "syn", ] -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] - [[package]] name = "clap_lex" version = "0.3.0" @@ -355,7 +325,7 @@ version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85914173c2f558d61613bfbbf1911f14e630895087a7ed2fafc0f5319e1536e7" dependencies = [ - "crossterm 0.25.0", + "crossterm", "strum", "strum_macros", "unicode-width", @@ -402,22 +372,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossterm" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2102ea4f781910f8a5b98dd061f4c2023f479ce7bb1236330099ceb5a93cf17" -dependencies = [ - "bitflags", - "crossterm_winapi", - "libc", - "mio", - "parking_lot", - "signal-hook", - "signal-hook-mio", - "winapi", -] - [[package]] name = "crossterm" version = "0.25.0" @@ -641,22 +595,24 @@ dependencies = [ name = "espflash" version = "2.0.0-dev" dependencies = [ + "addr2line 0.18.0", "base64", "binread", "bytemuck", - "clap 4.0.8", + "clap", "comfy-table", - "crossterm 0.25.0", + "crossterm", "dialoguer", "directories-next", "env_logger", "esp-idf-part", - "espmonitor", "flate2", "indicatif", + "lazy_static", "log", "miette", "parse_int", + "regex", "rppal", "serde", "serde-hex", @@ -671,23 +627,6 @@ dependencies = [ "xmas-elf", ] -[[package]] -name = "espmonitor" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d8108f9eddda1613b8c558a3bf0a363187e743f9b4825613dd924b9f422e6d" -dependencies = [ - "addr2line", - "clap 3.2.22", - "crossterm 0.23.2", - "gimli", - "lazy_static", - "nix", - "object 0.27.1", - "regex", - "serial", -] - [[package]] name = "fallible-iterator" version = "0.2.0" @@ -762,7 +701,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" dependencies = [ "fallible-iterator", - "indexmap", "stable_deref_trait", ] @@ -812,16 +750,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "indexmap" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" -dependencies = [ - "autocfg", - "hashbrown", -] - [[package]] name = "indicatif" version = "0.17.1" @@ -842,15 +770,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "ioctl-rs" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7970510895cee30b3e9128319f2cefd4bde883a39f38baa279567ba3a7eb97d" -dependencies = [ - "libc", -] - [[package]] name = "is_ci" version = "1.1.1" @@ -965,15 +884,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - [[package]] name = "miette" version = "5.3.0" @@ -1035,7 +945,6 @@ dependencies = [ "bitflags", "cfg-if", "libc", - "memoffset", ] [[package]] @@ -1059,22 +968,13 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" -[[package]] -name = "object" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" -dependencies = [ - "flate2", - "memchr", -] - [[package]] name = "object" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" dependencies = [ + "flate2", "memchr", ] @@ -1382,48 +1282,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serial" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1237a96570fc377c13baa1b88c7589ab66edced652e43ffb17088f003db3e86" -dependencies = [ - "serial-core", - "serial-unix", - "serial-windows", -] - -[[package]] -name = "serial-core" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f46209b345401737ae2125fe5b19a77acce90cd53e1658cda928e4fe9a64581" -dependencies = [ - "libc", -] - -[[package]] -name = "serial-unix" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f03fbca4c9d866e24a459cbca71283f545a37f8e3e002ad8c70593871453cab7" -dependencies = [ - "ioctl-rs", - "libc", - "serial-core", - "termios", -] - -[[package]] -name = "serial-windows" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15c6d3b776267a75d31bbdfd5d36c0ca051251caafc285827052bc53bcdc8162" -dependencies = [ - "libc", - "serial-core", -] - [[package]] name = "serialport" version = "4.2.0" @@ -1627,15 +1485,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "termios" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a" -dependencies = [ - "libc", -] - [[package]] name = "textwrap" version = "0.15.1" diff --git a/espflash/Cargo.toml b/espflash/Cargo.toml index 4c0f95cd..480bee70 100644 --- a/espflash/Cargo.toml +++ b/espflash/Cargo.toml @@ -34,6 +34,7 @@ path = "./src/bin/espflash.rs" required-features = ["cli"] [dependencies] +addr2line = "0.18.0" base64 = "0.13.0" binread = "2.2.0" bytemuck = { version = "1.12.1", features = ["derive"] } @@ -43,13 +44,14 @@ crossterm = { version = "0.25.0", optional = true } dialoguer = { version = "0.10.2", optional = true } directories-next = "2.0.0" esp-idf-part = "0.1.0" -espmonitor = "0.10.0" env_logger = "0.9.1" flate2 = "1.0.24" indicatif = "0.17.1" +lazy_static = "1.4.0" log = "0.4.17" miette = { version = "5.3.0", features = ["fancy"] } parse_int = "0.6.0" +regex = "1.6.0" rppal = { version = "0.13.1", optional = true } serde = { version = "1.0.145", features = ["derive"] } serde-hex = "0.1.0" diff --git a/espflash/src/cli/monitor.rs b/espflash/src/cli/monitor.rs deleted file mode 100644 index 684cc003..00000000 --- a/espflash/src/cli/monitor.rs +++ /dev/null @@ -1,151 +0,0 @@ -use std::{ - io::{stdout, ErrorKind}, - time::Duration, -}; - -use crossterm::{ - event::{poll, read, Event, KeyCode, KeyEvent, KeyModifiers}, - terminal::{disable_raw_mode, enable_raw_mode}, -}; -use espmonitor::{handle_serial, load_bin_context, SerialState}; -use miette::{IntoDiagnostic, Result}; - -use crate::{connection::reset_after_flash, interface::Interface}; - -/// Converts key events from crossterm into appropriate character/escape -/// sequences which are then sent over the serial connection. -/// -/// Adapted from https://github.com/dhylands/serial-monitor -fn handle_key_event(key_event: KeyEvent) -> Option> { - // The following escape sequences come from the MicroPython codebase. - // - // Up ESC [A - // Down ESC [B - // Right ESC [C - // Left ESC [D - // Home ESC [H or ESC [1~ - // End ESC [F or ESC [4~ - // Del ESC [3~ - // Insert ESC [2~ - - let mut buf = [0; 4]; - - let key_str: Option<&[u8]> = match key_event.code { - KeyCode::Backspace => Some(b"\x08"), - KeyCode::Enter => Some(b"\r"), - KeyCode::Left => Some(b"\x1b[D"), - KeyCode::Right => Some(b"\x1b[C"), - KeyCode::Home => Some(b"\x1b[H"), - KeyCode::End => Some(b"\x1b[F"), - KeyCode::Up => Some(b"\x1b[A"), - KeyCode::Down => Some(b"\x1b[B"), - KeyCode::Tab => Some(b"\x09"), - KeyCode::Delete => Some(b"\x1b[3~"), - KeyCode::Insert => Some(b"\x1b[2~"), - KeyCode::Esc => Some(b"\x1b"), - KeyCode::Char(ch) => { - if key_event.modifiers & KeyModifiers::CONTROL == KeyModifiers::CONTROL { - buf[0] = ch as u8; - if ('a'..='z').contains(&ch) || (ch == ' ') { - buf[0] &= 0x1f; - Some(&buf[0..1]) - } else if ('4'..='7').contains(&ch) { - // crossterm returns Control-4 thru 7 for \x1c thru \x1f - buf[0] = (buf[0] + 8) & 0x1f; - Some(&buf[0..1]) - } else { - Some(ch.encode_utf8(&mut buf).as_bytes()) - } - } else { - Some(ch.encode_utf8(&mut buf).as_bytes()) - } - } - _ => None, - }; - key_str.map(|slice| slice.into()) -} - -struct RawModeGuard; - -impl RawModeGuard { - pub fn new() -> Result { - enable_raw_mode().into_diagnostic()?; - Ok(RawModeGuard) - } -} - -impl Drop for RawModeGuard { - fn drop(&mut self) { - if let Err(e) = disable_raw_mode() { - eprintln!("{:#}", e) - } - } -} - -pub fn monitor( - mut serial: Interface, - elf: Option<&[u8]>, - pid: u16, - baud: u32, -) -> serialport::Result<()> { - println!("Commands:"); - println!(" CTRL+R Reset chip"); - println!(" CTRL+C Exit"); - println!(); - - // Explicitly set the baud rate when starting the serial monitor, to allow using - // different rates for flashing. - serial.serial_port_mut().set_baud_rate(baud)?; - serial - .serial_port_mut() - .set_timeout(Duration::from_millis(5))?; - - let _raw_mode = RawModeGuard::new(); - - let stdout = stdout(); - let mut stdout = stdout.lock(); - let mut serial_state; - if let Some(elf) = elf { - let symbols = load_bin_context(elf).ok(); - serial_state = SerialState::new(symbols); - } else { - serial_state = SerialState::new(None); - reset_after_flash(&mut serial, pid)?; - } - - let mut buff = [0; 1024]; - loop { - let read_count = match serial.serial_port_mut().read(&mut buff) { - Ok(count) => Ok(count), - Err(e) if e.kind() == ErrorKind::TimedOut => Ok(0), - Err(e) if e.kind() == ErrorKind::Interrupted => continue, - err => err, - }?; - - if read_count > 0 { - handle_serial(&mut serial_state, &buff[0..read_count], &mut stdout)?; - } - - if poll(Duration::from_secs(0))? { - if let Event::Key(key) = read()? { - if key.modifiers.contains(KeyModifiers::CONTROL) { - match key.code { - KeyCode::Char('c') => break, - KeyCode::Char('r') => { - reset_after_flash(&mut serial, pid)?; - continue; - } - _ => {} - } - } - - if let Some(bytes) = handle_key_event(key) { - serial.serial_port_mut().write_all(&bytes)?; - serial.serial_port_mut().flush()?; - } - } - } - } - - Ok(()) -} diff --git a/espflash/src/cli/monitor/line_endings.rs b/espflash/src/cli/monitor/line_endings.rs new file mode 100644 index 00000000..5d48410e --- /dev/null +++ b/espflash/src/cli/monitor/line_endings.rs @@ -0,0 +1,60 @@ +// Adapted from: https://github.com/derekdreery/normalize-line-endings + +struct Normalized { + iter: I, + prev_was_cr: bool, + peeked: Option, +} + +impl Iterator for Normalized +where + I: Iterator, +{ + type Item = u8; + + fn next(&mut self) -> Option { + if let Some(peeked) = self.peeked.take() { + return Some(peeked); + } + + match self.iter.next() { + Some(b'\n') if !self.prev_was_cr => { + self.peeked = Some(b'\n'); + self.prev_was_cr = false; + Some(b'\r') + } + Some(b'\r') => { + self.prev_was_cr = true; + Some(b'\r') + } + any => { + self.prev_was_cr = false; + any + } + } + } +} + +pub(crate) fn normalized(iter: impl Iterator) -> impl Iterator { + Normalized { + iter, + prev_was_cr: false, + peeked: None, + } +} + +#[cfg(test)] +mod tests { + use std::iter::FromIterator; + + use super::*; + + #[test] + fn test_normalized() { + let input = b"This is a string \n with \n some \n\r\n random newlines\r\n\n"; + assert_eq!( + &Vec::from_iter(normalized(input.iter().copied())), + b"This is a string \r\n with \r\n some \r\n\r\n random newlines\r\n\r\n" + ); + } +} diff --git a/espflash/src/cli/monitor/mod.rs b/espflash/src/cli/monitor/mod.rs new file mode 100644 index 00000000..b08755eb --- /dev/null +++ b/espflash/src/cli/monitor/mod.rs @@ -0,0 +1,263 @@ +use std::{ + io::{stdout, ErrorKind, Write}, + time::Duration, +}; + +use crossterm::{ + event::{poll, read, Event, KeyCode, KeyEvent, KeyModifiers}, + style::{Color, Print, PrintStyledContent, Stylize}, + terminal::{disable_raw_mode, enable_raw_mode}, + QueueableCommand, +}; +use lazy_static::lazy_static; +use miette::{IntoDiagnostic, Result}; +use regex::Regex; + +use self::{line_endings::normalized, symbols::Symbols}; +use crate::{connection::reset_after_flash, interface::Interface}; + +mod line_endings; +mod symbols; + +// Pattern to much a function address in serial output. +lazy_static! { + static ref RE_FN_ADDR: Regex = Regex::new(r"0x[[:xdigit:]]{8}").unwrap(); +} + +#[derive(Default)] +struct SerialContext<'ctx> { + symbols: Option>, + previous_frag: Option, + previous_line: Option, +} + +impl<'ctx> SerialContext<'ctx> { + fn new(symbols: Option>) -> Self { + Self { + symbols, + ..Self::default() + } + } +} + +struct RawModeGuard; + +impl RawModeGuard { + pub fn new() -> Result { + enable_raw_mode().into_diagnostic()?; + Ok(RawModeGuard) + } +} + +impl Drop for RawModeGuard { + fn drop(&mut self) { + if let Err(e) = disable_raw_mode() { + eprintln!("{:#}", e) + } + } +} + +pub fn monitor( + mut serial: Interface, + elf: Option<&[u8]>, + pid: u16, + baud: u32, +) -> serialport::Result<()> { + println!("Commands:"); + println!(" CTRL+R Reset chip"); + println!(" CTRL+C Exit"); + println!(); + + // Explicitly set the baud rate when starting the serial monitor, to allow using + // different rates for flashing. + serial.serial_port_mut().set_baud_rate(baud)?; + serial + .serial_port_mut() + .set_timeout(Duration::from_millis(5))?; + + // Load symbols from the ELF file (if provided) and initialize the context. + let symbols = if let Some(bytes) = elf { + Symbols::try_from(bytes).ok() + } else { + None + }; + let mut ctx = SerialContext::new(symbols); + + // We are in raw mode until `_raw_mode` is dropped (ie. this function returns). + let _raw_mode = RawModeGuard::new(); + + let stdout = stdout(); + let mut stdout = stdout.lock(); + + let mut buff = [0; 1024]; + loop { + let read_count = match serial.serial_port_mut().read(&mut buff) { + Ok(count) => Ok(count), + Err(e) if e.kind() == ErrorKind::TimedOut => Ok(0), + Err(e) if e.kind() == ErrorKind::Interrupted => continue, + err => err, + }?; + + if read_count > 0 { + handle_serial(&mut ctx, &buff[0..read_count], &mut stdout); + } + + if poll(Duration::from_secs(0))? { + if let Event::Key(key) = read()? { + if key.modifiers.contains(KeyModifiers::CONTROL) { + match key.code { + KeyCode::Char('c') => break, + KeyCode::Char('r') => { + reset_after_flash(&mut serial, pid)?; + continue; + } + _ => {} + } + } + + if let Some(bytes) = handle_key_event(key) { + serial.serial_port_mut().write_all(&bytes)?; + serial.serial_port_mut().flush()?; + } + } + } + } + + Ok(()) +} + +fn handle_serial(ctx: &mut SerialContext, buff: &[u8], out: &mut dyn Write) { + let text: Vec = normalized(buff.iter().copied()).collect(); + let text = String::from_utf8_lossy(&text).to_string(); + + // Split the text into lines, storing the last of which separately if it is + // incomplete (ie. does not end with '\n') because these need special handling. + let mut lines = text.lines().collect::>(); + let incomplete = if text.ends_with('\n') { + None + } else { + lines.pop() + }; + + // Iterate through all *complete* lines (ie. those ending with '\n') ... + for line in lines { + // ... and print the line. + out.queue(Print(line)).ok(); + + // If there is a previous line fragment, that means that the current line must + // be appended to it in order to form the complete line. Since we want to look + // for function addresses in the *entire* previous line we combine these prior + // to performing the symbol lookup(s). + ctx.previous_line = if let Some(frag) = &ctx.previous_frag { + Some(format!("{frag}{line}")) + } else { + Some(line.to_string()) + }; + + // The previous fragment has been completed (by this current line). + ctx.previous_frag = None; + + // If we have loaded some symbols... + if let Some(symbols) = &ctx.symbols { + // And there was previously a line printed to the terminal... + if let Some(line) = &ctx.previous_line { + // Check the previous line for function addresses. For each address found, + // attempt to look up the associated function's name and location and write both + // to the terminal. + for matched in RE_FN_ADDR.find_iter(line).map(|m| m.as_str()) { + // Since our regular expression already confirms that this is a correctly + // formatted hex literal, we can (fairly) safely assume that it will parse + // successfully into an integer. + let addr = parse_int::parse::(matched).unwrap(); + + let name = symbols.get_name(addr).unwrap_or_else(|| "??".into()); + let (file, line_num) = + if let Some((file, line_num)) = symbols.get_location(addr) { + (file, line_num.to_string()) + } else { + ("??".into(), "??".into()) + }; + + out.queue(PrintStyledContent( + format!("\r\n{matched} - {name}\r\n at {file}:{line_num}") + .with(Color::Yellow), + )) + .unwrap(); + } + } + } + + // Remember to begin a new line after we have printed this one! + out.write_all(b"\r\n").ok(); + } + + // If there is an incomplete line we will still print it. However, we will not + // perform function name lookups or terminate it with a newline. + if let Some(line) = incomplete { + out.queue(Print(line)).ok(); + + if let Some(frag) = &ctx.previous_frag { + ctx.previous_frag = Some(format!("{frag}{line}")); + } else { + ctx.previous_frag = Some(line.to_string()); + } + } + + // Don't forget to flush the writer! + out.flush().ok(); +} + +// Converts key events from crossterm into appropriate character/escape +// sequences which are then sent over the serial connection. +// +// Adapted from: https://github.com/dhylands/serial-monitor +fn handle_key_event(key_event: KeyEvent) -> Option> { + // The following escape sequences come from the MicroPython codebase. + // + // Up ESC [A + // Down ESC [B + // Right ESC [C + // Left ESC [D + // Home ESC [H or ESC [1~ + // End ESC [F or ESC [4~ + // Del ESC [3~ + // Insert ESC [2~ + + let mut buf = [0; 4]; + + let key_str: Option<&[u8]> = match key_event.code { + KeyCode::Backspace => Some(b"\x08"), + KeyCode::Enter => Some(b"\r"), + KeyCode::Left => Some(b"\x1b[D"), + KeyCode::Right => Some(b"\x1b[C"), + KeyCode::Home => Some(b"\x1b[H"), + KeyCode::End => Some(b"\x1b[F"), + KeyCode::Up => Some(b"\x1b[A"), + KeyCode::Down => Some(b"\x1b[B"), + KeyCode::Tab => Some(b"\x09"), + KeyCode::Delete => Some(b"\x1b[3~"), + KeyCode::Insert => Some(b"\x1b[2~"), + KeyCode::Esc => Some(b"\x1b"), + KeyCode::Char(ch) => { + if key_event.modifiers & KeyModifiers::CONTROL == KeyModifiers::CONTROL { + buf[0] = ch as u8; + + if ('a'..='z').contains(&ch) || (ch == ' ') { + buf[0] &= 0x1f; + Some(&buf[0..1]) + } else if ('4'..='7').contains(&ch) { + // crossterm returns Control-4 thru 7 for \x1c thru \x1f + buf[0] = (buf[0] + 8) & 0x1f; + Some(&buf[0..1]) + } else { + Some(ch.encode_utf8(&mut buf).as_bytes()) + } + } else { + Some(ch.encode_utf8(&mut buf).as_bytes()) + } + } + _ => None, + }; + + key_str.map(|slice| slice.into()) +} diff --git a/espflash/src/cli/monitor/symbols.rs b/espflash/src/cli/monitor/symbols.rs new file mode 100644 index 00000000..f5a54edf --- /dev/null +++ b/espflash/src/cli/monitor/symbols.rs @@ -0,0 +1,60 @@ +use std::error::Error; + +use addr2line::{ + gimli::{EndianRcSlice, RunTimeEndian}, + object::{read::File, Object}, + Context, +}; + +pub(crate) struct Symbols<'sym> { + file: File<'sym, &'sym [u8]>, + ctx: Context>, +} + +impl<'sym> Symbols<'sym> { + pub fn try_from(bytes: &'sym [u8]) -> Result> { + let file = File::parse(bytes)?; + let ctx = Context::new(&file)?; + + Ok(Self { file, ctx }) + } + + pub fn get_name(&self, addr: u64) -> Option { + // The basic steps here are: + // 1. find which frame `addr` is in + // 2. look up and demangle the function name + // 3. if no function name is found, try to look it up in the object file + // directly + // 4. return a demangled function name, if one was found + self.ctx + .find_frames(addr) + .ok() + .and_then(|mut frames| { + frames.next().ok().flatten().and_then(|frame| { + frame + .function + .and_then(|name| name.demangle().map(|s| s.into_owned()).ok()) + }) + }) + .or_else(|| { + self.file + .symbol_map() + .get(addr) + .map(|sym| sym.name().to_string()) + }) + } + + pub fn get_location(&self, addr: u64) -> Option<(String, u32)> { + // Find the location which `addr` is in. If we can dedetermine a file name and + // line number for this function we will return them both in a tuple. + self.ctx.find_location(addr).ok()?.map(|location| { + let file = location.file.map(|f| f.to_string()); + let line = location.line; + + match (file, line) { + (Some(file), Some(line)) => Some((file, line)), + _ => None, + } + })? + } +}