trace_error/
backtrace.rs

1//! Small extensions to the `backtrace` crate
2//!
3//! This module defines a formatting API for formatting both inline and captured backtraces,
4//! and a structure for holding file and line level captured backtraces.
5
6use std::os::raw::c_void;
7use std::fmt::{Debug, Formatter, Result as FmtResult};
8use std::path::Path;
9use std::thread;
10use std::mem;
11
12use bt::{resolve, trace, Backtrace, Symbol, SymbolName, BacktraceSymbol};
13
14/// Trait to define formatting for backtrace symbols
15pub trait BacktraceFmt {
16    /// Formats backtrace symbol components in some way
17    fn format(count: u32, symbol: &Symbol) -> String;
18
19    /// Same as `BacktraceFmt::format`, but accepts a captured `BacktraceSymbol` instead
20    fn format_captured(count: u32, symbol: &BacktraceSymbol) -> String;
21}
22
23/// Default backtrace formatter that tries to resemble rustc panic backtraces somewhat
24///
25/// Example:
26///
27/// ```text
28/// Stack backtrace for task "<main>" at line 47 of "examples\backtrace.rs":
29///    0:     0x7ff703417000 - backtrace::test
30///                         at E:\...\examples\backtrace.rs:47
31///    1:     0x7ff703417120 - backtrace::main
32///                         at E:\...\examples\backtrace.rs:53
33///    2:     0x7ff70343bb10 - panic_unwind::__rust_maybe_catch_panic
34///                         at C:\...\libpanic_unwind\lib.rs:98
35///    3:     0x7ff70343b240 - std::rt::lang_start
36///                         at C:\...\libstd\rt.rs:51
37///    4:     0x7ff7034171a0 - main
38///                         at <anonymous>
39///    5:     0x7ff70344d61c - __scrt_common_main_seh
40///                         at f:\...\exe_common.inl:253
41///    6:     0x7ffead558350 - BaseThreadInitThunk
42///                         at <anonymous>
43/// ```
44pub struct DefaultBacktraceFmt;
45
46impl DefaultBacktraceFmt {
47    #[inline(never)]
48    fn real_format(count: u32,
49                   name: Option<SymbolName>,
50                   addr: Option<*mut c_void>,
51                   filename: Option<&Path>,
52                   lineno: Option<u32>) -> String {
53        let ptr_width = mem::size_of::<usize>() * 2 + 2;
54
55        let name = name.and_then(|name| { name.as_str() }).unwrap_or("<unknown>");
56
57        let begin = format!("{:>4}: {:>4$p} - {:<}\n{:<5$}", count, addr.unwrap_or(0x0 as *mut _), name, "", ptr_width, ptr_width + 6);
58
59        let end = if let Some(filename) = filename {
60            if let Some(lineno) = lineno {
61                format!("at {}:{}\n", filename.display(), lineno)
62            } else {
63                format!("at {}\n", filename.display())
64            }
65        } else if let Some(lineno) = lineno {
66            format!("at <anonymous>:{}\n", lineno)
67        } else {
68            "at <anonymous>\n".to_string()
69        };
70
71        begin + end.as_str()
72    }
73}
74
75impl BacktraceFmt for DefaultBacktraceFmt {
76    #[inline]
77    fn format(count: u32, symbol: &Symbol) -> String {
78        DefaultBacktraceFmt::real_format(count, symbol.name(), symbol.addr(), symbol.filename(), symbol.lineno())
79    }
80
81    #[inline]
82    fn format_captured(count: u32, symbol: &BacktraceSymbol) -> String {
83        // Could just use format!("{:?}", symbol) since BacktraceSymbol has a debug format specifier, but eh, I like mine better
84        DefaultBacktraceFmt::real_format(count, symbol.name(), symbol.addr(), symbol.filename(), symbol.lineno())
85    }
86}
87
88/// Generates a formatted backtrace (via `Fmt` type) from here, but expects `line` and `file` to be where it was called from.
89///
90/// The actual call to `format_trace` and `trace` are ignored.
91#[inline(never)]
92pub fn format_trace<Fmt: BacktraceFmt>(header: bool, line: u32, file: &str) -> String {
93    let mut traces = if header {
94        format!("Stack backtrace for task \"<{}>\" at line {} of \"{}\":\n",
95                thread::current().name().unwrap_or("unnamed"), line, file)
96    } else {
97        String::new()
98    };
99
100    let mut count = 0;
101
102    trace(|frame| {
103        let before = count;
104
105        resolve(frame.ip(), |symbol| {
106            traces += Fmt::format(count, symbol).as_str();
107
108            count += 1;
109        });
110
111        // These will be equal if `resolve_cb` was not invoked
112        if count == before {
113            // If `symbol_address` doesn't work, oh well.
114            resolve(frame.symbol_address(), |symbol| {
115                traces += Fmt::format(count, symbol).as_str();
116
117                count += 1;
118            });
119        }
120
121        // Always continue
122        true
123    });
124
125    traces
126}
127
128/// Backtrace that also contains the exact line and file in which it originated from.
129///
130/// Usually created in a macro using the `line!()` and `file!()` macros
131#[derive(Clone)]
132pub struct SourceBacktrace {
133    backtrace: Backtrace,
134    line: u32,
135    file: &'static str,
136}
137
138impl Debug for SourceBacktrace {
139    fn fmt(&self, f: &mut Formatter) -> FmtResult {
140        write!(f, "SourceBacktrace {{\n    line: {},\n    file: {},\n    backtrace:\n{}}}", self.line, self.file, self.format::<DefaultBacktraceFmt>(false, false))
141    }
142}
143
144impl SourceBacktrace {
145    /// Create a new `SourceBacktrace` if you know the line and file
146    pub fn new(line: u32, file: &'static str) -> SourceBacktrace {
147        SourceBacktrace {
148            backtrace: Backtrace::new(),
149            line: line,
150            file: file,
151        }
152    }
153
154    /// Get a reference to the raw `Backtrace` instance
155    #[inline]
156    pub fn raw(&self) -> &Backtrace {
157        &self.backtrace
158    }
159
160    /// Get the line at which this backtrace originated from
161    #[inline]
162    pub fn line(&self) -> u32 {
163        self.line
164    }
165
166    /// Get the file path (as a `&'static str`) in which this backtrace originated from
167    #[inline]
168    pub fn file(&self) -> &'static str {
169        self.file
170    }
171
172    /// Format this backtrace with the given formatter and the given options
173    #[inline(never)]
174    pub fn format<Fmt: BacktraceFmt>(&self, header: bool, reverse: bool) -> String {
175        let mut traces = if header {
176            format!("Stack backtrace for task \"<{}>\" at line {} of \"{}\":\n",
177                    thread::current().name().unwrap_or("unnamed"), self.line, self.file)
178        } else {
179            String::new()
180        };
181
182        let mut count = 0;
183
184        if reverse {
185            // We can assume at least one symbol per frame
186            let mut symbols = Vec::with_capacity(self.backtrace.frames().len());
187
188            for frame in self.backtrace.frames() {
189                for symbol in frame.symbols() {
190                    symbols.push(symbol);
191                }
192            }
193
194            for symbol in symbols.iter().rev() {
195                traces += Fmt::format_captured(count, symbol).as_str();
196
197                count += 1;
198            }
199        } else {
200            for frame in self.backtrace.frames() {
201                for symbol in frame.symbols() {
202                    traces += Fmt::format_captured(count, symbol).as_str();
203
204                    count += 1;
205                }
206            }
207        }
208
209        traces
210    }
211}
212
213impl From<Backtrace> for SourceBacktrace {
214    fn from(backtrace: Backtrace) -> SourceBacktrace {
215        SourceBacktrace { line: line!(), file: file!(), backtrace: backtrace }
216    }
217}
218
219/// Returns a string containing the formatted backtrace and a header message
220///
221/// Pass a custom `BacktraceFmt` type to the macro to use custom formatting
222#[macro_export]
223macro_rules! backtrace {
224    () => {
225        backtrace!($crate::backtrace::DefaultBacktraceFmt)
226    };
227
228    ($fmt:ty) => {
229        $crate::backtrace::format_trace::<$fmt>(true, line!(), file!())
230    };
231}
232
233/// Variation of `backtrace!` that doesn't include the header line
234#[macro_export]
235macro_rules! backtrace_noheader {
236    () => {
237        backtrace_noheader!($crate::backtrace::DefaultBacktraceFmt)
238    };
239
240    ($fmt:ty) => {
241        $crate::backtrace::format_trace::<$fmt>(false, line!(), file!())
242    };
243}