diff --git a/.github/actions/build-with-patched-std/action.yml b/.github/actions/build-with-patched-std/action.yml index bdd127a38..5466a2289 100644 --- a/.github/actions/build-with-patched-std/action.yml +++ b/.github/actions/build-with-patched-std/action.yml @@ -41,7 +41,7 @@ runs: python3 x.py build library --stage 0 TEMP_BUILD_OUTPUT=$(mktemp test-binary-XXXXXXXX) - "$RUSTC_BUILD_DIR/stage0/bin/rustc" $RUSTC_FLAGS "${{ inputs.main-rs }}" -o "$TEMP_BUILD_OUTPUT" + "$RUSTC_BUILD_DIR/stage0-sysroot/bin/rustc" $RUSTC_FLAGS "${{ inputs.main-rs }}" -o "$TEMP_BUILD_OUTPUT" BINARY_SIZE=$(stat -c '%s' "$TEMP_BUILD_OUTPUT") rm "$TEMP_BUILD_OUTPUT" diff --git a/.github/workflows/check-binary-size.yml b/.github/workflows/check-binary-size.yml index d045fb7b3..5f4ec6168 100644 --- a/.github/workflows/check-binary-size.yml +++ b/.github/workflows/check-binary-size.yml @@ -6,8 +6,9 @@ name: Check binary size on: pull_request_target: - branches: - - master + # HACK(jubilee): something broke the distributed LLVM libso and I don't know what. + branches: [] +# - master # Both the "measure" and "report" jobs need to know this. env: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ba83f75c4..55e2d126f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,13 +26,18 @@ jobs: rust: stable - os: macos-latest rust: nightly - # HACK(jubilee): 1.77 broke backtraces on Windows lol - os: windows-latest - rust: 1.76.0-x86_64-msvc + rust: stable-x86_64-msvc - os: windows-latest - rust: 1.76.0-i686-msvc + rust: stable-i686-msvc - os: windows-latest - rust: 1.76.0-x86_64-gnu + rust: stable-x86_64-gnu + - os: windows-latest + rust: nightly-x86_64-msvc + - os: windows-latest + rust: nightly-i686-msvc + - os: windows-latest + rust: nightly-x86_64-gnu steps: - uses: actions/checkout@v3 with: @@ -50,9 +55,13 @@ jobs: shell: bash if: contains(matrix.rust, 'i686') + - name: Enable collapse_debuginfo based on version + run: echo RUSTFLAGS="--cfg dbginfo=\"collapsible\" $RUSTFLAGS" >> $GITHUB_ENV + shell: bash + if: contains(matrix.rust, 'nightly') || contains(matrix.rust, 'beta') + - run: cargo build - run: cargo test - - run: cargo test --features "serialize-rustc" - run: cargo test --features "serialize-serde" - run: cargo test --features "verify-winapi" - run: cargo test --features "cpp_demangle" @@ -64,18 +73,14 @@ jobs: env: CARGO_PROFILE_DEV_SPLIT_DEBUGINFO: packed CARGO_PROFILE_TEST_SPLIT_DEBUGINFO: packed - - run: cargo test --features gimli-symbolize --manifest-path crates/without_debuginfo/Cargo.toml - - run: cargo test --manifest-path crates/line-tables-only/Cargo.toml --features gimli-symbolize + - run: cargo test --manifest-path crates/without_debuginfo/Cargo.toml + - run: cargo test --manifest-path crates/line-tables-only/Cargo.toml # Test debuginfo compression still works - run: cargo test if: contains(matrix.os, 'ubuntu') env: - RUSTFLAGS: "-C link-arg=-Wl,--compress-debug-sections=zlib-gabi" - - run: cargo test - if: contains(matrix.os, 'ubuntu') - env: - RUSTFLAGS: "-C link-arg=-Wl,--compress-debug-sections=zlib-gnu" + RUSTFLAGS: "-C link-arg=-Wl,--compress-debug-sections=zlib" # Test that, on macOS, packed/unpacked debuginfo both work - run: cargo clean && cargo test @@ -190,7 +195,7 @@ jobs: with: submodules: true - name: Install Rust - run: rustup update stable && rustup default stable + run: rustup update stable --no-self-update && rustup default stable - run: rustup target add ${{ matrix.target }} - run: cargo generate-lockfile - run: echo RUSTFLAGS=-Dwarnings >> $GITHUB_ENV @@ -205,7 +210,7 @@ jobs: with: submodules: true - name: Install Rust - run: rustup update stable && rustup default stable && rustup component add rustfmt + run: rustup update stable --no-self-update && rustup default stable && rustup component add rustfmt - run: cargo fmt --all -- --check build: @@ -224,7 +229,7 @@ jobs: with: submodules: true - name: Install Rust - run: rustup update nightly && rustup default nightly + run: rustup update nightly --no-self-update && rustup default nightly - run: rustup target add ${{ matrix.target }} - run: echo RUSTFLAGS=-Dwarnings >> $GITHUB_ENV shell: bash @@ -245,7 +250,7 @@ jobs: with: submodules: true - name: Install Rust - run: rustup update 1.65.0 && rustup default 1.65.0 + run: rustup update 1.65.0 --no-self-update && rustup default 1.65.0 - run: cargo build miri: diff --git a/Cargo.lock b/Cargo.lock index ba8c53f4d..9772e64ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] @@ -32,7 +32,7 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.72" dependencies = [ "addr2line", "cc", @@ -44,16 +44,15 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "rustc-serialize", "serde", "winapi", ] [[package]] name = "cc" -version = "1.0.90" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" [[package]] name = "cfg-if" @@ -84,9 +83,9 @@ version = "0.1.0" [[package]] name = "gimli" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "libc" @@ -121,9 +120,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" dependencies = [ "memchr", ] @@ -148,15 +147,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "rustc-serialize" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe834bc780604f4674073badbad26d7219cadfb4a2275802db12cbae17498401" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "serde" diff --git a/Cargo.toml b/Cargo.toml index e1ee1c371..9dcc54a40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "backtrace" -version = "0.3.71" +version = "0.3.72" authors = ["The Rust Project Developers"] build = "build.rs" license = "MIT OR Apache-2.0" @@ -20,31 +20,32 @@ rust-version = "1.65.0" [workspace] members = ['crates/cpp_smoke_test', 'crates/as-if-std'] exclude = [ - 'crates/without_debuginfo', - 'crates/macos_frames_test', - 'crates/line-tables-only', - 'crates/debuglink', + 'crates/without_debuginfo', + 'crates/macos_frames_test', + 'crates/line-tables-only', + 'crates/debuglink', ] [dependencies] cfg-if = "1.0" -rustc-demangle = "0.1.4" +rustc-demangle = "0.1.24" # Optionally enable the ability to serialize a `Backtrace`, controlled through -# the `serialize-*` features below. +# the `serialize-serde` feature below. serde = { version = "1.0", optional = true, features = ['derive'] } -rustc-serialize = { version = "0.3", optional = true } # Optionally demangle C++ frames' symbols in backtraces. -cpp_demangle = { default-features = false, version = "0.4.0", optional = true, features = ["alloc"] } +cpp_demangle = { default-features = false, version = "0.4.0", optional = true, features = [ + "alloc", +] } [target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies] miniz_oxide = { version = "0.7.0", default-features = false } -addr2line = { version = "0.21.0", default-features = false } +addr2line = { version = "0.22.0", default-features = false } libc = { version = "0.2.146", default-features = false } [target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies.object] -version = "0.32.0" +version = "0.35.0" default-features = false features = ['read_core', 'elf', 'macho', 'pe', 'xcoff', 'unaligned', 'archive'] @@ -54,7 +55,7 @@ winapi = { version = "0.3.9", optional = true } [build-dependencies] # Only needed for Android, but cannot be target dependent # https://github.com/rust-lang/cargo/issues/4932 -cc = "1.0.90" +cc = "1.0.97" [dev-dependencies] dylib-dep = { path = "crates/dylib-dep" } @@ -67,11 +68,6 @@ default = ["std"] # Include std support. This enables types like `Backtrace`. std = [] -#======================================= -# Methods of serialization -# -# Various features used for enabling rustc-serialize or syntex codegen. -serialize-rustc = ["rustc-serialize"] serialize-serde = ["serde"] #======================================= @@ -81,10 +77,9 @@ serialize-serde = ["serde"] # purposes. New code should use none of these features. coresymbolication = [] dbghelp = [] +dl_iterate_phdr = [] dladdr = [] -gimli-symbolize = [] kernel32 = [] -libbacktrace = [] libunwind = [] unix-backtrace = [] verify-winapi = [ @@ -98,6 +93,8 @@ verify-winapi = [ 'winapi/tlhelp32', 'winapi/winbase', 'winapi/winnt', + 'winapi/winnls', + 'winapi/stringapiset', ] [[example]] @@ -135,3 +132,7 @@ harness = false name = "current-exe-mismatch" required-features = ["std"] harness = false + +[lints.rust] +# This crate uses them pervasively +unexpected_cfgs = "allow" diff --git a/crates/as-if-std/Cargo.toml b/crates/as-if-std/Cargo.toml index 1618058de..527d21056 100644 --- a/crates/as-if-std/Cargo.toml +++ b/crates/as-if-std/Cargo.toml @@ -18,18 +18,22 @@ libc = { version = "0.2.146", default-features = false } [target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies] miniz_oxide = { version = "0.7.0", optional = true, default-features = false } -addr2line = { version = "0.21.0", optional = true, default-features = false } +addr2line = { version = "0.22.0", optional = true, default-features = false } [target.'cfg(not(all(windows, target_env = "msvc", not(target_vendor = "uwp"))))'.dependencies.object] -version = "0.32.0" +version = "0.35.0" default-features = false optional = true features = ['read_core', 'elf', 'macho', 'pe', 'xcoff', 'unaligned', 'archive'] [build-dependencies] # Dependency of the `backtrace` crate -cc = "1.0.90" +cc = "1.0.97" [features] default = ['backtrace'] backtrace = ['addr2line', 'miniz_oxide', 'object'] +std = [] + +[lints.rust] +unexpected_cfgs = "allow" diff --git a/crates/line-tables-only/Cargo.toml b/crates/line-tables-only/Cargo.toml index 8d17db58c..4aa28f6a0 100644 --- a/crates/line-tables-only/Cargo.toml +++ b/crates/line-tables-only/Cargo.toml @@ -15,7 +15,3 @@ features = [ 'libunwind', 'std', ] - -[features] -libbacktrace = ['backtrace/libbacktrace'] -gimli-symbolize = ['backtrace/gimli-symbolize'] diff --git a/crates/macos_frames_test/tests/main.rs b/crates/macos_frames_test/tests/main.rs index 5d74bdcc3..1eace1a23 100644 --- a/crates/macos_frames_test/tests/main.rs +++ b/crates/macos_frames_test/tests/main.rs @@ -6,7 +6,7 @@ // so that it gets its own 'target' directory. We manually invoke this test // in .github/workflows/main.yml by passing `--manifest-path` to Cargo #[test] -#[cfg(target_os = "macos")] +#[cfg(target_vendor = "apple")] fn backtrace_no_dsym() { use std::{env, fs}; diff --git a/crates/without_debuginfo/Cargo.toml b/crates/without_debuginfo/Cargo.toml index 38e559971..b8c939414 100644 --- a/crates/without_debuginfo/Cargo.toml +++ b/crates/without_debuginfo/Cargo.toml @@ -14,7 +14,3 @@ debug = false [profile.test] debug = false - -[features] -libbacktrace = ['backtrace/libbacktrace'] -gimli-symbolize = ['backtrace/gimli-symbolize'] diff --git a/src/backtrace/dbghelp64.rs b/src/backtrace/dbghelp64.rs index d3ae06dff..78929e9f5 100644 --- a/src/backtrace/dbghelp64.rs +++ b/src/backtrace/dbghelp64.rs @@ -1,19 +1,10 @@ -//! Backtrace strategy for MSVC platforms. +//! Backtrace strategy for MSVC `x86_64` and `aarch64` platforms. //! -//! This module contains the ability to capture a backtrace on MSVC using one -//! of three possible methods. For `x86_64` and `aarch64`, we use `RtlVirtualUnwind` -//! to walk the stack one frame at a time. This function is much faster than using +//! This module contains the ability to capture a backtrace on MSVC using +//! `RtlVirtualUnwind` to walk the stack one frame at a time. This function is much faster than using //! `dbghelp!StackWalk*` because it does not load debug info to report inlined frames. //! We still report inlined frames during symbolization by consulting the appropriate //! `dbghelp` functions. -//! -//! For all other platforms, primarily `i686`, the `StackWalkEx` function is used if -//! possible, but not all systems have that. Failing that the `StackWalk64` function -//! is used instead. Note that `StackWalkEx` is favored because it handles debuginfo -//! internally and returns inline frame information. -//! -//! Note that all dbghelp support is loaded dynamically, see `src/dbghelp.rs` -//! for more information about that. #![allow(bad_style)] @@ -95,44 +86,66 @@ impl MyContext { pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) { use core::ptr; + // Capture the initial context to start walking from. let mut context = core::mem::zeroed::(); RtlCaptureContext(&mut context.0); - // Call `RtlVirtualUnwind` to find the previous stack frame, walking until we hit ip = 0. - while context.ip() != 0 { - let mut base = 0; + loop { + let ip = context.ip(); - let fn_entry = RtlLookupFunctionEntry(context.ip(), &mut base, ptr::null_mut()); + // The base address of the module containing the function will be stored here + // when RtlLookupFunctionEntry returns successfully. + let mut base = 0; + let fn_entry = RtlLookupFunctionEntry(ip, &mut base, ptr::null_mut()); if fn_entry.is_null() { + // No function entry could be found - this may indicate a corrupt + // stack or that a binary was unloaded (amongst other issues). Stop + // walking and don't call the callback as we can't be confident in + // this frame or the rest of the stack. break; } let frame = super::Frame { inner: Frame { - base_address: fn_entry.cast::(), - ip: context.ip() as *mut c_void, + base_address: base as *mut c_void, + ip: ip as *mut c_void, sp: context.sp() as *mut c_void, #[cfg(not(target_env = "gnu"))] inline_context: None, }, }; + // We've loaded all the info about the current frame, so now call the + // callback. if !cb(&frame) { + // Callback told us to stop, so we're done. break; } + // Unwind to the next frame. + let previous_ip = ip; + let previous_sp = context.sp(); let mut handler_data = 0usize; let mut establisher_frame = 0; - RtlVirtualUnwind( 0, base, - context.ip(), + ip, fn_entry, &mut context.0, ptr::addr_of_mut!(handler_data).cast::(), &mut establisher_frame, ptr::null_mut(), ); + + // RtlVirtualUnwind indicates the end of the stack in two different ways: + // * On x64, it sets the instruction pointer to 0. + // * On ARM64, it leaves the context unchanged (easiest way to check is + // to see if the instruction and stack pointers are the same). + // If we detect either of these, then unwinding is completed. + let ip = context.ip(); + if ip == 0 || (ip == previous_ip && context.sp() == previous_sp) { + break; + } } } diff --git a/src/backtrace/libunwind.rs b/src/backtrace/libunwind.rs index c3d88605c..d439be069 100644 --- a/src/backtrace/libunwind.rs +++ b/src/backtrace/libunwind.rs @@ -15,7 +15,6 @@ //! //! This is the default unwinding API for all non-Windows platforms currently. -use super::super::Bomb; use core::ffi::c_void; use core::ptr::addr_of_mut; @@ -100,6 +99,18 @@ impl Clone for Frame { } } +struct Bomb { + enabled: bool, +} + +impl Drop for Bomb { + fn drop(&mut self) { + if self.enabled { + panic!("cannot panic during the backtrace function"); + } + } +} + #[inline(always)] pub unsafe fn trace(mut cb: &mut dyn FnMut(&super::Frame) -> bool) { uw::_Unwind_Backtrace(trace_fn, addr_of_mut!(cb).cast()); diff --git a/src/capture.rs b/src/capture.rs index 73d94dc4b..f7d4b4fea 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -1,5 +1,7 @@ +#[cfg(feature = "serde")] +use crate::resolve; use crate::PrintFmt; -use crate::{resolve, resolve_frame, trace, BacktraceFmt, Symbol, SymbolName}; +use crate::{resolve_frame, trace, BacktraceFmt, Symbol, SymbolName}; use std::ffi::c_void; use std::fmt; use std::path::{Path, PathBuf}; @@ -21,7 +23,6 @@ use serde::{Deserialize, Serialize}; /// This function requires the `std` feature of the `backtrace` crate to be /// enabled, and the `std` feature is enabled by default. #[derive(Clone)] -#[cfg_attr(feature = "serialize-rustc", derive(RustcDecodable, RustcEncodable))] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct Backtrace { // Frames here are listed from top-to-bottom of the stack @@ -51,7 +52,7 @@ pub struct BacktraceFrame { #[derive(Clone)] enum Frame { Raw(crate::Frame), - #[allow(dead_code)] + #[cfg(feature = "serde")] Deserialized { ip: usize, symbol_address: usize, @@ -63,6 +64,7 @@ impl Frame { fn ip(&self) -> *mut c_void { match *self { Frame::Raw(ref f) => f.ip(), + #[cfg(feature = "serde")] Frame::Deserialized { ip, .. } => ip as *mut c_void, } } @@ -70,6 +72,7 @@ impl Frame { fn symbol_address(&self) -> *mut c_void { match *self { Frame::Raw(ref f) => f.symbol_address(), + #[cfg(feature = "serde")] Frame::Deserialized { symbol_address, .. } => symbol_address as *mut c_void, } } @@ -77,6 +80,7 @@ impl Frame { fn module_base_address(&self) -> Option<*mut c_void> { match *self { Frame::Raw(ref f) => f.module_base_address(), + #[cfg(feature = "serde")] Frame::Deserialized { module_base_address, .. @@ -98,6 +102,7 @@ impl Frame { }; match *self { Frame::Raw(ref f) => resolve_frame(f, sym), + #[cfg(feature = "serde")] Frame::Deserialized { ip, .. } => { resolve(ip as *mut c_void, sym); } @@ -116,7 +121,6 @@ impl Frame { /// This function requires the `std` feature of the `backtrace` crate to be /// enabled, and the `std` feature is enabled by default. #[derive(Clone)] -#[cfg_attr(feature = "serialize-rustc", derive(RustcDecodable, RustcEncodable))] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] pub struct BacktraceSymbol { name: Option>, @@ -440,53 +444,6 @@ impl fmt::Debug for BacktraceSymbol { } } -#[cfg(feature = "serialize-rustc")] -mod rustc_serialize_impls { - use super::*; - use rustc_serialize::{Decodable, Decoder, Encodable, Encoder}; - - #[derive(RustcEncodable, RustcDecodable)] - struct SerializedFrame { - ip: usize, - symbol_address: usize, - module_base_address: Option, - symbols: Option>, - } - - impl Decodable for BacktraceFrame { - fn decode(d: &mut D) -> Result - where - D: Decoder, - { - let frame: SerializedFrame = SerializedFrame::decode(d)?; - Ok(BacktraceFrame { - frame: Frame::Deserialized { - ip: frame.ip, - symbol_address: frame.symbol_address, - module_base_address: frame.module_base_address, - }, - symbols: frame.symbols, - }) - } - } - - impl Encodable for BacktraceFrame { - fn encode(&self, e: &mut E) -> Result<(), E::Error> - where - E: Encoder, - { - let BacktraceFrame { frame, symbols } = self; - SerializedFrame { - ip: frame.ip() as usize, - symbol_address: frame.symbol_address() as usize, - module_base_address: frame.module_base_address().map(|addr| addr as usize), - symbols: symbols.clone(), - } - .encode(e) - } - } -} - #[cfg(feature = "serde")] mod serde_impls { use super::*; diff --git a/src/dbghelp.rs b/src/dbghelp.rs index 711b53db6..2226faf21 100644 --- a/src/dbghelp.rs +++ b/src/dbghelp.rs @@ -376,16 +376,21 @@ pub fn init() -> Result { DBGHELP.ensure_open()?; static mut INITIALIZED: bool = false; - if INITIALIZED { - return Ok(ret); + if !INITIALIZED { + set_optional_options(); + INITIALIZED = true; } - - let orig = DBGHELP.SymGetOptions().unwrap()(); + Ok(ret) + } +} +fn set_optional_options() -> Option<()> { + unsafe { + let orig = DBGHELP.SymGetOptions()?(); // Ensure that the `SYMOPT_DEFERRED_LOADS` flag is set, because // according to MSVC's own docs about this: "This is the fastest, most // efficient way to use the symbol handler.", so let's do that! - DBGHELP.SymSetOptions().unwrap()(orig | SYMOPT_DEFERRED_LOADS); + DBGHELP.SymSetOptions()?(orig | SYMOPT_DEFERRED_LOADS); // Actually initialize symbols with MSVC. Note that this can fail, but we // ignore it. There's not a ton of prior art for this per se, but LLVM @@ -399,7 +404,7 @@ pub fn init() -> Result { // the time, but now that it's using this crate it means that someone will // get to initialization first and the other will pick up that // initialization. - DBGHELP.SymInitializeW().unwrap()(GetCurrentProcess(), ptr::null_mut(), TRUE); + DBGHELP.SymInitializeW()?(GetCurrentProcess(), ptr::null_mut(), TRUE); // The default search path for dbghelp will only look in the current working // directory and (possibly) `_NT_SYMBOL_PATH` and `_NT_ALT_SYMBOL_PATH`. @@ -413,7 +418,7 @@ pub fn init() -> Result { search_path_buf.resize(1024, 0); // Prefill the buffer with the current search path. - if DBGHELP.SymGetSearchPathW().unwrap()( + if DBGHELP.SymGetSearchPathW()?( GetCurrentProcess(), search_path_buf.as_mut_ptr(), search_path_buf.len() as _, @@ -433,7 +438,7 @@ pub fn init() -> Result { let mut search_path = SearchPath::new(search_path_buf); // Update the search path to include the directory of the executable and each DLL. - DBGHELP.EnumerateLoadedModulesW64().unwrap()( + DBGHELP.EnumerateLoadedModulesW64()?( GetCurrentProcess(), Some(enum_loaded_modules_callback), ((&mut search_path) as *mut SearchPath) as *mut c_void, @@ -442,11 +447,9 @@ pub fn init() -> Result { let new_search_path = search_path.finalize(); // Set the new search path. - DBGHELP.SymSetSearchPathW().unwrap()(GetCurrentProcess(), new_search_path.as_ptr()); - - INITIALIZED = true; - Ok(ret) + DBGHELP.SymSetSearchPathW()?(GetCurrentProcess(), new_search_path.as_ptr()); } + Some(()) } struct SearchPath { diff --git a/src/lib.rs b/src/lib.rs index 5b97281a7..20e9ecb80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,8 +97,6 @@ // irrelevant as this crate is developed out-of-tree. #![cfg_attr(backtrace_in_libstd, allow(warnings))] #![cfg_attr(not(feature = "std"), allow(dead_code))] -// We know this is deprecated, it's only here for back-compat reasons. -#![cfg_attr(feature = "rustc-serialize", allow(deprecated))] #[cfg(feature = "std")] #[macro_use] @@ -140,21 +138,6 @@ cfg_if::cfg_if! { } } -#[allow(dead_code)] -struct Bomb { - enabled: bool, -} - -#[allow(dead_code)] -impl Drop for Bomb { - fn drop(&mut self) { - if self.enabled { - panic!("cannot panic during the backtrace function"); - } - } -} - -#[allow(dead_code)] #[cfg(feature = "std")] mod lock { use std::boxed::Box; @@ -162,32 +145,95 @@ mod lock { use std::ptr; use std::sync::{Mutex, MutexGuard, Once}; + /// A "Maybe" LockGuard pub struct LockGuard(Option>); + /// The global lock, lazily allocated on first use static mut LOCK: *mut Mutex<()> = ptr::null_mut(); static INIT: Once = Once::new(); + // Whether this thread is the one that holds the lock thread_local!(static LOCK_HELD: Cell = Cell::new(false)); impl Drop for LockGuard { fn drop(&mut self) { + // Don't do anything if we're a LockGuard(None) if self.0.is_some() { LOCK_HELD.with(|slot| { + // Immediately crash if we somehow aren't the thread holding this lock assert!(slot.get()); + // We are no longer the thread holding this lock slot.set(false); }); } + // lock implicitly released here, if we're a LockGuard(Some(..)) } } + /// Acquire a partially unsound(!!!) global re-entrant lock over + /// backtrace's internals. + /// + /// That is, this lock can be acquired as many times as you want + /// on a single thread without deadlocking, allowing one thread + /// to acquire exclusive access to the ability to make backtraces. + /// Calls to this locking function are freely sprinkled in every place + /// where that needs to be enforced. + /// + /// + /// # Why + /// + /// This was first introduced to guard uses of Windows' dbghelp API, + /// which isn't threadsafe. It's unclear if other things now rely on + /// this locking. + /// + /// + /// # How + /// + /// The basic idea is to have a single global mutex, and a thread_local + /// boolean saying "yep this is the thread that acquired the mutex". + /// + /// The first time a thread acquires the lock, it is handed a + /// `LockGuard(Some(..))` that will actually release the lock on Drop. + /// All subsequence attempts to lock on the same thread will see + /// that their thread acquired the lock, and get `LockGuard(None)` + /// which will do nothing when dropped. + /// + /// + /// # Safety + /// + /// As long as you only ever assign the returned LockGuard to a freshly + /// declared local variable, it will do its job correctly, as the "first" + /// LockGuard will strictly outlive all subsequent LockGuards and + /// properly release the lock when the thread is done with backtracing. + /// + /// However if you ever attempt to store a LockGuard beyond the scope + /// it was acquired in, it might actually be a `LockGuard(None)` that + /// doesn't actually hold the lock! In this case another thread might + /// acquire the lock and you'll get races this system was intended to + /// avoid! + /// + /// This is why this is "partially unsound". As a public API this would + /// be unacceptable, but this is crate-private, and if you use this in + /// the most obvious and simplistic way it Just Works™. + /// + /// Note however that std specifically bypasses this lock, and uses + /// the `*_unsynchronized` backtrace APIs. This is "fine" because + /// it wraps its own calls to backtrace in a non-reentrant Mutex + /// that prevents two backtraces from getting interleaved during printing. pub fn lock() -> LockGuard { + // If we're the thread holding this lock, pretend to acquire the lock + // again by returning a LockGuard(None) if LOCK_HELD.with(|l| l.get()) { return LockGuard(None); } + // Insist that we totally are the thread holding the lock + // (our thread will block until we are) LOCK_HELD.with(|s| s.set(true)); unsafe { + // lazily allocate the lock if necessary INIT.call_once(|| { LOCK = Box::into_raw(Box::new(Mutex::new(()))); }); + // ok *actually* try to acquire the lock, blocking as necessary LockGuard(Some((*LOCK).lock().unwrap())) } } diff --git a/src/symbolize/dbghelp.rs b/src/symbolize/dbghelp.rs index 50d5384f0..dc4646459 100644 --- a/src/symbolize/dbghelp.rs +++ b/src/symbolize/dbghelp.rs @@ -19,7 +19,6 @@ use super::super::{dbghelp, windows::*}; use super::{BytesOrWideString, ResolveWhat, SymbolName}; -use core::char; use core::ffi::c_void; use core::marker; use core::mem; @@ -91,7 +90,7 @@ pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) ResolveWhat::Frame(frame) => { resolve_with_inline(&dbghelp, frame.ip(), frame.inner.inline_context(), cb) } - } + }; } #[cfg(target_vendor = "win7")] @@ -116,7 +115,7 @@ pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) ResolveWhat::Frame(frame) => { resolve_inner(&dbghelp, frame.ip(), frame.inner.inline_context(), cb) } - } + }; } /// Resolve the address using the legacy dbghelp API. @@ -147,22 +146,28 @@ unsafe fn resolve_with_inline( addr: *mut c_void, inline_context: Option, cb: &mut dyn FnMut(&super::Symbol), -) { +) -> Option<()> { let current_process = GetCurrentProcess(); + // Ensure we have the functions we need. Return if any aren't found. + let SymFromInlineContextW = (*dbghelp.dbghelp()).SymFromInlineContextW()?; + let SymGetLineFromInlineContextW = (*dbghelp.dbghelp()).SymGetLineFromInlineContextW()?; let addr = super::adjust_ip(addr) as DWORD64; let (inlined_frame_count, inline_context) = if let Some(ic) = inline_context { (0, ic) } else { - let mut inlined_frame_count = dbghelp.SymAddrIncludeInlineTrace()(current_process, addr); + let SymAddrIncludeInlineTrace = (*dbghelp.dbghelp()).SymAddrIncludeInlineTrace()?; + let SymQueryInlineTrace = (*dbghelp.dbghelp()).SymQueryInlineTrace()?; + + let mut inlined_frame_count = SymAddrIncludeInlineTrace(current_process, addr); let mut inline_context = 0; // If there is are inlined frames but we can't load them for some reason OR if there are no // inlined frames, then we disregard inlined_frame_count and inline_context. if (inlined_frame_count > 0 - && dbghelp.SymQueryInlineTrace()( + && SymQueryInlineTrace( current_process, addr, 0, @@ -184,22 +189,14 @@ unsafe fn resolve_with_inline( for inline_context in inline_context..last_inline_context { do_resolve( - |info| { - dbghelp.SymFromInlineContextW()(current_process, addr, inline_context, &mut 0, info) - }, + |info| SymFromInlineContextW(current_process, addr, inline_context, &mut 0, info), |line| { - dbghelp.SymGetLineFromInlineContextW()( - current_process, - addr, - inline_context, - 0, - &mut 0, - line, - ) + SymGetLineFromInlineContextW(current_process, addr, inline_context, 0, &mut 0, line) }, cb, ); } + Some(()) } unsafe fn do_resolve( @@ -225,26 +222,27 @@ unsafe fn do_resolve( // the real value. let name_len = ::core::cmp::min(info.NameLen as usize, info.MaxNameLen as usize - 1); let name_ptr = info.Name.as_ptr().cast::(); - let name = slice::from_raw_parts(name_ptr, name_len); // Reencode the utf-16 symbol to utf-8 so we can use `SymbolName::new` like // all other platforms - let mut name_len = 0; - let mut name_buffer = [0; 256]; - { - let mut remaining = &mut name_buffer[..]; - for c in char::decode_utf16(name.iter().cloned()) { - let c = c.unwrap_or(char::REPLACEMENT_CHARACTER); - let len = c.len_utf8(); - if len < remaining.len() { - c.encode_utf8(remaining); - let tmp = remaining; - remaining = &mut tmp[len..]; - name_len += len; - } else { - break; - } - } + let mut name_buffer = [0_u8; 256]; + let mut name_len = WideCharToMultiByte( + CP_UTF8, + 0, + name_ptr, + name_len as i32, + name_buffer.as_mut_ptr().cast::(), + name_buffer.len() as i32, + core::ptr::null_mut(), + core::ptr::null_mut(), + ) as usize; + if name_len == 0 { + // If the returned length is zero that means the buffer wasn't big enough. + // However, the buffer will be filled with as much as will fit. + name_len = name_buffer.len(); + } else if name_len > name_buffer.len() { + // This can't happen. + return; } let name = ptr::addr_of!(name_buffer[..name_len]); diff --git a/src/symbolize/gimli.rs b/src/symbolize/gimli.rs index fa8e59723..7465f755c 100644 --- a/src/symbolize/gimli.rs +++ b/src/symbolize/gimli.rs @@ -30,15 +30,16 @@ cfg_if::cfg_if! { if #[cfg(windows)] { #[path = "gimli/mmap_windows.rs"] mod mmap; + } else if #[cfg(target_vendor = "apple")] { + #[path = "gimli/mmap_unix.rs"] + mod mmap; } else if #[cfg(any( target_os = "android", target_os = "freebsd", target_os = "fuchsia", target_os = "haiku", target_os = "hurd", - target_os = "ios", target_os = "linux", - target_os = "macos", target_os = "openbsd", target_os = "solaris", target_os = "illumos", @@ -195,12 +196,7 @@ cfg_if::cfg_if! { if #[cfg(windows)] { mod coff; use self::coff::{handle_split_dwarf, Object}; - } else if #[cfg(any( - target_os = "macos", - target_os = "ios", - target_os = "tvos", - target_os = "watchos", - ))] { + } else if #[cfg(any(target_vendor = "apple"))] { mod macho; use self::macho::{handle_split_dwarf, Object}; } else if #[cfg(target_os = "aix")] { @@ -216,12 +212,7 @@ cfg_if::cfg_if! { if #[cfg(windows)] { mod libs_windows; use libs_windows::native_libraries; - } else if #[cfg(any( - target_os = "macos", - target_os = "ios", - target_os = "tvos", - target_os = "watchos", - ))] { + } else if #[cfg(target_vendor = "apple")] { mod libs_macos; use libs_macos::native_libraries; } else if #[cfg(target_os = "illumos")] { @@ -472,10 +463,7 @@ pub unsafe fn resolve(what: ResolveWhat<'_>, cb: &mut dyn FnMut(&super::Symbol)) } if !any_frames { if let Some(name) = cx.object.search_symtab(addr as u64) { - call(Symbol::Symtab { - addr: addr as *mut c_void, - name, - }); + call(Symbol::Symtab { name }); } } }); @@ -491,7 +479,7 @@ pub enum Symbol<'a> { }, /// Couldn't find debug information, but we found it in the symbol table of /// the elf executable. - Symtab { addr: *mut c_void, name: &'a [u8] }, + Symtab { name: &'a [u8] }, } impl Symbol<'_> { diff --git a/src/symbolize/mod.rs b/src/symbolize/mod.rs index b01fe86d6..9706835e6 100644 --- a/src/symbolize/mod.rs +++ b/src/symbolize/mod.rs @@ -292,32 +292,15 @@ cfg_if::cfg_if! { OptionCppSymbol(None) } } - } else { - use core::marker::PhantomData; - - // Make sure to keep this zero-sized, so that the `cpp_demangle` feature - // has no cost when disabled. - struct OptionCppSymbol<'a>(PhantomData<&'a ()>); - - impl<'a> OptionCppSymbol<'a> { - fn parse(_: &'a [u8]) -> OptionCppSymbol<'a> { - OptionCppSymbol(PhantomData) - } - - fn none() -> OptionCppSymbol<'a> { - OptionCppSymbol(PhantomData) - } - } } } /// A wrapper around a symbol name to provide ergonomic accessors to the /// demangled name, the raw bytes, the raw string, etc. -// Allow dead code for when the `cpp_demangle` feature is not enabled. -#[allow(dead_code)] pub struct SymbolName<'a> { bytes: &'a [u8], demangled: Option>, + #[cfg(feature = "cpp_demangle")] cpp_demangled: OptionCppSymbol<'a>, } @@ -327,6 +310,7 @@ impl<'a> SymbolName<'a> { let str_bytes = str::from_utf8(bytes).ok(); let demangled = str_bytes.and_then(|s| try_demangle(s).ok()); + #[cfg(feature = "cpp_demangle")] let cpp = if demangled.is_none() { OptionCppSymbol::parse(bytes) } else { @@ -336,6 +320,7 @@ impl<'a> SymbolName<'a> { SymbolName { bytes: bytes, demangled: demangled, + #[cfg(feature = "cpp_demangle")] cpp_demangled: cpp, } } @@ -380,65 +365,45 @@ fn format_symbol_name( Ok(()) } -cfg_if::cfg_if! { - if #[cfg(feature = "cpp_demangle")] { - impl<'a> fmt::Display for SymbolName<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(ref s) = self.demangled { - s.fmt(f) - } else if let Some(ref cpp) = self.cpp_demangled.0 { - cpp.fmt(f) - } else { - format_symbol_name(fmt::Display::fmt, self.bytes, f) - } - } +impl<'a> fmt::Display for SymbolName<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(ref s) = self.demangled { + return s.fmt(f); } - } else { - impl<'a> fmt::Display for SymbolName<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(ref s) = self.demangled { - s.fmt(f) - } else { - format_symbol_name(fmt::Display::fmt, self.bytes, f) - } + + #[cfg(feature = "cpp_demangle")] + { + if let Some(ref cpp) = self.cpp_demangled.0 { + return cpp.fmt(f); } } + + format_symbol_name(fmt::Display::fmt, self.bytes, f) } } -cfg_if::cfg_if! { - if #[cfg(all(feature = "std", feature = "cpp_demangle"))] { - impl<'a> fmt::Debug for SymbolName<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use std::fmt::Write; - - if let Some(ref s) = self.demangled { - return s.fmt(f) - } - - // This may to print if the demangled symbol isn't actually - // valid, so handle the error here gracefully by not propagating - // it outwards. - if let Some(ref cpp) = self.cpp_demangled.0 { - let mut s = String::new(); - if write!(s, "{cpp}").is_ok() { - return s.fmt(f) - } - } - - format_symbol_name(fmt::Debug::fmt, self.bytes, f) - } +impl<'a> fmt::Debug for SymbolName<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(ref s) = self.demangled { + return s.fmt(f); } - } else { - impl<'a> fmt::Debug for SymbolName<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(ref s) = self.demangled { - s.fmt(f) - } else { - format_symbol_name(fmt::Debug::fmt, self.bytes, f) + + #[cfg(all(feature = "std", feature = "cpp_demangle"))] + { + use std::fmt::Write; + + // This may to print if the demangled symbol isn't actually + // valid, so handle the error here gracefully by not propagating + // it outwards. + if let Some(ref cpp) = self.cpp_demangled.0 { + let mut s = String::new(); + if write!(s, "{cpp}").is_ok() { + return s.fmt(f); } } } + + format_symbol_name(fmt::Debug::fmt, self.bytes, f) } } @@ -453,7 +418,7 @@ cfg_if::cfg_if! { /// While this function is always available it doesn't actually do anything on /// most implementations. Libraries like dbghelp or libbacktrace do not provide /// facilities to deallocate state and manage the allocated memory. For now the -/// `gimli-symbolize` feature of this crate is the only feature where this +/// `std` feature of this crate is the only feature where this /// function has any effect. #[cfg(feature = "std")] pub fn clear_symbol_cache() { diff --git a/src/windows.rs b/src/windows.rs index a95762d35..305da0570 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -38,6 +38,8 @@ cfg_if::cfg_if! { pub use winapi::um::tlhelp32::*; pub use winapi::um::winbase::*; pub use winapi::um::winnt::*; + pub use winapi::um::winnls::*; + pub use winapi::um::stringapiset::*; // Work around winapi not having this function on aarch64. #[cfg(target_arch = "aarch64")] @@ -379,6 +381,7 @@ ffi! { pub const INVALID_HANDLE_VALUE: HANDLE = -1isize as HANDLE; pub const MAX_MODULE_NAME32: usize = 255; pub const MAX_PATH: usize = 260; + pub const CP_UTF8: u32 = 65001; pub type DWORD = u32; pub type PDWORD = *mut u32; @@ -456,6 +459,16 @@ ffi! { lpme: LPMODULEENTRY32W, ) -> BOOL; pub fn lstrlenW(lpstring: PCWSTR) -> i32; + pub fn WideCharToMultiByte( + codepage: u32, + dwflags: u32, + lpwidecharstr: PCWSTR, + cchwidechar: i32, + lpmultibytestr: *mut i8, + cbmultibyte: i32, + lpdefaultchar: *const i8, + lpuseddefaultchar: *mut BOOL + ) -> i32; } } diff --git a/tests/accuracy/main.rs b/tests/accuracy/main.rs index 568acab84..b50e4451f 100644 --- a/tests/accuracy/main.rs +++ b/tests/accuracy/main.rs @@ -1,3 +1,4 @@ +#![cfg(dbginfo = "collapsible")] mod auxiliary; macro_rules! pos { @@ -6,6 +7,7 @@ macro_rules! pos { }; } +#[collapse_debuginfo(yes)] macro_rules! check { ($($pos:expr),*) => ({ verify(&[$($pos,)* pos!()]); @@ -29,7 +31,7 @@ fn doit() { dir.pop(); if cfg!(windows) { dir.push("dylib_dep.dll"); - } else if cfg!(target_os = "macos") { + } else if cfg!(target_vendor = "apple") { dir.push("libdylib_dep.dylib"); } else if cfg!(target_os = "aix") { dir.push("libdylib_dep.a"); diff --git a/tests/smoke.rs b/tests/smoke.rs index b47f02323..9c87f530e 100644 --- a/tests/smoke.rs +++ b/tests/smoke.rs @@ -230,18 +230,6 @@ fn many_threads() { } } -#[test] -#[cfg(feature = "rustc-serialize")] -fn is_rustc_serialize() { - extern crate rustc_serialize; - - fn is_encode() {} - fn is_decode() {} - - is_encode::(); - is_decode::(); -} - #[test] #[cfg(feature = "serde")] fn is_serde() {