use std::{borrow::Cow, io::Write, sync::LazyLock};
use crossterm::{
QueueableCommand,
style::{Color, Print, PrintStyledContent, Stylize},
};
use regex::Regex;
use crate::cli::monitor::{line_endings::normalized, stack_dump, symbols::Symbols};
pub mod esp_defmt;
pub mod serial;
pub trait InputParser {
fn feed(&mut self, bytes: &[u8], out: &mut dyn Write);
}
static RE_FN_ADDR: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"0[xX][[:xdigit:]]{8}").unwrap());
const SUPPRESS_FOR_LINE_START: &[&str] = &[
"Saved PC:", "load:0x",
"entry 0x",
];
fn resolve_addresses(
symbols: &Symbols<'_>,
line: &str,
out: &mut dyn Write,
try_resolve_all_addresses: bool,
) -> std::io::Result<()> {
if !try_resolve_all_addresses && SUPPRESS_FOR_LINE_START.iter().any(|s| line.starts_with(s)) {
return Ok(());
}
for matched in RE_FN_ADDR.find_iter(line).map(|m| m.as_str()) {
let addr = u64::from_str_radix(&matched[2..], 16).unwrap();
let name = symbols.name(addr);
let location = symbols.location(addr);
if let Some(name) = name {
let output = if line.trim() == format!("0x{addr:x}") {
if let Some((file, line_num)) = location {
format!("{name}\r\n at {file}:{line_num}\r\n")
} else {
format!("{name}\r\n at ??:??\r\n")
}
} else if let Some((file, line_num)) = location {
format!("{matched} - {name}\r\n at {file}:{line_num}\r\n")
} else {
format!("{matched} - {name}\r\n at ??:??\r\n")
};
out.queue(PrintStyledContent(output.with(Color::Yellow)))?;
}
}
Ok(())
}
#[derive(Debug)]
struct Utf8Merger {
incomplete_utf8_buffer: Vec<u8>,
}
impl Utf8Merger {
fn new() -> Self {
Self {
incomplete_utf8_buffer: Vec::new(),
}
}
fn process_utf8(&mut self, buff: &[u8]) -> String {
let mut buffer = std::mem::take(&mut self.incomplete_utf8_buffer);
buffer.extend(normalized(buff.iter().copied()));
let mut len = 0;
loop {
match std::str::from_utf8(&buffer[len..]) {
Ok(str) if len == 0 => return String::from(str),
Ok(_) => return String::from_utf8_lossy(&buffer).to_string(),
Err(e) => {
len += e.valid_up_to();
if let Some(error_len) = e.error_len() {
len += error_len;
} else {
let (bytes, incomplete) = buffer.split_at(len);
self.incomplete_utf8_buffer = incomplete.to_vec();
return String::from_utf8_lossy(bytes).to_string();
}
}
}
}
}
}
#[allow(missing_debug_implementations)]
pub struct ResolvingPrinter<'ctx, W: Write> {
writer: W,
symbols: Vec<Symbols<'ctx>>,
elfs: Vec<&'ctx [u8]>,
merger: Utf8Merger,
line_fragment: String,
disable_address_resolution: bool,
try_resolve_all_addresses: bool,
}
impl<'ctx, W: Write> ResolvingPrinter<'ctx, W> {
pub fn new(elf: Vec<&'ctx [u8]>, writer: W, try_resolve_all_addresses: bool) -> Self {
Self {
writer,
symbols: elf
.iter()
.filter_map(|elf| Symbols::try_from(elf).ok())
.collect(),
elfs: elf,
merger: Utf8Merger::new(),
line_fragment: String::new(),
disable_address_resolution: false,
try_resolve_all_addresses,
}
}
pub fn new_no_addresses(_elf: Option<&'ctx [u8]>, writer: W) -> Self {
Self {
writer,
symbols: Vec::new(), elfs: Vec::new(),
merger: Utf8Merger::new(),
line_fragment: String::new(),
disable_address_resolution: true,
try_resolve_all_addresses: false,
}
}
}
impl<W: Write> Write for ResolvingPrinter<'_, W> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let text = self.merger.process_utf8(buf);
let mut lines = text.lines().collect::<Vec<_>>();
let incomplete = if text.ends_with('\n') {
None
} else {
lines.pop()
};
for line in lines {
self.writer.queue(Print(line))?;
let fragment = std::mem::take(&mut self.line_fragment);
let line = if fragment.is_empty() {
Cow::from(line)
} else {
Cow::from(format!("{fragment}{line}"))
};
self.writer.queue(Print("\r\n"))?;
if !self.disable_address_resolution {
for symbols in &self.symbols {
resolve_addresses(
symbols,
&line,
&mut self.writer,
self.try_resolve_all_addresses,
)?;
}
if line.starts_with(stack_dump::MARKER)
&& stack_dump::backtrace_from_stack_dump(
&line,
&mut self.writer,
&self.elfs,
&self.symbols,
)
.is_err()
{
self.writer.queue(Print("\nUnable to decode stack-dump. Double check `-Cforce-unwind-tables` is used.\n"))?;
}
}
}
if let Some(line) = incomplete {
self.writer.queue(Print(line))?;
let fragment = std::mem::take(&mut self.line_fragment);
self.line_fragment = format!("{fragment}{line}");
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
self.writer.flush()
}
}
#[cfg(test)]
mod test {
use super::Utf8Merger;
#[test]
fn returns_valid_strings_immediately() {
let mut ctx = Utf8Merger::new();
let buff = b"Hello, world!";
let text = ctx.process_utf8(buff);
assert_eq!(text, "Hello, world!");
}
#[test]
fn does_not_repeat_valid_strings() {
let mut ctx = Utf8Merger::new();
let text = ctx.process_utf8(b"Hello, world!");
assert_eq!(text, "Hello, world!");
let text = ctx.process_utf8(b"Something else");
assert_eq!(text, "Something else");
}
#[test]
fn replaces_invalid_sequence() {
let mut ctx = Utf8Merger::new();
let text = ctx.process_utf8(b"Hello, \xFF world!");
assert_eq!(text, "Hello, \u{FFFD} world!");
}
#[test]
fn can_replace_unfinished_incomplete_sequence() {
let mut ctx = Utf8Merger::new();
let mut incomplete = Vec::from("Hello, ".as_bytes());
let utf8 = "🙈".as_bytes();
incomplete.extend_from_slice(&utf8[..utf8.len() - 1]);
let text = ctx.process_utf8(&incomplete);
assert_eq!(text, "Hello, ");
let text = ctx.process_utf8(b" world!");
assert_eq!(text, "\u{FFFD} world!");
}
#[test]
fn can_merge_incomplete_sequence() {
let mut ctx = Utf8Merger::new();
let mut incomplete = Vec::from("Hello, ".as_bytes());
let utf8 = "🙈".as_bytes();
incomplete.extend_from_slice(&utf8[..utf8.len() - 1]);
let text = ctx.process_utf8(&incomplete);
assert_eq!(text, "Hello, ");
let text = ctx.process_utf8(&utf8[utf8.len() - 1..]);
assert_eq!(text, "🙈");
}
#[test]
fn issue_457() {
let mut ctx = Utf8Merger::new();
let mut result = String::new();
result.push_str(&ctx.process_utf8(&[0x48]));
result.push_str(&ctx.process_utf8(&[0x65, 0x6C, 0x6C]));
result.push_str(&ctx.process_utf8(&[
0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x21, 0x20, 0x77, 0x69, 0x74,
]));
result.push_str(&ctx.process_utf8(&[
0x68, 0x20, 0x55, 0x54, 0x46, 0x3A, 0x20, 0x77, 0x79, 0x73, 0x79,
]));
result.push_str(&ctx.process_utf8(&[0xC5, 0x82, 0x61, 0x6D, 0x0A]));
assert_eq!(result, "Hello world! with UTF: wysyłam\r\n");
}
}