Skip to content
This repository has been archived by the owner on Sep 10, 2024. It is now read-only.

Commit

Permalink
feat: format backtrace with multine
Browse files Browse the repository at this point in the history
  • Loading branch information
pyaillet committed Apr 2, 2022
1 parent 413e70a commit 566a0d0
Showing 1 changed file with 146 additions and 91 deletions.
237 changes: 146 additions & 91 deletions espmonitor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@

use addr2line::Context;
use crossterm::{
QueueableCommand,
event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
style::{Color, Print, PrintStyledContent, Stylize},
terminal::{disable_raw_mode, enable_raw_mode},
QueueableCommand,
};
use gimli::{EndianRcSlice, RunTimeEndian};
use lazy_static::lazy_static;
Expand All @@ -29,7 +29,7 @@ use regex::Regex;
use serial::{self, BaudRate, SerialPort, SystemPort};
use std::{
fs,
io::{self, ErrorKind, Read, Write, stdout},
io::{self, stdout, ErrorKind, Read, Write},
process::exit,
time::{Duration, Instant},
};
Expand All @@ -42,10 +42,13 @@ const DEFAULT_BAUD_RATE: BaudRate = BaudRate::Baud115200;
const UNFINISHED_LINE_TIMEOUT: Duration = Duration::from_secs(5);

lazy_static! {
static ref LINE_SEP_RE: Regex = Regex::new("\r?\n")
.expect("Failed to parse line separator regex");
static ref FUNC_ADDR_RE: Regex = Regex::new(r"0x4[0-9a-fA-F]{7}")
.expect("Failed to parse program address regex");
static ref LINE_SEP_RE: Regex =
Regex::new("\r?\n").expect("Failed to parse line separator regex");
static ref BACKTRACE_RE: Regex =
Regex::new(r"Backtrace:((0x4[0-9a-fA-F]{7}):(0x[0-9a-fA-F]{8})[ ]?)*")
.expect("Failed to parse backtrace regex");
static ref FRAME_RE: Regex =
Regex::new("(0x4[0-9a-fA-F]{7}):(0x[0-9a-fA-F]{8})").expect("Failed to parse frame regex");
}

macro_rules! rprintln {
Expand All @@ -70,32 +73,35 @@ impl<'a> SerialState<'a> {
Self {
unfinished_line: "".to_owned(),
last_unfinished_line_at: Instant::now(),
symbols
symbols,
}
}
}

