use std::{
env, fs,
io::{self, Read},
path::{Path, PathBuf},
};
use anyhow::anyhow;
use clap::Parser;
use defmt_decoder::{DecodeError, Frame, Locations, Table};
#[derive(Parser)]
#[command(name = "defmt-print")]
struct Opts {
#[arg(short, required = true, conflicts_with("version"))]
elf: Option<PathBuf>,
#[arg(long)]
json: bool,
#[arg(long)]
show_skipped_frames: bool,
#[arg(short, long)]
verbose: bool,
#[arg(short = 'V', long)]
version: bool,
}
const READ_BUFFER_SIZE: usize = 1024;
fn main() -> anyhow::Result<()> {
let Opts {
elf,
json,
show_skipped_frames,
verbose,
version,
} = Opts::parse();
if version {
return print_version();
}
defmt_decoder::log::init_logger(verbose, json, move |metadata| match verbose {
false => defmt_decoder::log::is_defmt_frame(metadata), true => true, });
let bytes = fs::read(&elf.unwrap())?;
let table = Table::parse(&bytes)?.ok_or_else(|| anyhow!(".defmt data not found"))?;
let locs = table.get_locations(&bytes)?;
let locs = if table.indices().all(|idx| locs.contains_key(&(idx as u64))) {
Some(locs)
} else {
log::warn!("(BUG) location info is incomplete; it will be omitted from the output");
None
};
let mut buf = [0; READ_BUFFER_SIZE];
let mut stream_decoder = table.new_stream_decoder();
let current_dir = env::current_dir()?;
let mut stdin = io::stdin().lock();
loop {
let n = stdin.read(&mut buf)?;
if n == 0 {
break Ok(());
}
stream_decoder.received(&buf[..n]);
loop {
match stream_decoder.decode() {
Ok(frame) => forward_to_logger(&frame, location_info(&locs, &frame, ¤t_dir)),
Err(DecodeError::UnexpectedEof) => break,
Err(DecodeError::Malformed) => match table.encoding().can_recover() {
false => return Err(DecodeError::Malformed.into()),
true => {
if show_skipped_frames || verbose {
println!("(HOST) malformed frame skipped");
println!("└─ {} @ {}:{}", env!("CARGO_PKG_NAME"), file!(), line!());
}
continue;
}
},
}
}
}
}
type LocationInfo = (Option<String>, Option<u32>, Option<String>);
fn forward_to_logger(frame: &Frame, location_info: LocationInfo) {
let (file, line, mod_path) = location_info;
defmt_decoder::log::log_defmt(frame, file.as_deref(), line, mod_path.as_deref());
}
fn location_info(locs: &Option<Locations>, frame: &Frame, current_dir: &Path) -> LocationInfo {
let (mut file, mut line, mut mod_path) = (None, None, None);
let loc = locs.as_ref().map(|locs| &locs[&frame.index()]);
if let Some(loc) = loc {
let path = loc.file.strip_prefix(¤t_dir).unwrap_or(&loc.file);
file = Some(path.display().to_string());
line = Some(loc.line as u32);
mod_path = Some(loc.module.clone());
}
(file, line, mod_path)
}
#[allow(clippy::unnecessary_wraps)]
fn print_version() -> anyhow::Result<()> {
println!("{} {}", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION"));
println!("supported defmt version: {}", defmt_decoder::DEFMT_VERSION);
Ok(())
}