use std::collections::hash_map::DefaultHasher;
use std::fmt;
use std::fmt::Display;
use std::hash::{Hash, Hasher};
use std::path::PathBuf;
use std::thread::ThreadId;
use base58::ToBase58;
pub struct Stracktrace {
pub frames: Vec<Frame>,
pub hash: String,
}
pub struct Frame {
pub method: String,
pub filename: String,
pub line_no: u32,
}
pub struct ThreadInfo {
pub thread_id: ThreadId,
pub name: String,
}
#[derive(Debug)]
pub enum BacktrackError {
NoStartFrame,
NoDebugSymbols,
}
impl Display for ThreadInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{:?}", self.name, self.thread_id)
}
}
impl Display for BacktrackError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BacktrackError::NoStartFrame =>
write!(f, "Start Frame not found!"),
BacktrackError::NoDebugSymbols => {
write!(f, "No debug symbols! Did you build in release mode?")
}
}
}
}
impl std::error::Error for BacktrackError {}
pub fn backtrack_frame(fn_skip_frame: fn(&str) -> bool) -> Result<Stracktrace, BacktrackError> {
const FRAMES_LIMIT: usize = 99;
let mut started = false;
let mut stop = false;
let mut symbols = 0;
let mut hasher = DefaultHasher::new();
let mut frames: Vec<Frame> = vec![];
backtrace::trace(|frame| {
backtrace::resolve_frame(frame, |symbol| {
if stop {
return;
}
if symbol.filename().is_none() {
return;
}
symbols += 1;
if frames.len() > FRAMES_LIMIT {
stop = true;
return;
}
if symbol.filename().unwrap().starts_with(PathBuf::from("/rustc")) {
stop = true;
return;
}
let symbol_name = symbol.name().unwrap().to_string();
if !symbol_name.starts_with("backtrace::backtrace::")
&& !fn_skip_frame(symbol_name.as_str()) {
started = true;
}
if !started {
return;
}
let frame = Frame {
method: symbol.name().unwrap().to_string(),
filename: symbol.filename().unwrap().file_name().unwrap().to_str().unwrap().to_string(),
line_no: symbol.lineno().unwrap()
};
hasher.write(frame.method.as_bytes());
hasher.write_i32(0x2A66ED); hasher.write(frame.filename.as_bytes());
hasher.write_i32(0x2A66ED); hasher.write_u32(frame.line_no);
hasher.write_i32(0xF122ED);
frames.push(frame);
});
!stop
});
if started == false {
if symbols == 0 {
return Err(BacktrackError::NoDebugSymbols);
} else {
return Err(BacktrackError::NoStartFrame);
}
} else {
let hash32 = hasher.finish() as u32;
let hash = hash32.to_be_bytes().to_base58();
return Ok(Stracktrace { frames, hash });
}
}
fn debug_frames(frames: &Result<Vec<Frame>, BacktrackError>) {
for frame in frames.as_ref().unwrap() {
println!("\t>{}:{}:{}", frame.filename, frame.method, frame.line_no);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stacktrace_from_method() {
let stacktrace = caller_function().unwrap();
assert!(stacktrace.frames.get(0).unwrap().method.starts_with("rust_debugging_locks::stacktrace_util::tests::caller_function::h"),
"method name: {}", stacktrace.frames.get(0).unwrap().method);
}
fn caller_function() -> Result<Stracktrace, BacktrackError> {
backtrack_frame(|symbol_name| !symbol_name.contains("::caller_function"))
}
}