]> git.lizzy.rs Git - rust.git/blob - src/libstd/sys_common/backtrace.rs
Rollup merge of #58273 - taiki-e:rename-dependency, r=matthewjasper
[rust.git] / src / libstd / sys_common / backtrace.rs
1 /// Common code for printing the backtrace in the same way across the different
2 /// supported platforms.
3
4 use env;
5 use io::prelude::*;
6 use io;
7 use path::{self, Path};
8 use ptr;
9 use rustc_demangle::demangle;
10 use str;
11 use sync::atomic::{self, Ordering};
12 use sys::mutex::Mutex;
13
14 pub use sys::backtrace::{
15     unwind_backtrace,
16     resolve_symname,
17     foreach_symbol_fileline,
18     BacktraceContext
19 };
20
21 #[cfg(target_pointer_width = "64")]
22 pub const HEX_WIDTH: usize = 18;
23
24 #[cfg(target_pointer_width = "32")]
25 pub const HEX_WIDTH: usize = 10;
26
27 /// Represents an item in the backtrace list. See `unwind_backtrace` for how
28 /// it is created.
29 #[derive(Debug, Copy, Clone)]
30 pub struct Frame {
31     /// Exact address of the call that failed.
32     pub exact_position: *const u8,
33     /// Address of the enclosing function.
34     pub symbol_addr: *const u8,
35     /// Which inlined function is this frame referring to
36     pub inline_context: u32,
37 }
38
39 /// Max number of frames to print.
40 const MAX_NB_FRAMES: usize = 100;
41
42 /// Prints the current backtrace.
43 pub fn print(w: &mut dyn Write, format: PrintFormat) -> io::Result<()> {
44     static LOCK: Mutex = Mutex::new();
45
46     // There are issues currently linking libbacktrace into tests, and in
47     // general during libstd's own unit tests we're not testing this path. In
48     // test mode immediately return here to optimize away any references to the
49     // libbacktrace symbols
50     if cfg!(test) {
51         return Ok(())
52     }
53
54     // Use a lock to prevent mixed output in multithreading context.
55     // Some platforms also requires it, like `SymFromAddr` on Windows.
56     unsafe {
57         LOCK.lock();
58         let res = _print(w, format);
59         LOCK.unlock();
60         res
61     }
62 }
63
64 fn _print(w: &mut dyn Write, format: PrintFormat) -> io::Result<()> {
65     let mut frames = [Frame {
66         exact_position: ptr::null(),
67         symbol_addr: ptr::null(),
68         inline_context: 0,
69     }; MAX_NB_FRAMES];
70     let (nb_frames, context) = unwind_backtrace(&mut frames)?;
71     let (skipped_before, skipped_after) =
72         filter_frames(&frames[..nb_frames], format, &context);
73     if skipped_before + skipped_after > 0 {
74         writeln!(w, "note: Some details are omitted, \
75                      run with `RUST_BACKTRACE=full` for a verbose backtrace.")?;
76     }
77     writeln!(w, "stack backtrace:")?;
78
79     let filtered_frames = &frames[..nb_frames - skipped_after];
80     for (index, frame) in filtered_frames.iter().skip(skipped_before).enumerate() {
81         resolve_symname(*frame, |symname| {
82             output(w, index, *frame, symname, format)
83         }, &context)?;
84         let has_more_filenames = foreach_symbol_fileline(*frame, |file, line| {
85             output_fileline(w, file, line, format)
86         }, &context)?;
87         if has_more_filenames {
88             w.write_all(b" <... and possibly more>")?;
89         }
90     }
91
92     Ok(())
93 }
94
95 /// Returns a number of frames to remove at the beginning and at the end of the
96 /// backtrace, according to the backtrace format.
97 fn filter_frames(frames: &[Frame],
98                  format: PrintFormat,
99                  context: &BacktraceContext) -> (usize, usize)
100 {
101     if format == PrintFormat::Full {
102         return (0, 0);
103     }
104
105     let skipped_before = 0;
106
107     let skipped_after = frames.len() - frames.iter().position(|frame| {
108         let mut is_marker = false;
109         let _ = resolve_symname(*frame, |symname| {
110             if let Some(mangled_symbol_name) = symname {
111                 // Use grep to find the concerned functions
112                 if mangled_symbol_name.contains("__rust_begin_short_backtrace") {
113                     is_marker = true;
114                 }
115             }
116             Ok(())
117         }, context);
118         is_marker
119     }).unwrap_or(frames.len());
120
121     if skipped_before + skipped_after >= frames.len() {
122         // Avoid showing completely empty backtraces
123         return (0, 0);
124     }
125
126     (skipped_before, skipped_after)
127 }
128
129
130 /// Fixed frame used to clean the backtrace with `RUST_BACKTRACE=1`.
131 #[inline(never)]
132 pub fn __rust_begin_short_backtrace<F, T>(f: F) -> T
133     where F: FnOnce() -> T, F: Send, T: Send
134 {
135     f()
136 }
137
138 /// Controls how the backtrace should be formatted.
139 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
140 pub enum PrintFormat {
141     /// Show only relevant data from the backtrace.
142     Short = 2,
143     /// Show all the frames with absolute path for files.
144     Full = 3,
145 }
146
147 // For now logging is turned off by default, and this function checks to see
148 // whether the magical environment variable is present to see if it's turned on.
149 pub fn log_enabled() -> Option<PrintFormat> {
150     static ENABLED: atomic::AtomicIsize = atomic::AtomicIsize::new(0);
151     match ENABLED.load(Ordering::SeqCst) {
152         0 => {}
153         1 => return None,
154         2 => return Some(PrintFormat::Short),
155         _ => return Some(PrintFormat::Full),
156     }
157
158     let val = env::var_os("RUST_BACKTRACE").and_then(|x|
159         if &x == "0" {
160             None
161         } else if &x == "full" {
162             Some(PrintFormat::Full)
163         } else {
164             Some(PrintFormat::Short)
165         }
166     );
167     ENABLED.store(match val {
168         Some(v) => v as isize,
169         None => 1,
170     }, Ordering::SeqCst);
171     val
172 }
173
174 /// Prints the symbol of the backtrace frame.
175 ///
176 /// These output functions should now be used everywhere to ensure consistency.
177 /// You may want to also use `output_fileline`.
178 fn output(w: &mut dyn Write, idx: usize, frame: Frame,
179               s: Option<&str>, format: PrintFormat) -> io::Result<()> {
180     // Remove the `17: 0x0 - <unknown>` line.
181     if format == PrintFormat::Short && frame.exact_position == ptr::null() {
182         return Ok(());
183     }
184     match format {
185         PrintFormat::Full => write!(w,
186                                     "  {:2}: {:2$?} - ",
187                                     idx,
188                                     frame.exact_position,
189                                     HEX_WIDTH)?,
190         PrintFormat::Short => write!(w, "  {:2}: ", idx)?,
191     }
192     match s {
193         Some(string) => {
194             let symbol = demangle(string);
195             match format {
196                 PrintFormat::Full => write!(w, "{}", symbol)?,
197                 // strip the trailing hash if short mode
198                 PrintFormat::Short => write!(w, "{:#}", symbol)?,
199             }
200         }
201         None => w.write_all(b"<unknown>")?,
202     }
203     w.write_all(b"\n")
204 }
205
206 /// Prints the filename and line number of the backtrace frame.
207 ///
208 /// See also `output`.
209 #[allow(dead_code)]
210 fn output_fileline(w: &mut dyn Write,
211                    file: &[u8],
212                    line: u32,
213                    format: PrintFormat) -> io::Result<()> {
214     // prior line: "  ##: {:2$} - func"
215     w.write_all(b"")?;
216     match format {
217         PrintFormat::Full => write!(w,
218                                     "           {:1$}",
219                                     "",
220                                     HEX_WIDTH)?,
221         PrintFormat::Short => write!(w, "           ")?,
222     }
223
224     let file = str::from_utf8(file).unwrap_or("<unknown>");
225     let file_path = Path::new(file);
226     let mut already_printed = false;
227     if format == PrintFormat::Short && file_path.is_absolute() {
228         if let Ok(cwd) = env::current_dir() {
229             if let Ok(stripped) = file_path.strip_prefix(&cwd) {
230                 if let Some(s) = stripped.to_str() {
231                     write!(w, "  at .{}{}:{}", path::MAIN_SEPARATOR, s, line)?;
232                     already_printed = true;
233                 }
234             }
235         }
236     }
237     if !already_printed {
238         write!(w, "  at {}:{}", file, line)?;
239     }
240
241     w.write_all(b"\n")
242 }
243