| 1 | //! Support for capturing a stack backtrace of an OS thread |
| 2 | //! |
| 3 | //! This module contains the support necessary to capture a stack backtrace of a |
| 4 | //! running OS thread from the OS thread itself. The `Backtrace` type supports |
| 5 | //! capturing a stack trace via the `Backtrace::capture` and |
| 6 | //! `Backtrace::force_capture` functions. |
| 7 | //! |
| 8 | //! A backtrace is typically quite handy to attach to errors (e.g. types |
| 9 | //! implementing `std::error::Error`) to get a causal chain of where an error |
| 10 | //! was generated. |
| 11 | //! |
| 12 | //! ## Accuracy |
| 13 | //! |
| 14 | //! Backtraces are attempted to be as accurate as possible, but no guarantees |
| 15 | //! are provided about the exact accuracy of a backtrace. Instruction pointers, |
| 16 | //! symbol names, filenames, line numbers, etc, may all be incorrect when |
| 17 | //! reported. Accuracy is attempted on a best-effort basis, however, any bug |
| 18 | //! reports are always welcome to indicate areas of improvement! |
| 19 | //! |
| 20 | //! For most platforms a backtrace with a filename/line number requires that |
| 21 | //! programs be compiled with debug information. Without debug information |
| 22 | //! filenames/line numbers will not be reported. |
| 23 | //! |
| 24 | //! ## Platform support |
| 25 | //! |
| 26 | //! Not all platforms that std compiles for support capturing backtraces. Some |
| 27 | //! platforms simply do nothing when capturing a backtrace. To check whether the |
| 28 | //! platform supports capturing backtraces you can consult the `BacktraceStatus` |
| 29 | //! enum as a result of `Backtrace::status`. |
| 30 | //! |
| 31 | //! Like above with accuracy platform support is done on a best effort basis. |
| 32 | //! Sometimes libraries might not be available at runtime or something may go |
| 33 | //! wrong which would cause a backtrace to not be captured. Please feel free to |
| 34 | //! report issues with platforms where a backtrace cannot be captured though! |
| 35 | //! |
| 36 | //! ## Environment Variables |
| 37 | //! |
| 38 | //! The `Backtrace::capture` function might not actually capture a backtrace by |
| 39 | //! default. Its behavior is governed by two environment variables: |
| 40 | //! |
| 41 | //! * `RUST_LIB_BACKTRACE` - if this is set to `0` then `Backtrace::capture` |
| 42 | //! will never capture a backtrace. Any other value set will enable |
| 43 | //! `Backtrace::capture`. |
| 44 | //! |
| 45 | //! * `RUST_BACKTRACE` - if `RUST_LIB_BACKTRACE` is not set, then this variable |
| 46 | //! is consulted with the same rules of `RUST_LIB_BACKTRACE`. |
| 47 | //! |
| 48 | //! * If neither of the above env vars are set, then `Backtrace::capture` will |
| 49 | //! be disabled. |
| 50 | //! |
| 51 | //! Capturing a backtrace can be a quite expensive runtime operation, so the |
| 52 | //! environment variables allow either forcibly disabling this runtime |
| 53 | //! performance hit or allow selectively enabling it in some programs. |
| 54 | //! |
| 55 | //! Note that the `Backtrace::force_capture` function can be used to ignore |
| 56 | //! these environment variables. Also note that the state of environment |
| 57 | //! variables is cached once the first backtrace is created, so altering |
| 58 | //! `RUST_LIB_BACKTRACE` or `RUST_BACKTRACE` at runtime might not actually change |
| 59 | //! how backtraces are captured. |
| 60 | |
| 61 | #![stable (feature = "backtrace" , since = "1.65.0" )] |
| 62 | |
| 63 | #[cfg (test)] |
| 64 | mod tests; |
| 65 | |
| 66 | // NB: A note on resolution of a backtrace: |
| 67 | // |
| 68 | // Backtraces primarily happen in two steps, one is where we actually capture |
| 69 | // the stack backtrace, giving us a list of instruction pointers corresponding |
| 70 | // to stack frames. Next we take these instruction pointers and, one-by-one, |
| 71 | // turn them into a human readable name (like `main`). |
| 72 | // |
| 73 | // The first phase can be somewhat expensive (walking the stack), especially |
| 74 | // on MSVC where debug information is consulted to return inline frames each as |
| 75 | // their own frame. The second phase, however, is almost always extremely |
| 76 | // expensive (on the order of milliseconds sometimes) when it's consulting debug |
| 77 | // information. |
| 78 | // |
| 79 | // We attempt to amortize this cost as much as possible by delaying resolution |
| 80 | // of an address to a human readable name for as long as possible. When |
| 81 | // `Backtrace::create` is called to capture a backtrace it doesn't actually |
| 82 | // perform any symbol resolution, but rather we lazily resolve symbols only just |
| 83 | // before they're needed for printing. This way we can make capturing a |
| 84 | // backtrace and throwing it away much cheaper, but actually printing a |
| 85 | // backtrace is still basically the same cost. |
| 86 | // |
| 87 | // This strategy comes at the cost of some synchronization required inside of a |
| 88 | // `Backtrace`, but that's a relatively small price to pay relative to capturing |
| 89 | // a backtrace or actually symbolizing it. |
| 90 | |
| 91 | use crate::backtrace_rs::{self, BytesOrWideString}; |
| 92 | use crate::ffi::c_void; |
| 93 | use crate::panic::UnwindSafe; |
| 94 | use crate::sync::LazyLock; |
| 95 | use crate::sync::atomic::Ordering::Relaxed; |
| 96 | use crate::sync::atomic::{Atomic, AtomicU8}; |
| 97 | use crate::sys::backtrace::{lock, output_filename, set_image_base}; |
| 98 | use crate::{env, fmt}; |
| 99 | |
| 100 | /// A captured OS thread stack backtrace. |
| 101 | /// |
| 102 | /// This type represents a stack backtrace for an OS thread captured at a |
| 103 | /// previous point in time. In some instances the `Backtrace` type may |
| 104 | /// internally be empty due to configuration. For more information see |
| 105 | /// `Backtrace::capture`. |
| 106 | #[stable (feature = "backtrace" , since = "1.65.0" )] |
| 107 | #[must_use ] |
| 108 | pub struct Backtrace { |
| 109 | inner: Inner, |
| 110 | } |
| 111 | |
| 112 | /// The current status of a backtrace, indicating whether it was captured or |
| 113 | /// whether it is empty for some other reason. |
| 114 | #[stable (feature = "backtrace" , since = "1.65.0" )] |
| 115 | #[non_exhaustive ] |
| 116 | #[derive (Debug, PartialEq, Eq)] |
| 117 | pub enum BacktraceStatus { |
| 118 | /// Capturing a backtrace is not supported, likely because it's not |
| 119 | /// implemented for the current platform. |
| 120 | #[stable (feature = "backtrace" , since = "1.65.0" )] |
| 121 | Unsupported, |
| 122 | /// Capturing a backtrace has been disabled through either the |
| 123 | /// `RUST_LIB_BACKTRACE` or `RUST_BACKTRACE` environment variables. |
| 124 | #[stable (feature = "backtrace" , since = "1.65.0" )] |
| 125 | Disabled, |
| 126 | /// A backtrace has been captured and the `Backtrace` should print |
| 127 | /// reasonable information when rendered. |
| 128 | #[stable (feature = "backtrace" , since = "1.65.0" )] |
| 129 | Captured, |
| 130 | } |
| 131 | |
| 132 | enum Inner { |
| 133 | Unsupported, |
| 134 | Disabled, |
| 135 | Captured(LazyLock<Capture, LazyResolve>), |
| 136 | } |
| 137 | |
| 138 | struct Capture { |
| 139 | actual_start: usize, |
| 140 | frames: Vec<BacktraceFrame>, |
| 141 | } |
| 142 | |
| 143 | fn _assert_send_sync() { |
| 144 | fn _assert<T: Send + Sync>() {} |
| 145 | _assert::<Backtrace>(); |
| 146 | } |
| 147 | |
| 148 | /// A single frame of a backtrace. |
| 149 | #[unstable (feature = "backtrace_frames" , issue = "79676" )] |
| 150 | pub struct BacktraceFrame { |
| 151 | frame: RawFrame, |
| 152 | symbols: Vec<BacktraceSymbol>, |
| 153 | } |
| 154 | |
| 155 | #[derive (Debug)] |
| 156 | enum RawFrame { |
| 157 | Actual(backtrace_rs::Frame), |
| 158 | #[cfg (test)] |
| 159 | Fake, |
| 160 | } |
| 161 | |
| 162 | struct BacktraceSymbol { |
| 163 | name: Option<Vec<u8>>, |
| 164 | filename: Option<BytesOrWide>, |
| 165 | lineno: Option<u32>, |
| 166 | colno: Option<u32>, |
| 167 | } |
| 168 | |
| 169 | enum BytesOrWide { |
| 170 | Bytes(Vec<u8>), |
| 171 | Wide(Vec<u16>), |
| 172 | } |
| 173 | |
| 174 | #[stable (feature = "backtrace" , since = "1.65.0" )] |
| 175 | impl fmt::Debug for Backtrace { |
| 176 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 177 | let capture = match &self.inner { |
| 178 | Inner::Unsupported => return fmt.write_str("<unsupported>" ), |
| 179 | Inner::Disabled => return fmt.write_str("<disabled>" ), |
| 180 | Inner::Captured(c) => &**c, |
| 181 | }; |
| 182 | |
| 183 | let frames = &capture.frames[capture.actual_start..]; |
| 184 | |
| 185 | write!(fmt, "Backtrace " )?; |
| 186 | |
| 187 | let mut dbg = fmt.debug_list(); |
| 188 | |
| 189 | for frame in frames { |
| 190 | if frame.frame.ip().is_null() { |
| 191 | continue; |
| 192 | } |
| 193 | |
| 194 | dbg.entries(&frame.symbols); |
| 195 | } |
| 196 | |
| 197 | dbg.finish() |
| 198 | } |
| 199 | } |
| 200 | |
| 201 | #[unstable (feature = "backtrace_frames" , issue = "79676" )] |
| 202 | impl fmt::Debug for BacktraceFrame { |
| 203 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 204 | let mut dbg: DebugList<'_, '_> = fmt.debug_list(); |
| 205 | dbg.entries(&self.symbols); |
| 206 | dbg.finish() |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | impl fmt::Debug for BacktraceSymbol { |
| 211 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 212 | // FIXME: improve formatting: https://github.com/rust-lang/rust/issues/65280 |
| 213 | // FIXME: Also, include column numbers into the debug format as Display already has them. |
| 214 | // Until there are stable per-frame accessors, the format shouldn't be changed: |
| 215 | // https://github.com/rust-lang/rust/issues/65280#issuecomment-638966585 |
| 216 | write!(fmt, " {{ " )?; |
| 217 | |
| 218 | if let Some(fn_name) = self.name.as_ref().map(|b| backtrace_rs::SymbolName::new(b)) { |
| 219 | write!(fmt, "fn: \"{:#}\"" , fn_name)?; |
| 220 | } else { |
| 221 | write!(fmt, "fn: <unknown>" )?; |
| 222 | } |
| 223 | |
| 224 | if let Some(fname) = self.filename.as_ref() { |
| 225 | write!(fmt, ", file: \"{:?}\"" , fname)?; |
| 226 | } |
| 227 | |
| 228 | if let Some(line) = self.lineno { |
| 229 | write!(fmt, ", line: {:?}" , line)?; |
| 230 | } |
| 231 | |
| 232 | write!(fmt, " }}" ) |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | impl fmt::Debug for BytesOrWide { |
| 237 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 238 | output_filename( |
| 239 | fmt, |
| 240 | bows:match self { |
| 241 | BytesOrWide::Bytes(w) => BytesOrWideString::Bytes(w), |
| 242 | BytesOrWide::Wide(w) => BytesOrWideString::Wide(w), |
| 243 | }, |
| 244 | backtrace_rs::PrintFmt::Short, |
| 245 | cwd:crate::env::current_dir().as_ref().ok(), |
| 246 | ) |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | impl Backtrace { |
| 251 | /// Returns whether backtrace captures are enabled through environment |
| 252 | /// variables. |
| 253 | fn enabled() -> bool { |
| 254 | // Cache the result of reading the environment variables to make |
| 255 | // backtrace captures speedy, because otherwise reading environment |
| 256 | // variables every time can be somewhat slow. |
| 257 | static ENABLED: Atomic<u8> = AtomicU8::new(0); |
| 258 | match ENABLED.load(Relaxed) { |
| 259 | 0 => {} |
| 260 | 1 => return false, |
| 261 | _ => return true, |
| 262 | } |
| 263 | let enabled = match env::var("RUST_LIB_BACKTRACE" ) { |
| 264 | Ok(s) => s != "0" , |
| 265 | Err(_) => match env::var("RUST_BACKTRACE" ) { |
| 266 | Ok(s) => s != "0" , |
| 267 | Err(_) => false, |
| 268 | }, |
| 269 | }; |
| 270 | ENABLED.store(enabled as u8 + 1, Relaxed); |
| 271 | enabled |
| 272 | } |
| 273 | |
| 274 | /// Captures a stack backtrace of the current thread. |
| 275 | /// |
| 276 | /// This function will capture a stack backtrace of the current OS thread of |
| 277 | /// execution, returning a `Backtrace` type which can be later used to print |
| 278 | /// the entire stack trace or render it to a string. |
| 279 | /// |
| 280 | /// This function will be a noop if the `RUST_BACKTRACE` or |
| 281 | /// `RUST_LIB_BACKTRACE` backtrace variables are both not set. If either |
| 282 | /// environment variable is set and enabled then this function will actually |
| 283 | /// capture a backtrace. Capturing a backtrace can be both memory intensive |
| 284 | /// and slow, so these environment variables allow liberally using |
| 285 | /// `Backtrace::capture` and only incurring a slowdown when the environment |
| 286 | /// variables are set. |
| 287 | /// |
| 288 | /// To forcibly capture a backtrace regardless of environment variables, use |
| 289 | /// the `Backtrace::force_capture` function. |
| 290 | #[stable (feature = "backtrace" , since = "1.65.0" )] |
| 291 | #[inline (never)] // want to make sure there's a frame here to remove |
| 292 | pub fn capture() -> Backtrace { |
| 293 | if !Backtrace::enabled() { |
| 294 | return Backtrace { inner: Inner::Disabled }; |
| 295 | } |
| 296 | Backtrace::create(Backtrace::capture as usize) |
| 297 | } |
| 298 | |
| 299 | /// Forcibly captures a full backtrace, regardless of environment variable |
| 300 | /// configuration. |
| 301 | /// |
| 302 | /// This function behaves the same as `capture` except that it ignores the |
| 303 | /// values of the `RUST_BACKTRACE` and `RUST_LIB_BACKTRACE` environment |
| 304 | /// variables, always capturing a backtrace. |
| 305 | /// |
| 306 | /// Note that capturing a backtrace can be an expensive operation on some |
| 307 | /// platforms, so this should be used with caution in performance-sensitive |
| 308 | /// parts of code. |
| 309 | #[stable (feature = "backtrace" , since = "1.65.0" )] |
| 310 | #[inline (never)] // want to make sure there's a frame here to remove |
| 311 | pub fn force_capture() -> Backtrace { |
| 312 | Backtrace::create(Backtrace::force_capture as usize) |
| 313 | } |
| 314 | |
| 315 | /// Forcibly captures a disabled backtrace, regardless of environment |
| 316 | /// variable configuration. |
| 317 | #[stable (feature = "backtrace" , since = "1.65.0" )] |
| 318 | #[rustc_const_stable (feature = "backtrace" , since = "1.65.0" )] |
| 319 | pub const fn disabled() -> Backtrace { |
| 320 | Backtrace { inner: Inner::Disabled } |
| 321 | } |
| 322 | |
| 323 | // Capture a backtrace which start just before the function addressed by |
| 324 | // `ip` |
| 325 | fn create(ip: usize) -> Backtrace { |
| 326 | let _lock = lock(); |
| 327 | let mut frames = Vec::new(); |
| 328 | let mut actual_start = None; |
| 329 | set_image_base(); |
| 330 | unsafe { |
| 331 | backtrace_rs::trace_unsynchronized(|frame| { |
| 332 | frames.push(BacktraceFrame { |
| 333 | frame: RawFrame::Actual(frame.clone()), |
| 334 | symbols: Vec::new(), |
| 335 | }); |
| 336 | if frame.symbol_address().addr() == ip && actual_start.is_none() { |
| 337 | actual_start = Some(frames.len()); |
| 338 | } |
| 339 | true |
| 340 | }); |
| 341 | } |
| 342 | |
| 343 | // If no frames came out assume that this is an unsupported platform |
| 344 | // since `backtrace` doesn't provide a way of learning this right now, |
| 345 | // and this should be a good enough approximation. |
| 346 | let inner = if frames.is_empty() { |
| 347 | Inner::Unsupported |
| 348 | } else { |
| 349 | Inner::Captured(LazyLock::new(lazy_resolve(Capture { |
| 350 | actual_start: actual_start.unwrap_or(0), |
| 351 | frames, |
| 352 | }))) |
| 353 | }; |
| 354 | |
| 355 | Backtrace { inner } |
| 356 | } |
| 357 | |
| 358 | /// Returns the status of this backtrace, indicating whether this backtrace |
| 359 | /// request was unsupported, disabled, or a stack trace was actually |
| 360 | /// captured. |
| 361 | #[stable (feature = "backtrace" , since = "1.65.0" )] |
| 362 | #[must_use ] |
| 363 | pub fn status(&self) -> BacktraceStatus { |
| 364 | match self.inner { |
| 365 | Inner::Unsupported => BacktraceStatus::Unsupported, |
| 366 | Inner::Disabled => BacktraceStatus::Disabled, |
| 367 | Inner::Captured(_) => BacktraceStatus::Captured, |
| 368 | } |
| 369 | } |
| 370 | } |
| 371 | |
| 372 | impl<'a> Backtrace { |
| 373 | /// Returns an iterator over the backtrace frames. |
| 374 | #[must_use ] |
| 375 | #[unstable (feature = "backtrace_frames" , issue = "79676" )] |
| 376 | pub fn frames(&'a self) -> &'a [BacktraceFrame] { |
| 377 | if let Inner::Captured(c: &LazyLock … + Send + Sync + UnwindSafe>) = &self.inner { &c.frames } else { &[] } |
| 378 | } |
| 379 | } |
| 380 | |
| 381 | #[stable (feature = "backtrace" , since = "1.65.0" )] |
| 382 | impl fmt::Display for Backtrace { |
| 383 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 384 | let capture = match &self.inner { |
| 385 | Inner::Unsupported => return fmt.write_str("unsupported backtrace" ), |
| 386 | Inner::Disabled => return fmt.write_str("disabled backtrace" ), |
| 387 | Inner::Captured(c) => &**c, |
| 388 | }; |
| 389 | |
| 390 | let full = fmt.alternate(); |
| 391 | let (frames, style) = if full { |
| 392 | (&capture.frames[..], backtrace_rs::PrintFmt::Full) |
| 393 | } else { |
| 394 | (&capture.frames[capture.actual_start..], backtrace_rs::PrintFmt::Short) |
| 395 | }; |
| 396 | |
| 397 | // When printing paths we try to strip the cwd if it exists, otherwise |
| 398 | // we just print the path as-is. Note that we also only do this for the |
| 399 | // short format, because if it's full we presumably want to print |
| 400 | // everything. |
| 401 | let cwd = crate::env::current_dir(); |
| 402 | let mut print_path = move |fmt: &mut fmt::Formatter<'_>, path: BytesOrWideString<'_>| { |
| 403 | output_filename(fmt, path, style, cwd.as_ref().ok()) |
| 404 | }; |
| 405 | |
| 406 | let mut f = backtrace_rs::BacktraceFmt::new(fmt, style, &mut print_path); |
| 407 | f.add_context()?; |
| 408 | for frame in frames { |
| 409 | if frame.symbols.is_empty() { |
| 410 | f.frame().print_raw(frame.frame.ip(), None, None, None)?; |
| 411 | } else { |
| 412 | for symbol in frame.symbols.iter() { |
| 413 | f.frame().print_raw_with_column( |
| 414 | frame.frame.ip(), |
| 415 | symbol.name.as_ref().map(|b| backtrace_rs::SymbolName::new(b)), |
| 416 | symbol.filename.as_ref().map(|b| match b { |
| 417 | BytesOrWide::Bytes(w) => BytesOrWideString::Bytes(w), |
| 418 | BytesOrWide::Wide(w) => BytesOrWideString::Wide(w), |
| 419 | }), |
| 420 | symbol.lineno, |
| 421 | symbol.colno, |
| 422 | )?; |
| 423 | } |
| 424 | } |
| 425 | } |
| 426 | f.finish()?; |
| 427 | Ok(()) |
| 428 | } |
| 429 | } |
| 430 | |
| 431 | mod helper { |
| 432 | use super::*; |
| 433 | pub(super) type LazyResolve = impl (FnOnce() -> Capture) + Send + Sync + UnwindSafe; |
| 434 | |
| 435 | #[define_opaque(LazyResolve)] |
| 436 | pub(super) fn lazy_resolve(mut capture: Capture) -> LazyResolve { |
| 437 | move || { |
| 438 | // Use the global backtrace lock to synchronize this as it's a |
| 439 | // requirement of the `backtrace` crate, and then actually resolve |
| 440 | // everything. |
| 441 | let _lock = lock(); |
| 442 | for frame in capture.frames.iter_mut() { |
| 443 | let symbols = &mut frame.symbols; |
| 444 | let frame = match &frame.frame { |
| 445 | RawFrame::Actual(frame) => frame, |
| 446 | #[cfg (test)] |
| 447 | RawFrame::Fake => unimplemented!(), |
| 448 | }; |
| 449 | unsafe { |
| 450 | backtrace_rs::resolve_frame_unsynchronized(frame, |symbol| { |
| 451 | symbols.push(BacktraceSymbol { |
| 452 | name: symbol.name().map(|m| m.as_bytes().to_vec()), |
| 453 | filename: symbol.filename_raw().map(|b| match b { |
| 454 | BytesOrWideString::Bytes(b) => BytesOrWide::Bytes(b.to_owned()), |
| 455 | BytesOrWideString::Wide(b) => BytesOrWide::Wide(b.to_owned()), |
| 456 | }), |
| 457 | lineno: symbol.lineno(), |
| 458 | colno: symbol.colno(), |
| 459 | }); |
| 460 | }); |
| 461 | } |
| 462 | } |
| 463 | |
| 464 | capture |
| 465 | } |
| 466 | } |
| 467 | } |
| 468 | use helper::*; |
| 469 | |
| 470 | impl RawFrame { |
| 471 | fn ip(&self) -> *mut c_void { |
| 472 | match self { |
| 473 | RawFrame::Actual(frame: &Frame) => frame.ip(), |
| 474 | #[cfg (test)] |
| 475 | RawFrame::Fake => crate::ptr::without_provenance_mut(1), |
| 476 | } |
| 477 | } |
| 478 | } |
| 479 | |