std\backtrace\src\backtrace/
win64.rs

1//! Backtrace strategy for Windows `x86_64` and `aarch64` platforms.
2//!
3//! This module contains the ability to capture a backtrace on Windows using
4//!  `RtlVirtualUnwind` to walk the stack one frame at a time. This function is much faster than using
5//! `dbghelp!StackWalk*` because it does not load debug info to report inlined frames.
6//! We still report inlined frames during symbolization by consulting the appropriate
7//! `dbghelp` functions.
8
9use super::super::windows_sys::*;
10use core::ffi::c_void;
11
12#[derive(Clone, Copy)]
13pub struct Frame {
14    base_address: *mut c_void,
15    ip: *mut c_void,
16    sp: *mut c_void,
17    #[cfg(not(target_env = "gnu"))]
18    inline_context: Option<u32>,
19}
20
21// we're just sending around raw pointers and reading them, never interpreting
22// them so this should be safe to both send and share across threads.
23unsafe impl Send for Frame {}
24unsafe impl Sync for Frame {}
25
26impl Frame {
27    pub fn ip(&self) -> *mut c_void {
28        self.ip
29    }
30
31    pub fn sp(&self) -> *mut c_void {
32        self.sp
33    }
34
35    pub fn symbol_address(&self) -> *mut c_void {
36        self.ip
37    }
38
39    pub fn module_base_address(&self) -> Option<*mut c_void> {
40        Some(self.base_address)
41    }
42
43    #[cfg(not(target_env = "gnu"))]
44    pub fn inline_context(&self) -> Option<u32> {
45        self.inline_context
46    }
47}
48
49#[repr(C, align(16))] // required by `CONTEXT`, is a FIXME in windows metadata right now
50struct MyContext(CONTEXT);
51
52#[cfg(any(target_arch = "x86_64", target_arch = "arm64ec"))]
53impl MyContext {
54    #[inline(always)]
55    fn ip(&self) -> u64 {
56        self.0.Rip
57    }
58
59    #[inline(always)]
60    fn sp(&self) -> u64 {
61        self.0.Rsp
62    }
63}
64
65#[cfg(target_arch = "aarch64")]
66impl MyContext {
67    #[inline(always)]
68    fn ip(&self) -> usize {
69        self.0.Pc as usize
70    }
71
72    #[inline(always)]
73    fn sp(&self) -> usize {
74        self.0.Sp as usize
75    }
76}
77
78#[inline(always)]
79pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) {
80    use core::ptr;
81
82    // Capture the initial context to start walking from.
83    // FIXME: shouldn't this have a Default impl?
84    let mut context = unsafe { core::mem::zeroed::<MyContext>() };
85    unsafe { RtlCaptureContext(&mut context.0) };
86
87    loop {
88        let ip = context.ip();
89
90        // The base address of the module containing the function will be stored here
91        // when RtlLookupFunctionEntry returns successfully.
92        let mut base = 0;
93        // We use the `RtlLookupFunctionEntry` function in kernel32 which allows
94        // us to backtrace through JIT frames.
95        // Note that `RtlLookupFunctionEntry` only works for in-process backtraces,
96        // but that's all we support anyway, so it all lines up well.
97        let fn_entry = unsafe { RtlLookupFunctionEntry(ip, &mut base, ptr::null_mut()) };
98        if fn_entry.is_null() {
99            // No function entry could be found - this may indicate a corrupt
100            // stack or that a binary was unloaded (amongst other issues). Stop
101            // walking and don't call the callback as we can't be confident in
102            // this frame or the rest of the stack.
103            break;
104        }
105
106        let frame = super::Frame {
107            inner: Frame {
108                base_address: base as *mut c_void,
109                ip: ip as *mut c_void,
110                sp: context.sp() as *mut c_void,
111                #[cfg(not(target_env = "gnu"))]
112                inline_context: None,
113            },
114        };
115
116        // We've loaded all the info about the current frame, so now call the
117        // callback.
118        if !cb(&frame) {
119            // Callback told us to stop, so we're done.
120            break;
121        }
122
123        // Unwind to the next frame.
124        let previous_ip = ip;
125        let previous_sp = context.sp();
126        let mut handler_data = 0usize;
127        let mut establisher_frame = 0;
128        unsafe {
129            RtlVirtualUnwind(
130                0,
131                base,
132                ip,
133                fn_entry,
134                &mut context.0,
135                ptr::addr_of_mut!(handler_data).cast::<*mut c_void>(),
136                &mut establisher_frame,
137                ptr::null_mut(),
138            );
139        }
140
141        // RtlVirtualUnwind indicates the end of the stack in two different ways:
142        // * On x64, it sets the instruction pointer to 0.
143        // * On ARM64, it leaves the context unchanged (easiest way to check is
144        //   to see if the instruction and stack pointers are the same).
145        // If we detect either of these, then unwinding is completed.
146        let ip = context.ip();
147        if ip == 0 || (ip == previous_ip && context.sp() == previous_sp) {
148            break;
149        }
150    }
151}