use std::{
io::{self, ErrorKind, Read, Write, stdout},
time::{Duration, Instant},
};
use crossterm::{
event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, poll, read},
terminal::{disable_raw_mode, enable_raw_mode},
};
use external_processors::ExternalProcessors;
use log::{debug, error, warn};
use miette::{IntoDiagnostic, Result};
#[cfg(feature = "serialport")]
use serialport::SerialPort;
use strum::{Display, EnumIter, EnumString, VariantNames};
use crate::{
cli::{
MonitorConfigArgs,
monitor::parser::{InputParser, ResolvingPrinter},
},
connection::{Port, reset::reset_after_flash},
image_format::Metadata,
};
pub mod external_processors;
pub mod parser;
mod line_endings;
mod stack_dump;
mod symbols;
#[cfg_attr(feature = "cli", derive(clap::ValueEnum))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, EnumIter, EnumString, VariantNames)]
#[non_exhaustive]
#[strum(serialize_all = "lowercase")]
pub enum LogFormat {
Defmt,
Serial,
}
struct RawModeGuard;
impl RawModeGuard {
pub fn new() -> Result<Self> {
enable_raw_mode().into_diagnostic()?;
Ok(RawModeGuard)
}
}
impl Drop for RawModeGuard {
fn drop(&mut self) {
if let Err(e) = disable_raw_mode() {
error!("Failed to disable raw_mode: {e:#}")
}
}
}
pub fn monitor(
mut serial: Port,
elfs: Vec<&[u8]>,
pid: u16,
monitor_args: MonitorConfigArgs,
non_interactive: bool,
) -> miette::Result<()> {
if !non_interactive {
println!("Commands:");
println!(" CTRL+R Reset chip");
println!(" CTRL+C Exit");
println!();
} else if !monitor_args.no_reset {
reset_after_flash(&mut serial, pid).into_diagnostic()?;
}
let baud = monitor_args.monitor_baud;
debug!("Opening serial monitor with baudrate: {baud}");
serial.set_baud_rate(baud).into_diagnostic()?;
serial
.set_timeout(Duration::from_millis(5))
.into_diagnostic()?;
let _raw_mode = RawModeGuard::new();
let firmware_elf = elfs.first().map(|v| &**v);
let stdout = stdout();
let mut stdout = if monitor_args.no_addresses {
ResolvingPrinter::new_no_addresses(firmware_elf, stdout.lock())
} else {
ResolvingPrinter::new(elfs, stdout.lock(), monitor_args.all_addresses)
};
let mut parser: Box<dyn InputParser> = match monitor_args
.log_format
.unwrap_or_else(|| deduce_log_format(firmware_elf))
{
LogFormat::Defmt => Box::new(parser::esp_defmt::EspDefmt::new(
firmware_elf,
monitor_args.output_format,
)?),
LogFormat::Serial => {
if monitor_args.output_format.is_some() {
warn!("Output format specified but log format is serial. Ignoring output format.");
}
Box::new(parser::serial::Serial)
}
};
let mut external_processors =
ExternalProcessors::new(monitor_args.processors, monitor_args.elf)?;
let mut buff = [0; 1024];
let mut user_input_handler = InputHandler::new(pid, non_interactive);
loop {
let read_count = match serial.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.into_diagnostic(),
}?;
let processed = external_processors.process(&buff[0..read_count]);
parser.feed(&processed, &mut stdout);
stdout.flush().ok();
if !user_input_handler.handle(&mut serial)? {
break;
}
}
Ok(())
}
struct InputHandler {
pid: u16,
non_interactive: bool,
flush_deadline: Option<Instant>,
}
impl InputHandler {
fn new(pid: u16, non_interactive: bool) -> Self {
Self {
pid,
non_interactive,
flush_deadline: None,
}
}
fn flush_if_needed(&mut self, serial: &mut Port) -> Result<()> {
let Some(deadline) = self.flush_deadline else {
return Ok(());
};
if deadline <= Instant::now() {
self.flush_deadline = None;
#[cfg(target_os = "linux")]
let _timer = linux::arm_timeout_workaround(Duration::from_millis(100));
#[cfg(target_os = "macos")]
let _timer = macos::arm_timeout_workaround(Duration::from_millis(100));
serial.flush().ignore_timeout().into_diagnostic()?;
}
Ok(())
}
fn handle(&mut self, serial: &mut Port) -> Result<bool> {
let key = match key_event().into_diagnostic() {
Ok(Some(event)) => event,
Ok(None) => {
self.flush_if_needed(serial)?;
return Ok(true);
}
Err(_) if self.non_interactive => return Ok(true),
Err(err) => return Err(err),
};
if key.kind == KeyEventKind::Press {
if key.modifiers.contains(KeyModifiers::CONTROL) {
match key.code {
KeyCode::Char('c') => return Ok(false),
KeyCode::Char('r') => {
reset_after_flash(serial, self.pid).into_diagnostic()?;
return Ok(true);
}
_ => {}
}
}
self.flush_if_needed(serial)?;
if let Some(bytes) = handle_key_event(key) {
serial
.write_all(&bytes)
.ignore_timeout()
.into_diagnostic()?;
if self.flush_deadline.is_none() {
self.flush_deadline = Some(Instant::now() + Duration::from_millis(50));
}
}
}
Ok(true)
}
}
#[cfg(target_os = "linux")]
mod linux {
use std::time::Duration;
use nix::{
sys::{
signal::{self, SaFlags, SigAction, SigEvent, SigHandler, SigSet, SigevNotify, Signal},
timer::{Expiration, Timer, TimerSetTimeFlags},
},
time::ClockId,
};
pub struct Workaround {
_timer: Timer,
previous_handler: SigHandler,
}
const SIGNAL: Signal = Signal::SIGALRM;
impl Drop for Workaround {
fn drop(&mut self) {
unsafe { signal::signal(SIGNAL, self.previous_handler) }.unwrap();
}
}
pub fn arm_timeout_workaround(timeout: Duration) -> Workaround {
extern "C" fn handle_signal(_signal: libc::c_int) {}
let handler = SigHandler::Handler(handle_signal);
unsafe {
signal::sigaction(
SIGNAL,
&SigAction::new(handler, SaFlags::empty(), SigSet::all()),
)
.unwrap()
};
let previous_handler = unsafe { signal::signal(SIGNAL, handler) }.unwrap();
let mut timer = Timer::new(
ClockId::CLOCK_MONOTONIC,
SigEvent::new(SigevNotify::SigevSignal {
signal: SIGNAL,
si_value: 0,
}),
)
.unwrap();
let expiration = Expiration::OneShot(timeout.into());
let flags = TimerSetTimeFlags::empty();
timer.set(expiration, flags).expect("could not set timer");
Workaround {
_timer: timer,
previous_handler,
}
}
}
#[cfg(target_os = "macos")]
mod macos {
use std::time::Duration;
use libc::{self, ITIMER_REAL, SIGALRM, c_int, itimerval, sigaction, sigemptyset, timeval};
pub struct Workaround {
previous_action: sigaction,
previous_timer: itimerval,
}
impl Drop for Workaround {
fn drop(&mut self) {
unsafe {
libc::sigaction(SIGALRM, &self.previous_action, std::ptr::null_mut());
libc::setitimer(ITIMER_REAL, &self.previous_timer, std::ptr::null_mut());
}
}
}
pub fn arm_timeout_workaround(timeout: Duration) -> Workaround {
unsafe extern "C" fn handle_signal(_signal: c_int) {}
unsafe {
let mut new_action: sigaction = std::mem::zeroed();
sigemptyset(&mut new_action.sa_mask);
new_action.sa_flags = 0;
new_action.sa_sigaction = handle_signal as *const () as usize;
let mut old_action: sigaction = std::mem::zeroed();
libc::sigaction(SIGALRM, &new_action, &mut old_action);
let timeout_tv = duration_to_timeval(timeout);
let new_timer = itimerval {
it_interval: timeval {
tv_sec: 0,
tv_usec: 0,
},
it_value: timeout_tv,
};
let mut old_timer: itimerval = std::mem::zeroed();
libc::setitimer(ITIMER_REAL, &new_timer, &mut old_timer);
Workaround {
previous_action: old_action,
previous_timer: old_timer,
}
}
}
fn duration_to_timeval(d: Duration) -> timeval {
timeval {
tv_sec: d.as_secs() as libc::time_t,
tv_usec: d.subsec_micros() as libc::suseconds_t,
}
}
}
trait ErrorExt {
fn ignore_timeout(self) -> Self;
}
impl ErrorExt for Result<(), io::Error> {
fn ignore_timeout(self) -> Self {
match self {
Ok(_) => Ok(()),
Err(e) if e.kind() == ErrorKind::TimedOut => Ok(()),
Err(e) => Err(e),
}
}
}
fn key_event() -> std::io::Result<Option<KeyEvent>> {
if !poll(Duration::ZERO)? {
return Ok(None);
}
match read()? {
Event::Key(key) => Ok(Some(key)),
_ => Ok(None),
}
}
fn deduce_log_format(elf: Option<&[u8]>) -> LogFormat {
let metadata = Metadata::from_bytes(elf);
let Some(log_format) = metadata.log_format() else {
return LogFormat::Serial;
};
match log_format {
"defmt-espflash" => LogFormat::Defmt,
"serial" => LogFormat::Serial,
other => {
warn!("Unknown log format symbol: {other}. Defaulting to serial.");
LogFormat::Serial
}
}
}
fn handle_key_event(key_event: KeyEvent) -> Option<Vec<u8>> {
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 ch.is_ascii_lowercase() || (ch == ' ') {
buf[0] &= 0x1f;
Some(&buf[0..1])
} else if ('4'..='7').contains(&ch) {
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())
}
pub fn check_monitor_args(
monitor: &bool,
monitor_args: &MonitorConfigArgs,
non_interactive: bool,
) -> Result<()> {
if !monitor
&& (monitor_args.elf.is_some()
|| monitor_args.log_format.is_some()
|| monitor_args.output_format.is_some()
|| monitor_args.processors.is_some()
|| non_interactive
|| monitor_args.no_reset
|| monitor_args.no_addresses
|| monitor_args.all_addresses
|| monitor_args.monitor_baud != 115_200)
{
warn!(
"Monitor options were provided, but `--monitor/-M` flag isn't set. These options will be ignored."
);
}
if !non_interactive && monitor_args.no_reset {
warn!(
"The `--no-reset` flag only applies when using the `--non-interactive` flag. Ignoring it."
);
}
if monitor_args.no_addresses && monitor_args.all_addresses {
log::warn!(
"Using `--no-addresses` disables address resolution, making `--all-addresses` ineffective. Consider using only one of these flags."
);
}
if let Some(LogFormat::Serial) = monitor_args.log_format
&& monitor_args.output_format.is_some()
{
warn!(
"Output format specified but log format is serial. The output format option will be ignored."
);
}
if let Some(LogFormat::Defmt) = monitor_args.log_format
&& monitor_args.elf.is_none()
{
warn!(
"Log format `defmt` requires an ELF file. Please provide one with the `--elf` option."
);
}
Ok(())
}