diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5f4bd505b..51a2c5c79 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,6 +13,7 @@ jobs: name: Test runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: include: - os: ubuntu-latest diff --git a/examples/other_thread.rs b/examples/other_thread.rs new file mode 100644 index 000000000..2073895cf --- /dev/null +++ b/examples/other_thread.rs @@ -0,0 +1,57 @@ +#[cfg(all(windows, not(target_vendor = "uwp"), feature = "std"))] +use backtrace::{Backtrace, BacktraceFrame}; +#[cfg(all(windows, not(target_vendor = "uwp"), feature = "std"))] +use std::os::windows::prelude::AsRawHandle; + +#[cfg(all(windows, not(target_vendor = "uwp"), feature = "std"))] +fn worker() { + foo(); +} + +#[cfg(all(windows, not(target_vendor = "uwp"), feature = "std"))] +fn foo() { + bar() +} + +#[cfg(all(windows, not(target_vendor = "uwp"), feature = "std"))] +fn bar() { + baz() +} + +#[cfg(all(windows, not(target_vendor = "uwp"), feature = "std"))] +fn baz() { + println!("Hello from thread!"); + // Sleep for simple sync. Can't read thread that has finished running + std::thread::sleep(std::time::Duration::from_millis(1000)); + loop { + print!(""); + } +} + +#[cfg(all(windows, not(target_vendor = "uwp"), feature = "std"))] +fn main() { + let thread = std::thread::spawn(|| { + worker(); + }); + let os_handle = thread.as_raw_handle(); + + // Allow the thread to start + std::thread::sleep(std::time::Duration::from_millis(100)); + + let mut frames = Vec::new(); + unsafe { + backtrace::trace_thread_unsynchronized(os_handle, |frame| { + frames.push(BacktraceFrame::from(frame.clone())); + true + }); + } + + let mut bt = Backtrace::from(frames); + bt.resolve(); + println!("{:?}", bt); +} + +#[cfg(not(all(windows, not(target_vendor = "uwp"), feature = "std")))] +fn main() { + println!("This example is skipped on non-Windows or no-std platforms"); +} diff --git a/src/backtrace/dbghelp.rs b/src/backtrace/dbghelp.rs index ba0f05f3b..ca9f14617 100644 --- a/src/backtrace/dbghelp.rs +++ b/src/backtrace/dbghelp.rs @@ -90,12 +90,36 @@ struct MyContext(CONTEXT); #[inline(always)] pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) { + let thread = GetCurrentThread(); + trace_thread(cb, thread) +} + +#[inline(always)] +pub unsafe fn trace_thread(cb: &mut dyn FnMut(&super::Frame) -> bool, thread: HANDLE) { // Allocate necessary structures for doing the stack walk let process = GetCurrentProcess(); - let thread = GetCurrentThread(); let mut context = mem::zeroed::(); - RtlCaptureContext(&mut context.0); + if thread == GetCurrentThread() || thread.is_null() { + RtlCaptureContext(&mut context.0); + } else { + // The suspending and resuming of threads is very risky. + // It can end in a deadlock if the current thread tries to access + // an object thats been locked by the suspended thread. + // That's why we only do as little work as possible while + // the thread is suspended, and resume it quickly after. + // There might be more pitfalls we haven't thought of or encountered + + context.0.ContextFlags = CONTEXT_ALL; // TODO: Narrow down required flags + if SuspendThread(thread) as i32 == -1 { + ResumeThread(thread); + return; + } + let status = GetThreadContext(thread, &mut context.0); + if ResumeThread(thread) as i32 == -1 || status == 0 { + return; + } + } // Ensure this process's symbols are initialized let dbghelp = match dbghelp::init() { diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs index 93355d744..7f2661d1f 100644 --- a/src/backtrace/mod.rs +++ b/src/backtrace/mod.rs @@ -66,6 +66,33 @@ pub unsafe fn trace_unsynchronized bool>(mut cb: F) { trace_imp(&mut cb) } +/// Similar to [trace_unsynchronized], but additionally, it supports tracing a thread other than the current thread. +/// +/// It gets the traced thread's handle as its first argument. +/// +/// # Safety +/// +/// This function is intended for profiling and debugging. +/// +/// If the given thread handle is not the current thread, +/// this function suspends the thread for a short amount of time to be able to extract the thread context. +/// This might end in a deadlock if the current thread tries to access an object thats been locked by the suspended thread. +/// +/// This function does not have synchronization guarantees but is available +/// when the `std` feature of this crate isn't compiled in. See the `trace` +/// function for more documentation and examples. +/// +/// # Panics +/// +/// See information on `trace` for caveats on `cb` panicking. +#[cfg(all(windows, not(target_vendor = "uwp")))] +pub unsafe fn trace_thread_unsynchronized bool>( + thread: *mut c_void, + mut cb: F, +) { + trace_thread_imp(&mut cb, thread as super::windows::HANDLE); +} + /// A trait representing one frame of a backtrace, yielded to the `trace` /// function of this crate. /// @@ -151,6 +178,7 @@ cfg_if::cfg_if! { } else if #[cfg(all(windows, not(target_vendor = "uwp")))] { mod dbghelp; use self::dbghelp::trace as trace_imp; + use self::dbghelp::trace_thread as trace_thread_imp; pub(crate) use self::dbghelp::Frame as FrameImp; #[cfg(target_env = "msvc")] // only used in dbghelp symbolize pub(crate) use self::dbghelp::StackFrame; diff --git a/src/lib.rs b/src/lib.rs index e5dea3387..eeca444a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,6 +110,9 @@ extern crate std; #[allow(unused_extern_crates)] extern crate alloc; +#[cfg(all(windows, not(target_vendor = "uwp")))] +pub use self::backtrace::trace_thread_unsynchronized; + pub use self::backtrace::{trace_unsynchronized, Frame}; mod backtrace; diff --git a/src/windows.rs b/src/windows.rs index d091874f1..a29964506 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -375,6 +375,9 @@ ffi! { pub fn GetCurrentProcess() -> HANDLE; pub fn GetCurrentThread() -> HANDLE; pub fn RtlCaptureContext(ContextRecord: PCONTEXT) -> (); + pub fn GetThreadContext(ThreadHandle: HANDLE, ContextRecord: PCONTEXT) -> BOOL; + pub fn SuspendThread(ThreadHandle: HANDLE) -> DWORD; + pub fn ResumeThread(ThreadHandle: HANDLE) -> DWORD; pub fn LoadLibraryA(a: *const i8) -> HMODULE; pub fn GetProcAddress(h: HMODULE, name: *const i8) -> FARPROC; pub fn GetModuleHandleA(name: *const i8) -> HMODULE; @@ -518,6 +521,16 @@ ffi! { pub struct ARM64_NT_NEON128 { pub D: [f64; 2], } + + pub const CONTEXT_ARM64: DWORD = 0x00400000; + pub const CONTEXT_CONTROL: DWORD = CONTEXT_ARM64 | 0x00000001; + pub const CONTEXT_INTEGER: DWORD = CONTEXT_ARM64 | 0x00000002; + pub const CONTEXT_FLOATING_POINT: DWORD = CONTEXT_ARM64 | 0x00000004; + pub const CONTEXT_DEBUG_REGISTERS: DWORD = CONTEXT_ARM64 | 0x00000008; + pub const CONTEXT_X18: DWORD = CONTEXT_ARM64 | 0x00000010; + pub const CONTEXT_FULL: DWORD = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT; + pub const CONTEXT_ALL: DWORD = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT + | CONTEXT_DEBUG_REGISTERS | CONTEXT_X18; } #[cfg(target_arch = "x86")] @@ -563,6 +576,18 @@ ffi! { pub RegisterArea: [u8; 80], pub Spare0: DWORD, } + + pub const CONTEXT_i386: DWORD = 0x00010000; + pub const CONTEXT_i486: DWORD = 0x00010000; + pub const CONTEXT_CONTROL: DWORD = CONTEXT_i386 | 0x00000001; + pub const CONTEXT_INTEGER: DWORD = CONTEXT_i386 | 0x00000002; + pub const CONTEXT_SEGMENTS: DWORD = CONTEXT_i386 | 0x00000004; + pub const CONTEXT_FLOATING_POINT: DWORD = CONTEXT_i386 | 0x00000008; + pub const CONTEXT_DEBUG_REGISTERS: DWORD = CONTEXT_i386 | 0x00000010; + pub const CONTEXT_EXTENDED_REGISTERS: DWORD = CONTEXT_i386 | 0x00000020; + pub const CONTEXT_FULL: DWORD = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS; + pub const CONTEXT_ALL: DWORD = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS + | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_EXTENDED_REGISTERS; } #[cfg(target_arch = "x86_64")] @@ -630,6 +655,16 @@ ffi! { pub Low: u64, pub High: i64, } + + pub const CONTEXT_AMD64: DWORD = 0x00100000; + pub const CONTEXT_CONTROL: DWORD = CONTEXT_AMD64 | 0x00000001; + pub const CONTEXT_INTEGER: DWORD = CONTEXT_AMD64 | 0x00000002; + pub const CONTEXT_SEGMENTS: DWORD = CONTEXT_AMD64 | 0x00000004; + pub const CONTEXT_FLOATING_POINT: DWORD = CONTEXT_AMD64 | 0x00000008; + pub const CONTEXT_DEBUG_REGISTERS: DWORD = CONTEXT_AMD64 | 0x00000010; + pub const CONTEXT_FULL: DWORD = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT; + pub const CONTEXT_ALL: DWORD = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS + | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS; } #[repr(C)]