rust_debugging_locks/
stacktrace_util.rs

1use std::collections::hash_map::DefaultHasher;
2use std::fmt;
3use std::fmt::Display;
4use std::hash::{Hash, Hasher};
5use std::path::PathBuf;
6use std::thread::ThreadId;
7use base58::ToBase58;
8
9pub struct Stracktrace {
10    pub frames: Vec<Frame>,
11    // simple tagging of stacktrace e.g. 'JuCPL' - use for grepping
12    pub hash: String,
13}
14
15pub struct Frame {
16    pub method: String,
17    pub filename: String,
18    pub line_no: u32,
19}
20
21pub struct ThreadInfo {
22    pub thread_id: ThreadId,
23    pub name: String,
24}
25
26#[derive(Debug)]
27pub enum BacktrackError {
28    NoStartFrame,
29    NoDebugSymbols,
30}
31
32impl Display for ThreadInfo {
33    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34        // TODO fix format "main:ThreadId(1)" -> how to deal with numeric thread id?
35        write!(f, "{}:{:?}", self.name, self.thread_id)
36    }
37}
38
39impl Display for BacktrackError {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
41        match self {
42            BacktrackError::NoStartFrame =>
43                write!(f, "Start Frame not found!"),
44            BacktrackError::NoDebugSymbols => {
45                write!(f, "No debug symbols! Did you build in release mode?")
46            }
47        }
48    }
49}
50
51
52impl std::error::Error for BacktrackError {}
53
54/// Returns a list of stack frames starting with innermost frame.
55///
56/// # Examples
57///
58/// ```
59/// use rust_debugging_locks::stacktrace_util::backtrack_frame;
60/// let frames = backtrack_frame(|symbol_name| symbol_name.starts_with("rust_basics::debugging_locks::"));
61/// ```
62pub fn backtrack_frame(fn_skip_frame: fn(&str) -> bool) -> Result<Stracktrace, BacktrackError> {
63
64    const FRAMES_LIMIT: usize = 99;
65
66    let mut started = false;
67    let mut stop = false;
68    let mut symbols = 0;
69    let mut hasher = DefaultHasher::new();
70
71    // ordering: inside out
72    let mut frames: Vec<Frame> = vec![];
73
74    backtrace::trace(|frame| {
75        backtrace::resolve_frame(frame, |symbol| {
76            // note: values are None for release build
77            // sample output:
78            // Symbol { name: backtrace::backtrace::trace_unsynchronized::hc02a5cecd085adce,
79            //   addr: 0x100001b2a, filename: ".../.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.67/src/backtrace/mod.rs", lineno: 66 }
80
81            if stop {
82                return;
83            }
84
85            if symbol.filename().is_none() {
86                return;
87            }
88
89            symbols += 1;
90
91            if frames.len() > FRAMES_LIMIT {
92                stop = true;
93                return;
94            }
95
96            // /rustc/69f9c33d71c871fc16ac445211281c6e7a340943/library/std/src/rt.rs
97            if symbol.filename().unwrap().starts_with(PathBuf::from("/rustc")) {
98                stop = true;
99                return;
100            }
101
102            // symbol.name looks like this "rust_basics::debugging_lock_newtype::backtrack::h1cb6032f9b10548c"
103            let symbol_name = symbol.name().unwrap().to_string();
104            // module_path is "rust_debugging_locks::stacktrace_util"
105
106            if !symbol_name.starts_with("backtrace::backtrace::")
107                && !fn_skip_frame(symbol_name.as_str()) {
108
109                started = true;
110                // do not return to catch the current frame
111
112            }
113
114            if !started {
115                return;
116            }
117
118            let frame = Frame {
119                method: symbol.name().unwrap().to_string(),
120                filename: symbol.filename().unwrap().file_name().unwrap().to_str().unwrap().to_string(),
121                line_no: symbol.lineno().unwrap()
122            };
123
124            // hash frame data
125            hasher.write(frame.method.as_bytes());
126            hasher.write_i32(0x2A66ED); // random separator
127            hasher.write(frame.filename.as_bytes());
128            hasher.write_i32(0x2A66ED); // random separator
129            hasher.write_u32(frame.line_no);
130            hasher.write_i32(0xF122ED); // random separator
131
132            frames.push(frame);
133
134        });
135
136        !stop
137    });
138
139    if started == false {
140        if symbols == 0 {
141            // detected implicitly by checking frames
142            return Err(BacktrackError::NoDebugSymbols);
143        } else {
144            return Err(BacktrackError::NoStartFrame);
145        }
146    } else {
147        let hash32 = hasher.finish() as u32;
148        let hash = hash32.to_be_bytes().to_base58();
149        return Ok(Stracktrace { frames, hash });
150    }
151
152}
153
154fn debug_frames(frames: &Result<Vec<Frame>, BacktrackError>) {
155    for frame in frames.as_ref().unwrap() {
156        println!("\t>{}:{}:{}", frame.filename, frame.method, frame.line_no);
157    }
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn stacktrace_from_method() {
166        let stacktrace = caller_function().unwrap();
167        // debug_frames(&frames);
168        assert!(stacktrace.frames.get(0).unwrap().method.starts_with("rust_debugging_locks::stacktrace_util::tests::caller_function::h"),
169                "method name: {}", stacktrace.frames.get(0).unwrap().method);
170    }
171
172    fn caller_function() -> Result<Stracktrace, BacktrackError> {
173        backtrack_frame(|symbol_name| !symbol_name.contains("::caller_function"))
174    }
175}