From fb115a75945a70b0c7156199ebf0a3ef3ee43a96 Mon Sep 17 00:00:00 2001 From: Christian Jordan Date: Sun, 1 May 2022 01:36:19 +0200 Subject: [PATCH 01/13] Added tracing of threads other than the calling thread This probably wont compile on anything other than windows currently. Previously RtlCaptureContext was used to effortlessly get the calling thread's context. Instead pass in a handle to trace and then use GetThreadContext to get the context of the thread. This should allow reading the backtrace of threads other than the calling thread. This is needed in the windows implementation of pprof-rs. Some notes: - The thread handle must be opened with THREAD_GET_CONTEXT access (see windows docs) - Thread must be suspended before the trace call was made (see windows docs) - A crossplatform interface/implementation might not be possible. - I have no idea whether the stacktrace is even valid. It makes sense if it is? --- src/backtrace/dbghelp.rs | 25 +++++++++++++++++++++---- src/backtrace/mod.rs | 7 ++++++- src/lib.rs | 2 +- src/windows.rs | 1 + 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/backtrace/dbghelp.rs b/src/backtrace/dbghelp.rs index ba0f05f3b..aebdc639c 100644 --- a/src/backtrace/dbghelp.rs +++ b/src/backtrace/dbghelp.rs @@ -88,14 +88,31 @@ impl Frame { #[repr(C, align(16))] // required by `CONTEXT`, is a FIXME in winapi right now struct MyContext(CONTEXT); -#[inline(always)] -pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) { +//#[inline(always)] +pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool, thread: *mut c_void) { // 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 { + const CONTEXT_i386: u32 = 0x10000; + const CONTEXT_CONTROL: u32 = CONTEXT_i386 | 0x01; // SS:SP, CS:IP, FLAGS, B; + const CONTEXT_INTEGER: u32 = CONTEXT_i386 | 0x02; // AX, BX, CX, DX, SI, D; + const CONTEXT_SEGMENTS: u32 = CONTEXT_i386 | 0x04; // DS, ES, FS, G; + const CONTEXT_FLOATING_POINT: u32 = CONTEXT_i386 | 0x08; // 387 stat; + const CONTEXT_DEBUG_REGISTERS: u32 = CONTEXT_i386 | 0x10; // DB 0-3,6,; + const CONTEXT_EXTENDED_REGISTERS: u32 = CONTEXT_i386 | 0x20; // cpu specific extension; + const CONTEXT_ALL: u32 = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_EXTENDED_REGISTERS; + + context.0.ContextFlags = CONTEXT_ALL; // TODO: Narrow down flags + let status = GetThreadContext(thread, &mut context.0); + if status == 0 { + return // GetThreadContext failed + } + } + // 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..14661c359 100644 --- a/src/backtrace/mod.rs +++ b/src/backtrace/mod.rs @@ -63,7 +63,12 @@ pub fn trace bool>(cb: F) { /// /// See information on `trace` for caveats on `cb` panicking. pub unsafe fn trace_unsynchronized bool>(mut cb: F) { - trace_imp(&mut cb) + trace_imp(&mut cb, 0 as _) +} + +/// TODO docs +pub unsafe fn trace_thread_unsynchronized bool>(mut cb: F, thread: *mut c_void) { + trace_imp(&mut cb, thread) } /// A trait representing one frame of a backtrace, yielded to the `trace` diff --git a/src/lib.rs b/src/lib.rs index e5dea3387..f8327facd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,7 +110,7 @@ extern crate std; #[allow(unused_extern_crates)] extern crate alloc; -pub use self::backtrace::{trace_unsynchronized, Frame}; +pub use self::backtrace::{trace_unsynchronized, trace_thread_unsynchronized, Frame}; mod backtrace; pub use self::symbolize::resolve_frame_unsynchronized; diff --git a/src/windows.rs b/src/windows.rs index d091874f1..6f7f363ee 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -375,6 +375,7 @@ ffi! { pub fn GetCurrentProcess() -> HANDLE; pub fn GetCurrentThread() -> HANDLE; pub fn RtlCaptureContext(ContextRecord: PCONTEXT) -> (); + pub fn GetThreadContext(ThreadHandle: HANDLE, ContextRecord: PCONTEXT) -> DWORD; pub fn LoadLibraryA(a: *const i8) -> HMODULE; pub fn GetProcAddress(h: HMODULE, name: *const i8) -> FARPROC; pub fn GetModuleHandleA(name: *const i8) -> HMODULE; From 7f3474fa6e31a8a1cda08fdc789caf2d24a6f275 Mon Sep 17 00:00:00 2001 From: Christian Jordan Date: Sun, 1 May 2022 14:07:07 +0200 Subject: [PATCH 02/13] Moved SuspendThread and ResumeThread inside of trace_impl on windows --- src/backtrace/dbghelp.rs | 28 ++++++++++++++++------------ src/backtrace/mod.rs | 2 +- src/windows.rs | 11 +++++++++++ 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/backtrace/dbghelp.rs b/src/backtrace/dbghelp.rs index aebdc639c..15cbfbb9f 100644 --- a/src/backtrace/dbghelp.rs +++ b/src/backtrace/dbghelp.rs @@ -97,19 +97,23 @@ pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool, thread: *mut c_vo if thread == GetCurrentThread() || thread.is_null() { RtlCaptureContext(&mut context.0); } else { - const CONTEXT_i386: u32 = 0x10000; - const CONTEXT_CONTROL: u32 = CONTEXT_i386 | 0x01; // SS:SP, CS:IP, FLAGS, B; - const CONTEXT_INTEGER: u32 = CONTEXT_i386 | 0x02; // AX, BX, CX, DX, SI, D; - const CONTEXT_SEGMENTS: u32 = CONTEXT_i386 | 0x04; // DS, ES, FS, G; - const CONTEXT_FLOATING_POINT: u32 = CONTEXT_i386 | 0x08; // 387 stat; - const CONTEXT_DEBUG_REGISTERS: u32 = CONTEXT_i386 | 0x10; // DB 0-3,6,; - const CONTEXT_EXTENDED_REGISTERS: u32 = CONTEXT_i386 | 0x20; // cpu specific extension; - const CONTEXT_ALL: u32 = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_EXTENDED_REGISTERS; - - context.0.ContextFlags = CONTEXT_ALL; // TODO: Narrow down flags + // 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 is most definitely more pitfalls i haven't thought + // of or encountered. This is windows after all. + + 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 status == 0 { - return // GetThreadContext failed + if ResumeThread(thread) as i32 == -1 || status == 0{ + return; } } diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs index 14661c359..452ec5cb1 100644 --- a/src/backtrace/mod.rs +++ b/src/backtrace/mod.rs @@ -67,7 +67,7 @@ pub unsafe fn trace_unsynchronized bool>(mut cb: F) { } /// TODO docs -pub unsafe fn trace_thread_unsynchronized bool>(mut cb: F, thread: *mut c_void) { +pub unsafe fn trace_thread_unsynchronized bool>(thread: *mut c_void, mut cb: F) { trace_imp(&mut cb, thread) } diff --git a/src/windows.rs b/src/windows.rs index 6f7f363ee..43930153c 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -346,6 +346,15 @@ 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 CONTEXT_i386: u32 = 0x10000; + pub const CONTEXT_CONTROL: u32 = CONTEXT_i386 | 0x01; // SS:SP, CS:IP, FLAGS, B; + pub const CONTEXT_INTEGER: u32 = CONTEXT_i386 | 0x02; // AX, BX, CX, DX, SI, D; + pub const CONTEXT_SEGMENTS: u32 = CONTEXT_i386 | 0x04; // DS, ES, FS, G; + pub const CONTEXT_FLOATING_POINT: u32 = CONTEXT_i386 | 0x08; // 387 stat; + pub const CONTEXT_DEBUG_REGISTERS: u32 = CONTEXT_i386 | 0x10; // DB 0-3,6,; + pub const CONTEXT_EXTENDED_REGISTERS: u32 = CONTEXT_i386 | 0x20; // cpu specific extension; + pub const CONTEXT_ALL: u32 = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_EXTENDED_REGISTERS; + pub type DWORD = u32; pub type PDWORD = *mut u32; @@ -376,6 +385,8 @@ ffi! { pub fn GetCurrentThread() -> HANDLE; pub fn RtlCaptureContext(ContextRecord: PCONTEXT) -> (); pub fn GetThreadContext(ThreadHandle: HANDLE, ContextRecord: PCONTEXT) -> DWORD; + 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; From f433ffdd5140a0ba4fbd959b111091415d08d265 Mon Sep 17 00:00:00 2001 From: Christian Jordan Date: Sun, 1 May 2022 14:07:38 +0200 Subject: [PATCH 03/13] Added example for capturing backtrace of non calling thread --- examples/other_thread.rs | 45 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 examples/other_thread.rs diff --git a/examples/other_thread.rs b/examples/other_thread.rs new file mode 100644 index 000000000..aa2131acd --- /dev/null +++ b/examples/other_thread.rs @@ -0,0 +1,45 @@ +// Windows only currently +use std::os::windows::prelude::AsRawHandle; +use backtrace::{Backtrace, BacktraceFrame}; + + +fn worker() { + foo(); +} +fn foo() { + bar() +} +fn bar() { + baz() +} +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!(""); + }; +} + + +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); +} From f7d4e6d7344647a9df8df5e98894df0bc05f3ac2 Mon Sep 17 00:00:00 2001 From: Amin Yahyaabadi Date: Sat, 9 Jul 2022 18:23:37 -0700 Subject: [PATCH 04/13] ci: disable fail-fast for GitHub Actions --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) 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 From 68e192725858dfba60b3c37d2cc5ad1c727da9f2 Mon Sep 17 00:00:00 2001 From: Amin Yahyaabadi Date: Sat, 9 Jul 2022 18:40:54 -0700 Subject: [PATCH 05/13] fix: add a separate trace_thread function --- examples/other_thread.rs | 10 ++++------ src/backtrace/dbghelp.rs | 13 +++++++++---- src/backtrace/mod.rs | 23 +++++++++++++++++++---- src/lib.rs | 2 +- 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/examples/other_thread.rs b/examples/other_thread.rs index aa2131acd..44abd96ab 100644 --- a/examples/other_thread.rs +++ b/examples/other_thread.rs @@ -1,7 +1,6 @@ // Windows only currently -use std::os::windows::prelude::AsRawHandle; use backtrace::{Backtrace, BacktraceFrame}; - +use std::os::windows::prelude::AsRawHandle; fn worker() { foo(); @@ -18,22 +17,21 @@ fn baz() { //std::thread::sleep(std::time::Duration::from_millis(1000)); loop { print!(""); - }; + } } - 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| { + backtrace::trace_thread_unsynchronized(os_handle, |frame| { frames.push(BacktraceFrame::from(frame.clone())); true }); diff --git a/src/backtrace/dbghelp.rs b/src/backtrace/dbghelp.rs index 15cbfbb9f..9577be4e7 100644 --- a/src/backtrace/dbghelp.rs +++ b/src/backtrace/dbghelp.rs @@ -88,8 +88,14 @@ impl Frame { #[repr(C, align(16))] // required by `CONTEXT`, is a FIXME in winapi right now struct MyContext(CONTEXT); -//#[inline(always)] -pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool, thread: *mut c_void) { +#[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: *mut c_void) { // Allocate necessary structures for doing the stack walk let process = GetCurrentProcess(); @@ -112,12 +118,11 @@ pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool, thread: *mut c_vo return; } let status = GetThreadContext(thread, &mut context.0); - if ResumeThread(thread) as i32 == -1 || status == 0{ + if ResumeThread(thread) as i32 == -1 || status == 0 { return; } } - // Ensure this process's symbols are initialized let dbghelp = match dbghelp::init() { Ok(dbghelp) => dbghelp, diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs index 452ec5cb1..97d0050bd 100644 --- a/src/backtrace/mod.rs +++ b/src/backtrace/mod.rs @@ -1,6 +1,8 @@ use core::ffi::c_void; use core::fmt; +use self::dbghelp::trace_thread; + /// Inspects the current call-stack, passing all active frames into the closure /// provided to calculate a stack trace. /// @@ -63,12 +65,25 @@ pub fn trace bool>(cb: F) { /// /// See information on `trace` for caveats on `cb` panicking. pub unsafe fn trace_unsynchronized bool>(mut cb: F) { - trace_imp(&mut cb, 0 as _) + trace_imp(&mut cb) } -/// TODO docs -pub unsafe fn trace_thread_unsynchronized bool>(thread: *mut c_void, mut cb: F) { - trace_imp(&mut cb, thread) +/// Similar to [trace_unsynchronized], but additionally, it supports tracing a thread other than the current thread. +/// +/// The function gets the traced thread's handle as its first argument. +/// +/// 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. +pub unsafe fn trace_thread_unsynchronized bool>( + thread: *mut c_void, + mut cb: F, +) { + trace_thread(&mut cb, thread) } /// A trait representing one frame of a backtrace, yielded to the `trace` diff --git a/src/lib.rs b/src/lib.rs index f8327facd..310e51613 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,7 +110,7 @@ extern crate std; #[allow(unused_extern_crates)] extern crate alloc; -pub use self::backtrace::{trace_unsynchronized, trace_thread_unsynchronized, Frame}; +pub use self::backtrace::{trace_thread_unsynchronized, trace_unsynchronized, Frame}; mod backtrace; pub use self::symbolize::resolve_frame_unsynchronized; From c5163cda15e48f0fc7247962624a977cac6a93f4 Mon Sep 17 00:00:00 2001 From: Amin Yahyaabadi Date: Sat, 9 Jul 2022 18:51:31 -0700 Subject: [PATCH 06/13] fix: enable trace_thread_unsynchronized only on windows and noop --- src/backtrace/mod.rs | 8 +++++--- src/backtrace/noop.rs | 5 +++++ src/lib.rs | 5 ++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs index 97d0050bd..83aa00d35 100644 --- a/src/backtrace/mod.rs +++ b/src/backtrace/mod.rs @@ -1,8 +1,6 @@ use core::ffi::c_void; use core::fmt; -use self::dbghelp::trace_thread; - /// Inspects the current call-stack, passing all active frames into the closure /// provided to calculate a stack trace. /// @@ -79,11 +77,12 @@ pub unsafe fn trace_unsynchronized bool>(mut cb: F) { /// # 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(&mut cb, thread) + trace_thread_imp(&mut cb, thread) } /// A trait representing one frame of a backtrace, yielded to the `trace` @@ -171,12 +170,15 @@ 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; } else { mod noop; use self::noop::trace as trace_imp; + use self::noop::trace_thread as trace_thread_imp; + use self::noop::trace_thread_unsynchronized; pub(crate) use self::noop::Frame as FrameImp; } } diff --git a/src/backtrace/noop.rs b/src/backtrace/noop.rs index 7bcea67aa..d8280316d 100644 --- a/src/backtrace/noop.rs +++ b/src/backtrace/noop.rs @@ -6,6 +6,11 @@ use core::ffi::c_void; #[inline(always)] pub fn trace(_cb: &mut dyn FnMut(&super::Frame) -> bool) {} +#[inline(always)] +pub fn trace_thread(cb: &mut dyn FnMut(&super::Frame) -> bool, thread: *mut c_void) {} + +pub fn trace_thread_unsynchronized bool>(thread: *mut c_void, mut cb: F) {} + #[derive(Clone)] pub struct Frame; diff --git a/src/lib.rs b/src/lib.rs index 310e51613..eeca444a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,7 +110,10 @@ extern crate std; #[allow(unused_extern_crates)] extern crate alloc; -pub use self::backtrace::{trace_thread_unsynchronized, trace_unsynchronized, Frame}; +#[cfg(all(windows, not(target_vendor = "uwp")))] +pub use self::backtrace::trace_thread_unsynchronized; + +pub use self::backtrace::{trace_unsynchronized, Frame}; mod backtrace; pub use self::symbolize::resolve_frame_unsynchronized; From 897795ec936b2d56b16ceec762f674c20d73dd52 Mon Sep 17 00:00:00 2001 From: Amin Yahyaabadi Date: Sat, 9 Jul 2022 19:12:20 -0700 Subject: [PATCH 07/13] fix: use thread HANDLE in trace_thread --- src/backtrace/dbghelp.rs | 2 +- src/backtrace/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backtrace/dbghelp.rs b/src/backtrace/dbghelp.rs index 9577be4e7..3c59e33fa 100644 --- a/src/backtrace/dbghelp.rs +++ b/src/backtrace/dbghelp.rs @@ -95,7 +95,7 @@ pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) { } #[inline(always)] -pub unsafe fn trace_thread(cb: &mut dyn FnMut(&super::Frame) -> bool, thread: *mut c_void) { +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(); diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs index 83aa00d35..559f2902a 100644 --- a/src/backtrace/mod.rs +++ b/src/backtrace/mod.rs @@ -82,7 +82,7 @@ pub unsafe fn trace_thread_unsynchronized bool>( thread: *mut c_void, mut cb: F, ) { - trace_thread_imp(&mut cb, thread) + trace_thread_imp(&mut cb, thread as super::windows::HANDLE); } /// A trait representing one frame of a backtrace, yielded to the `trace` From cff4ef41487eb1b45d5511d87d3db6cbd4e3e7f0 Mon Sep 17 00:00:00 2001 From: Amin Yahyaabadi Date: Sat, 9 Jul 2022 19:22:39 -0700 Subject: [PATCH 08/13] test: make the other_thread example noop on other platforms --- examples/other_thread.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/examples/other_thread.rs b/examples/other_thread.rs index 44abd96ab..e7fe23736 100644 --- a/examples/other_thread.rs +++ b/examples/other_thread.rs @@ -1,25 +1,34 @@ -// Windows only currently +#[cfg(all(windows, not(target_vendor = "uwp")))] use backtrace::{Backtrace, BacktraceFrame}; +#[cfg(all(windows, not(target_vendor = "uwp")))] use std::os::windows::prelude::AsRawHandle; +#[cfg(all(windows, not(target_vendor = "uwp")))] fn worker() { foo(); } + +#[cfg(all(windows, not(target_vendor = "uwp")))] fn foo() { bar() } + +#[cfg(all(windows, not(target_vendor = "uwp")))] fn bar() { baz() } + +#[cfg(all(windows, not(target_vendor = "uwp")))] 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)); + std::thread::sleep(std::time::Duration::from_millis(1000)); loop { print!(""); } } +#[cfg(all(windows, not(target_vendor = "uwp")))] fn main() { let thread = std::thread::spawn(|| { worker(); @@ -41,3 +50,8 @@ fn main() { bt.resolve(); println!("{:?}", bt); } + +#[cfg(not(all(windows, not(target_vendor = "uwp"))))] +fn main() { + println!("This example is skipped on non Windows platforms"); +} From acfa2c01232f186cbf39d9e84e59a39ce31ee4fb Mon Sep 17 00:00:00 2001 From: Amin Yahyaabadi Date: Sat, 9 Jul 2022 19:30:47 -0700 Subject: [PATCH 09/13] fix: make CONTEXT_ALL cross-architecture --- src/windows.rs | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/windows.rs b/src/windows.rs index 43930153c..5d774b90d 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -346,15 +346,6 @@ 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 CONTEXT_i386: u32 = 0x10000; - pub const CONTEXT_CONTROL: u32 = CONTEXT_i386 | 0x01; // SS:SP, CS:IP, FLAGS, B; - pub const CONTEXT_INTEGER: u32 = CONTEXT_i386 | 0x02; // AX, BX, CX, DX, SI, D; - pub const CONTEXT_SEGMENTS: u32 = CONTEXT_i386 | 0x04; // DS, ES, FS, G; - pub const CONTEXT_FLOATING_POINT: u32 = CONTEXT_i386 | 0x08; // 387 stat; - pub const CONTEXT_DEBUG_REGISTERS: u32 = CONTEXT_i386 | 0x10; // DB 0-3,6,; - pub const CONTEXT_EXTENDED_REGISTERS: u32 = CONTEXT_i386 | 0x20; // cpu specific extension; - pub const CONTEXT_ALL: u32 = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_EXTENDED_REGISTERS; - pub type DWORD = u32; pub type PDWORD = *mut u32; @@ -530,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")] @@ -575,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")] @@ -642,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)] From 9f437216e8c75d4e5ba13389c84599e77ed74eee Mon Sep 17 00:00:00 2001 From: Amin Yahyaabadi Date: Sat, 9 Jul 2022 19:38:36 -0700 Subject: [PATCH 10/13] fix: fix the GetThreadContext return type https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadcontext --- src/windows.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/windows.rs b/src/windows.rs index 5d774b90d..a29964506 100644 --- a/src/windows.rs +++ b/src/windows.rs @@ -375,7 +375,7 @@ ffi! { pub fn GetCurrentProcess() -> HANDLE; pub fn GetCurrentThread() -> HANDLE; pub fn RtlCaptureContext(ContextRecord: PCONTEXT) -> (); - pub fn GetThreadContext(ThreadHandle: HANDLE, ContextRecord: PCONTEXT) -> DWORD; + 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; From 0cd54c54d49b164b137cb4a473638537c0e5342e Mon Sep 17 00:00:00 2001 From: Amin Yahyaabadi Date: Sat, 9 Jul 2022 19:47:37 -0700 Subject: [PATCH 11/13] fix: remove noop trace_thread --- src/backtrace/mod.rs | 2 -- src/backtrace/noop.rs | 5 ----- 2 files changed, 7 deletions(-) diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs index 559f2902a..a8d9e7397 100644 --- a/src/backtrace/mod.rs +++ b/src/backtrace/mod.rs @@ -177,8 +177,6 @@ cfg_if::cfg_if! { } else { mod noop; use self::noop::trace as trace_imp; - use self::noop::trace_thread as trace_thread_imp; - use self::noop::trace_thread_unsynchronized; pub(crate) use self::noop::Frame as FrameImp; } } diff --git a/src/backtrace/noop.rs b/src/backtrace/noop.rs index d8280316d..7bcea67aa 100644 --- a/src/backtrace/noop.rs +++ b/src/backtrace/noop.rs @@ -6,11 +6,6 @@ use core::ffi::c_void; #[inline(always)] pub fn trace(_cb: &mut dyn FnMut(&super::Frame) -> bool) {} -#[inline(always)] -pub fn trace_thread(cb: &mut dyn FnMut(&super::Frame) -> bool, thread: *mut c_void) {} - -pub fn trace_thread_unsynchronized bool>(thread: *mut c_void, mut cb: F) {} - #[derive(Clone)] pub struct Frame; From 97396ddabb43162b96bac7024f0771fd4633f719 Mon Sep 17 00:00:00 2001 From: Amin Yahyaabadi Date: Sat, 9 Jul 2022 19:50:40 -0700 Subject: [PATCH 12/13] test: make other_thread example no-op on no-std --- examples/other_thread.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/other_thread.rs b/examples/other_thread.rs index e7fe23736..2073895cf 100644 --- a/examples/other_thread.rs +++ b/examples/other_thread.rs @@ -1,24 +1,24 @@ -#[cfg(all(windows, not(target_vendor = "uwp")))] +#[cfg(all(windows, not(target_vendor = "uwp"), feature = "std"))] use backtrace::{Backtrace, BacktraceFrame}; -#[cfg(all(windows, not(target_vendor = "uwp")))] +#[cfg(all(windows, not(target_vendor = "uwp"), feature = "std"))] use std::os::windows::prelude::AsRawHandle; -#[cfg(all(windows, not(target_vendor = "uwp")))] +#[cfg(all(windows, not(target_vendor = "uwp"), feature = "std"))] fn worker() { foo(); } -#[cfg(all(windows, not(target_vendor = "uwp")))] +#[cfg(all(windows, not(target_vendor = "uwp"), feature = "std"))] fn foo() { bar() } -#[cfg(all(windows, not(target_vendor = "uwp")))] +#[cfg(all(windows, not(target_vendor = "uwp"), feature = "std"))] fn bar() { baz() } -#[cfg(all(windows, not(target_vendor = "uwp")))] +#[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 @@ -28,7 +28,7 @@ fn baz() { } } -#[cfg(all(windows, not(target_vendor = "uwp")))] +#[cfg(all(windows, not(target_vendor = "uwp"), feature = "std"))] fn main() { let thread = std::thread::spawn(|| { worker(); @@ -51,7 +51,7 @@ fn main() { println!("{:?}", bt); } -#[cfg(not(all(windows, not(target_vendor = "uwp"))))] +#[cfg(not(all(windows, not(target_vendor = "uwp"), feature = "std")))] fn main() { - println!("This example is skipped on non Windows platforms"); + println!("This example is skipped on non-Windows or no-std platforms"); } From fa964d1d60c5c7eda5c664b1e136f5d58f79e0db Mon Sep 17 00:00:00 2001 From: Amin Yahyaabadi Date: Mon, 11 Jul 2022 00:53:19 -0700 Subject: [PATCH 13/13] docs: add the safety notes to the doc string --- src/backtrace/dbghelp.rs | 4 +--- src/backtrace/mod.rs | 10 +++++++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/backtrace/dbghelp.rs b/src/backtrace/dbghelp.rs index 3c59e33fa..ca9f14617 100644 --- a/src/backtrace/dbghelp.rs +++ b/src/backtrace/dbghelp.rs @@ -108,9 +108,7 @@ pub unsafe fn trace_thread(cb: &mut dyn FnMut(&super::Frame) -> bool, thread: HA // 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 is most definitely more pitfalls i haven't thought - // of or encountered. This is windows after all. + // 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 { diff --git a/src/backtrace/mod.rs b/src/backtrace/mod.rs index a8d9e7397..7f2661d1f 100644 --- a/src/backtrace/mod.rs +++ b/src/backtrace/mod.rs @@ -68,7 +68,15 @@ pub unsafe fn trace_unsynchronized bool>(mut cb: F) { /// Similar to [trace_unsynchronized], but additionally, it supports tracing a thread other than the current thread. /// -/// The function gets the traced thread's handle as its first argument. +/// 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`