#[cfg(unix)]
pub fn run(args: AppArgs) -> Result<(), Box<dyn std::error::Error>> {
use nix::{sys::wait::{WaitStatus, waitpid}, unistd::{ForkResult, fork}};
use nix::{
sys::wait::{waitpid, WaitStatus},
unistd::{fork, ForkResult},
};

enable_raw_mode()?;

match unsafe { fork() } {
Err(err) => {
disable_raw_mode()?;
Err(err.into())
},
}
Ok(ForkResult::Parent { child }) => loop {
match waitpid(child, None) {
Ok(WaitStatus::Exited(_, status)) => {
disable_raw_mode()?;
exit(status);
},
}
Ok(WaitStatus::Signaled(_, _, _)) => {
disable_raw_mode()?;
exit(255);
},
}
_ => (),
}
},
Expand All @@ -119,39 +125,49 @@ fn run_child(args: AppArgs) -> Result<(), Box<dyn std::error::Error>> {
rprintln!(" CTRL+C Exit");
rprintln!();

let speed = args.speed.map(BaudRate::from_speed).unwrap_or(DEFAULT_BAUD_RATE);
let speed = args
.speed
.map(BaudRate::from_speed)
.unwrap_or(DEFAULT_BAUD_RATE);
rprintln!("Opening {} with speed {}", args.serial, speed.speed());

let mut dev = serial::open(&args.serial)?;
dev.set_timeout(Duration::from_millis(200))?;
dev.reconfigure(&|settings| {
settings.set_baud_rate(speed)
})?;

let bin_data = args.bin.as_ref().and_then(|bin_name| match fs::read(bin_name) {
Ok(bin_data) => {
rprintln!("Using {} as flash image", bin_name.to_string_lossy());
Some(bin_data)
},
Err(err) => {
rprintln!("WARNING: Unable to open flash image {}: {}", bin_name.to_string_lossy(), err);
None
},
});

let symbols = bin_data.as_ref().and_then(|bin_data| match load_bin_context(bin_data.as_slice()) {
Ok(symbols) => Some(symbols),
Err(err) => {
rprintln!("WARNING: Failed to parse flash image: {}", err);
None
},
});
dev.reconfigure(&|settings| settings.set_baud_rate(speed))?;

let bin_data = args
.bin
.as_ref()
.and_then(|bin_name| match fs::read(bin_name) {
Ok(bin_data) => {
rprintln!("Using {} as flash image", bin_name.to_string_lossy());
Some(bin_data)
}
Err(err) => {
rprintln!(
"WARNING: Unable to open flash image {}: {}",
bin_name.to_string_lossy(),
err
);
None
}
});

let symbols =
bin_data
.as_ref()
.and_then(|bin_data| match load_bin_context(bin_data.as_slice()) {
Ok(symbols) => Some(symbols),
Err(err) => {
rprintln!("WARNING: Failed to parse flash image: {}", err);
None
}
});

if args.reset {
reset_chip(&mut dev)?;
}


let mut serial_state = SerialState {
unfinished_line: String::new(),
last_unfinished_line_at: Instant::now(),
Expand All @@ -162,11 +178,15 @@ fn run_child(args: AppArgs) -> Result<(), Box<dyn std::error::Error>> {
let mut buf = [0u8; 1024];
loop {
match dev.read(&mut buf) {
Ok(bytes) if bytes > 0 => handle_serial(&mut serial_state, &buf[0..bytes], &mut output)?,
Ok(_) => if dev.read_dsr().is_err() {
rprintln!("Device disconnected; exiting");
break Ok(());
},
Ok(bytes) if bytes > 0 => {
handle_serial(&mut serial_state, &buf[0..bytes], &mut output)?
}
Ok(_) => {
if dev.read_dsr().is_err() {
rprintln!("Device disconnected; exiting");
break Ok(());
}
}
Err(err) if err.kind() == ErrorKind::TimedOut => (),
Err(err) if err.kind() == ErrorKind::WouldBlock => (),
Err(err) if err.kind() == ErrorKind::Interrupted => (),
Expand All @@ -186,10 +206,7 @@ fn run_child(args: AppArgs) -> Result<(), Box<dyn std::error::Error>> {
pub fn load_bin_context(data: &[u8]) -> Result<Symbols, Box<dyn std::error::Error + 'static>> {
let obj = object::File::parse(data)?;
let context = Context::new(&obj)?;
Ok(Symbols {
obj,
context,
})
Ok(Symbols { obj, context })
}

fn reset_chip(dev: &mut SystemPort) -> io::Result<()> {
Expand All @@ -202,25 +219,27 @@ fn reset_chip(dev: &mut SystemPort) -> io::Result<()> {
Ok(())
}

pub fn handle_serial(state: &mut SerialState, buf: &[u8], output: &mut dyn Write) -> io::Result<()> {
pub fn handle_serial(
state: &mut SerialState,
buf: &[u8],
output: &mut dyn Write,
) -> io::Result<()> {
let data = String::from_utf8_lossy(buf);
let mut lines = LINE_SEP_RE.split(&data).collect::<Vec<&str>>();

let new_unfinished_line =
if data.ends_with('\n') {
None
} else {
lines.pop()
};
let new_unfinished_line = if data.ends_with('\n') {
None
} else {
lines.pop()
};

for line in lines {
let full_line =
if !state.unfinished_line.is_empty() {
state.unfinished_line.push_str(line);
state.unfinished_line.as_str()
} else {
line
};
let full_line = if !state.unfinished_line.is_empty() {
state.unfinished_line.push_str(line);
state.unfinished_line.as_str()
} else {
line
};

if !full_line.is_empty() {
output_line(state, full_line, output)?;
Expand All @@ -231,7 +250,9 @@ pub fn handle_serial(state: &mut SerialState, buf: &[u8], output: &mut dyn Write
if let Some(nel) = new_unfinished_line {
state.unfinished_line.push_str(nel);
state.last_unfinished_line_at = Instant::now();
} else if !state.unfinished_line.is_empty() && state.last_unfinished_line_at.elapsed() > UNFINISHED_LINE_TIMEOUT {
} else if !state.unfinished_line.is_empty()
&& state.last_unfinished_line_at.elapsed() > UNFINISHED_LINE_TIMEOUT
{
output_line(state, &state.unfinished_line, output)?;
state.unfinished_line.clear();
}
Expand All @@ -241,31 +262,34 @@ pub fn handle_serial(state: &mut SerialState, buf: &[u8], output: &mut dyn Write

pub fn output_line(state: &SerialState, line: &str, output: &mut dyn Write) -> io::Result<()> {
if let Some(symbols) = state.symbols.as_ref() {
let mut cur_start_offset = 0;

for mat in FUNC_ADDR_RE.find_iter(line) {
let (function, file, lineno) = u64::from_str_radix(&mat.as_str()[2..], 16)
.ok()
.map(|addr| {
let function = find_function_name(symbols, addr);
let (file, lineno) = find_location(symbols, addr);
(function, file, lineno)
})
.unwrap_or((None, None, None));

fn or_qq(s: Option<String>) -> String {
s.unwrap_or_else(|| "??".to_string())
}

output.queue(Print(line[cur_start_offset..mat.end()].to_string()))?;
cur_start_offset = mat.end();
let symbolicated_name = format!(" [{}:{}:{}]", or_qq(function), or_qq(file), or_qq(lineno.map(|l| l.to_string())))
if BACKTRACE_RE.is_match(line) {
for cap in FRAME_RE.captures_iter(line) {
let (function, file, lineno) = u64::from_str_radix(&cap[1][2..], 16)
.ok()
.map(|addr| {
let function = find_function_name(symbols, addr);
let (file, lineno) = find_location(symbols, addr);
(function, file, lineno)
})
.unwrap_or((None, None, None));

fn or_qq(s: Option<String>) -> String {
s.unwrap_or_else(|| "??".to_string())
}

let symbolicated_name = format!(
"[{}:{}:{}:{}]",
or_qq(function),
or_qq(file),
or_qq(lineno.map(|l| l.to_string())),
&cap[1]
)
.with(Color::Yellow);
output.queue(PrintStyledContent(symbolicated_name))?;
}

if cur_start_offset < line.len() {
output.queue(Print(line[cur_start_offset..line.len()].to_string()))?;
output.queue(PrintStyledContent(symbolicated_name))?;
output.queue(Print(format!(":{}\r\n", &cap[2])))?;
}
} else {
output.queue(Print(line.to_string()))?;
}
} else {
output.queue(Print(line.to_string()))?;
Expand All @@ -290,21 +314,52 @@ fn handle_input(dev: &mut SystemPort, key_event: KeyEvent) -> io::Result<()> {
}

pub fn find_function_name(symbols: &Symbols<'_>, addr: u64) -> Option<String> {
symbols.context
symbols
.context
.find_frames(addr)
.ok()
.and_then(|mut frames| frames.next().ok().flatten())
.and_then(|frame| frame.function.and_then(|f| f.demangle().ok().map(|c| c.into_owned())))
.or_else(|| symbols.obj.symbol_map().get(addr).map(|sym| sym.name().to_string()))
.and_then(|frame| {
frame
.function
.and_then(|f| f.demangle().ok().map(|c| c.into_owned()))
})
.or_else(|| {
symbols
.obj
.symbol_map()
.get(addr)
.map(|sym| sym.name().to_string())
})
}

pub fn find_location(symbols: &Symbols<'_>, addr: u64) -> (Option<String>, Option<u32>) {
symbols.context
symbols
.context
.find_location(addr)
.ok()
.map(|location| (
location.as_ref().and_then(|location| location.file).map(|file| file.to_string()),
location.as_ref().and_then(|location| location.line)
))
.map(|location| {
(
location
.as_ref()
.and_then(|location| location.file)
.map(|file| file.to_string()),
location.as_ref().and_then(|location| location.line),
)
})
.unwrap_or((None, None))
}

#[cfg(test)]
mod tests {
use super::*;

const BACKTRACE_EXAMPLE: &str = "Backtrace:0x40083156:0x3ffd9300 0x40088285:0x3ffd9320 0x4008b889:0x3ffd9340 0x4008a07d:0x3ffd93c0 0x4008837c:0x3ffd93e0 0x4008832e:0x3ffd9400 0x4000117d:0x00000120";

#[test]
fn test_backtrace_re() {
for cap in FRAME_RE.captures_iter(BACKTRACE_EXAMPLE) {
println!("{} - {}", &cap[1], &cap[2])
}
}
}

0 comments on commit 566a0d0

Please sign in to comment.