Skip to main content

ntex_error/
bt.rs

1//! Backtrace
2#![allow(warnings)]
3use std::collections::HashMap;
4use std::hash::{BuildHasher, Hasher};
5use std::panic::Location;
6use std::{cell::RefCell, ffi::c_void, fmt, fmt::Write, os, path, ptr, sync::Arc, sync::LazyLock};
7
8use backtrace::{BacktraceFmt, BytesOrWideString, Frame, SymbolName};
9
10thread_local! {
11    static FRAMES: RefCell<HashMap<usize, Arc<ResolvedFrame>>> = RefCell::new(HashMap::default());
12    static REPRS: RefCell<HashMap<u64, Arc<str>>> = RefCell::new(HashMap::default());
13    static DEFAULT: Arc<str> = Arc::from("Unresolved backtrace");
14}
15
16static mut START: Option<(&'static str, u32)> = None;
17static mut START_ALT: Option<(&'static str, u32)> = None;
18
19/// Sets the source location where rendered backtraces are cut off.
20///
21/// The frame matching `file` (a path suffix) and `line` (`0` matches any line),
22/// and all of its callers, are omitted from the rendered backtrace.
23/// Must be called once during initialization, before any backtrace is resolved.
24pub fn set_backtrace_start(file: &'static str, line: u32) {
25    unsafe {
26        START = Some((file, line));
27    }
28}
29
30#[doc(hidden)]
31pub fn set_backtrace_start_alt(file: &'static str, line: u32) {
32    unsafe {
33        START_ALT = Some((file, line));
34    }
35}
36
37#[derive(Clone)]
38/// Representation of a backtrace.
39///
40/// This structure can be used to capture a backtrace at various
41/// points in a program and later used to inspect what the backtrace
42/// was at that time.
43pub struct Backtrace(Arc<BacktraceRaw>);
44
45#[derive(Debug)]
46/// Backtrace resolver.
47///
48/// Symbol resolution may require filesystem access and can be blocking.
49/// In asynchronous contexts, this work should be offloaded to a thread
50/// pool.
51///
52/// **Note:** Once resolution is complete, control must return to the
53/// originating thread to ensure caching is performed correctly.
54pub struct BacktraceResolver {
55    bt: Arc<BacktraceRaw>,
56    repr: Option<Arc<str>>,
57    resolved: bool,
58    frames: HashMap<usize, Arc<ResolvedFrame>>,
59}
60
61#[derive(Debug)]
62/// Representation of a backtrace.
63pub struct BacktraceRaw {
64    id: u64,
65    frames: [Option<RawFrame>; 80],
66    location: &'static str,
67}
68
69#[derive(Debug, Clone)]
70enum RawFrame {
71    Frame(Frame),
72    Ip(usize),
73}
74
75impl RawFrame {
76    fn ip(&self) -> usize {
77        match self {
78            RawFrame::Frame(frm) => frm.ip() as usize,
79            RawFrame::Ip(ip) => *ip,
80        }
81    }
82}
83
84#[derive(Debug)]
85struct ResolvedFrame {
86    ip: usize,
87    symbols: Vec<ResolvedSymbol>,
88}
89
90#[derive(Debug)]
91struct ResolvedSymbol {
92    name: Option<Vec<u8>>,
93    filename: Option<path::PathBuf>,
94    lineno: Option<u32>,
95    colno: Option<u32>,
96}
97
98impl ResolvedFrame {
99    fn resolve(frm: &RawFrame) -> Self {
100        let mut symbols = Vec::new();
101        let mut cb = |sym: &backtrace::Symbol| {
102            symbols.push(ResolvedSymbol {
103                name: sym.name().map(|n| n.as_bytes().to_vec()),
104                filename: sym.filename().map(path::Path::to_path_buf),
105                lineno: sym.lineno(),
106                colno: sym.colno(),
107            });
108        };
109        match frm {
110            RawFrame::Frame(f) => backtrace::resolve_frame(f, &mut cb),
111            RawFrame::Ip(ip) => backtrace::resolve(*ip as *mut c_void, &mut cb),
112        }
113        ResolvedFrame {
114            ip: frm.ip(),
115            symbols,
116        }
117    }
118}
119
120impl BacktraceRaw {
121    /// Create new backtrace
122    pub fn new(location: &'static Location<'static>) -> Self {
123        Self::with_filename(location.file())
124    }
125
126    #[track_caller]
127    /// Create new backtrace with current location
128    pub fn with_current() -> Self {
129        Self::new(Location::caller())
130    }
131
132    /// Create new backtrace with filename location
133    pub fn with_filename(location: &'static str) -> Self {
134        Self::capture(location, |cb| backtrace::trace(cb))
135    }
136
137    #[doc(hidden)]
138    /// Create new backtrace without acquiring the global backtrace lock.
139    ///
140    /// Does not allocate or lock, so it can be used from a signal handler
141    /// (subject to the platform unwinder being signal-safe).
142    ///
143    /// # Safety
144    ///
145    /// Same requirements as [`backtrace::trace_unsynchronized`].
146    pub unsafe fn with_filename_unsynchronized(location: &'static str) -> Self {
147        Self::capture(location, |cb| unsafe {
148            backtrace::trace_unsynchronized(cb);
149        })
150    }
151
152    #[doc(hidden)]
153    /// Create new backtrace from instruction pointers, innermost frame first.
154    ///
155    /// Used for stacks walked outside of the `backtrace` crate, for example
156    /// of another suspended thread. Does not allocate or lock.
157    pub fn from_ips(ips: &[usize], location: &'static str) -> Self {
158        let mut st = foldhash::fast::FixedState::default().build_hasher();
159        let mut frames: [Option<RawFrame>; 80] = [const { None }; 80];
160
161        for (frm, ip) in frames.iter_mut().zip(ips) {
162            st.write_usize(*ip);
163            *frm = Some(RawFrame::Ip(*ip));
164        }
165        let id = st.finish();
166
167        BacktraceRaw {
168            id,
169            frames,
170            location,
171        }
172    }
173
174    fn capture<F>(location: &'static str, trace: F) -> Self
175    where
176        F: FnOnce(&mut dyn FnMut(&Frame) -> bool),
177    {
178        let mut st = foldhash::fast::FixedState::default().build_hasher();
179        let mut idx = 0;
180        let mut frames: [Option<RawFrame>; 80] = [const { None }; 80];
181
182        trace(&mut |frm| {
183            let ip = frm.ip();
184            st.write_usize(ip as usize);
185            frames[idx] = Some(RawFrame::Frame(frm.clone()));
186            idx += 1;
187            idx < 80
188        });
189        let id = st.finish();
190
191        BacktraceRaw {
192            id,
193            frames,
194            location,
195        }
196    }
197}
198
199impl From<BacktraceRaw> for Backtrace {
200    fn from(bt: BacktraceRaw) -> Backtrace {
201        Backtrace(Arc::new(bt))
202    }
203}
204
205impl Backtrace {
206    /// Create new backtrace
207    pub fn new(location: &'static Location<'static>) -> Self {
208        Self(Arc::new(BacktraceRaw::new(location)))
209    }
210
211    #[track_caller]
212    /// Create new backtrace with current location
213    pub fn with_current() -> Self {
214        Self(Arc::new(BacktraceRaw::new(Location::caller())))
215    }
216
217    /// Create new backtrace with filename location
218    pub fn with_filename(location: &'static str) -> Self {
219        Self(Arc::new(BacktraceRaw::with_filename(location)))
220    }
221
222    /// Returns the rendered backtrace, if it has been resolved on the current thread.
223    pub fn repr(&self) -> Option<Arc<str>> {
224        REPRS.with(|r| r.borrow_mut().get(&self.0.id).cloned())
225    }
226
227    /// Returns `true` if this backtrace has been resolved (or resolution
228    /// has started) on the current thread.
229    pub fn is_resolved(&self) -> bool {
230        REPRS.with(|r| r.borrow_mut().contains_key(&self.0.id))
231    }
232
233    /// Resolves the backtrace symbols on the current thread and caches the result.
234    #[must_use]
235    pub fn resolve(self) -> Self {
236        self.resolver().resolve();
237        self
238    }
239
240    /// Returns a resolver that can resolve symbols outside the current thread.
241    ///
242    /// See [`BacktraceResolver`] for details.
243    pub fn resolver(&self) -> BacktraceResolver {
244        REPRS.with(|r| {
245            let mut reprs = r.borrow_mut();
246            if let Some(repr) = reprs.get(&self.0.id) {
247                BacktraceResolver {
248                    repr: None,
249                    resolved: true,
250                    bt: self.0.clone(),
251                    frames: HashMap::default(),
252                }
253            } else {
254                DEFAULT.with(|s| {
255                    reprs.insert(self.0.id, s.clone());
256                });
257
258                let mut frames = HashMap::default();
259
260                FRAMES.with(|c| {
261                    let mut cache = c.borrow();
262
263                    for frm in &self.0.frames {
264                        if let Some(frm) = frm {
265                            let ip = frm.ip();
266                            if let Some(frame) = cache.get(&ip) {
267                                frames.insert(ip, frame.clone());
268                            }
269                        }
270                    }
271                });
272
273                BacktraceResolver {
274                    frames,
275                    resolved: false,
276                    repr: None,
277                    bt: self.0.clone(),
278                }
279            }
280        })
281    }
282}
283
284impl BacktraceResolver {
285    /// Resolves backtrace symbols and renders the representation.
286    ///
287    /// The result is cached when the resolver is dropped.
288    #[allow(clippy::return_self_not_must_use)]
289    pub fn resolve(mut self) -> Self {
290        if self.resolved {
291            return self;
292        }
293
294        for frm in &self.bt.frames {
295            if let Some(frm) = frm {
296                let ip = frm.ip();
297                if self.frames.contains_key(&ip) {
298                    continue;
299                }
300                self.frames
301                    .insert(ip, Arc::new(ResolvedFrame::resolve(frm)));
302            }
303        }
304
305        let mut idx = 0;
306        let mut frames: [Option<&ResolvedFrame>; 80] = [None; 80];
307        for frm in &self.bt.frames {
308            if let Some(frm) = frm {
309                let ip = frm.ip();
310                frames[idx] = Some(self.frames[&ip].as_ref());
311                idx += 1;
312            }
313        }
314
315        find_loc(self.bt.location, &mut frames);
316
317        #[allow(static_mut_refs)]
318        {
319            if let Some(start) = unsafe { START } {
320                find_loc_start(start, &mut frames);
321            }
322            if let Some(start) = unsafe { START_ALT } {
323                find_loc_start(start, &mut frames);
324            }
325            PATHS2.with(|paths| {
326                for s in paths {
327                    find_loc_start((s.as_str(), 0), &mut frames);
328                }
329            });
330        }
331
332        let mut idx = 0;
333        for frm in &mut frames {
334            if frm.is_some() {
335                if idx > 10 {
336                    *frm = None;
337                } else {
338                    idx += 1;
339                }
340            }
341        }
342
343        let bt = Bt(&frames[..]);
344        let mut buf = String::new();
345        let _ = write!(&mut buf, "\n{bt:?}");
346        self.repr = Some(Arc::from(buf));
347
348        self
349    }
350}
351
352impl Drop for BacktraceResolver {
353    fn drop(&mut self) {
354        if !self.resolved {
355            if let Some(repr) = self.repr.take() {
356                REPRS.with(|r| {
357                    r.borrow_mut().insert(self.bt.id, repr);
358                });
359            }
360
361            FRAMES.with(|c| {
362                let mut cache = c.borrow_mut();
363
364                for (ip, frm) in &self.frames {
365                    let ip = frm.ip;
366                    if !cache.contains_key(&ip) {
367                        cache.insert(ip, frm.clone());
368                    }
369                }
370            });
371        }
372    }
373}
374
375fn find_loc(loc: &str, frames: &mut [Option<&ResolvedFrame>]) {
376    let mut idx = 0;
377
378    'outter: for (i, frm) in frames.iter().enumerate() {
379        if let Some(f) = frm {
380            for sym in &f.symbols {
381                if let Some(fname) = &sym.filename
382                    && fname.ends_with(loc)
383                {
384                    idx = i;
385                    break 'outter;
386                }
387            }
388        } else {
389            break;
390        }
391    }
392
393    for f in frames.iter_mut().take(idx) {
394        *f = None;
395    }
396
397    PATHS.with(|paths| {
398        'outter: for frm in &mut frames[idx..] {
399            if let Some(f) = frm {
400                for sym in &f.symbols {
401                    if let Some(fname) = &sym.filename {
402                        for p in paths {
403                            if fname.ends_with(p) {
404                                *frm = None;
405                                continue 'outter;
406                            }
407                        }
408                    }
409                }
410            }
411        }
412    });
413}
414
415thread_local! {
416    static PATHS: Vec<String> = {
417        let mut paths = Vec::new();
418        for item in [
419            &["src", "ctx.rs"][..],
420            &["src", "map_err.rs"][..],
421            &["src", "and_then.rs"][..],
422            &["src", "fn_service.rs"][..],
423            &["src", "pipeline.rs"][..],
424            &["src", "net", "factory.rs"][..],
425            &["src", "future", "future.rs"][..],
426            &["src", "net", "service.rs"][..],
427            &["src", "boxed.rs"][..],
428            &["src", "error.rs"][..],
429            &["src", "wrk.rs"][..],
430            &["src", "future.rs"][..],
431            &["std", "src", "thread", "local.rs"][..],
432        ] {
433            paths.push(item.iter().collect::<path::PathBuf>().to_string_lossy().into_owned());
434        }
435        paths
436    };
437
438    static PATHS2: Vec<String> = {
439        let mut paths = Vec::new();
440        for item in [
441            &["src", "driver.rs"][..],
442            &["src", "rt_compio.rs"][..],
443            &["core", "src", "panic", "unwind_safe.rs"][..],
444            &["src", "runtime", "task", "core.rs"][..]
445        ] {
446            paths.push(item.iter().collect::<path::PathBuf>().to_string_lossy().into_owned());
447        }
448        paths
449    }
450}
451
452fn find_loc_start(loc: (&str, u32), frames: &mut [Option<&ResolvedFrame>]) {
453    let mut idx = 0;
454    while idx < frames.len() {
455        if let Some(frm) = &frames[idx] {
456            for sym in &frm.symbols {
457                if let Some(fname) = &sym.filename
458                    && let Some(lineno) = sym.lineno
459                    && fname.ends_with(loc.0)
460                    && (loc.1 == 0 || lineno == loc.1)
461                {
462                    for f in frames.iter_mut().skip(idx) {
463                        if f.is_some() {
464                            *f = None;
465                        }
466                    }
467                    return;
468                }
469            }
470        }
471        idx += 1;
472    }
473}
474
475struct Bt<'a>(&'a [Option<&'a ResolvedFrame>]);
476
477impl fmt::Debug for Bt<'_> {
478    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
479        let cwd = std::env::current_dir();
480        let mut print_path = move |fmt: &mut fmt::Formatter<'_>, path: BytesOrWideString<'_>| {
481            let path = crate::utils::module_path_fs(path.to_str_lossy().as_ref());
482            fmt::Display::fmt(&path, fmt)
483        };
484
485        let mut f = BacktraceFmt::new(fmt, backtrace::PrintFmt::Short, &mut print_path);
486        f.add_context()?;
487        for frm in self.0.iter().flatten() {
488            let ip = frm.ip as *mut c_void;
489            let mut ff = f.frame();
490            for sym in &frm.symbols {
491                ff.print_raw_with_column(
492                    ip,
493                    sym.name.as_deref().map(SymbolName::new),
494                    sym.filename
495                        .as_deref()
496                        .and_then(|p| Some(BytesOrWideString::Bytes(p.to_str()?.as_bytes()))),
497                    sym.lineno,
498                    sym.colno,
499                )?;
500            }
501            if frm.symbols.is_empty() {
502                ff.print_raw(ip, None, None, None)?;
503            }
504        }
505        f.finish()?;
506        Ok(())
507    }
508}
509
510impl fmt::Debug for Backtrace {
511    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
512        if let Some(repr) = self.repr() {
513            fmt::Display::fmt(repr.as_ref(), f)
514        } else {
515            Ok(())
516        }
517    }
518}
519
520impl fmt::Display for Backtrace {
521    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
522        if let Some(repr) = self.repr() {
523            fmt::Display::fmt(repr.as_ref(), f)
524        } else {
525            Ok(())
526        }
527    }
528}