11//! Backtrace strategy for MSVC platforms.
22//!
3- //! This module contains the ability to generate a backtrace on MSVC using one
4- //! of two possible methods. The `StackWalkEx` function is primarily used if
5- //! possible, but not all systems have that. Failing that the `StackWalk64`
6- //! function is used instead. Note that `StackWalkEx` is favored because it
7- //! handles debuginfo internally and returns inline frame information.
3+ //! This module contains the ability to capture a backtrace on MSVC using one
4+ //! of three possible methods. For `x86_64` and `aarch64`, we use `RtlVirtualUnwind`
5+ //! to walk the stack one frame at a time. This function is much faster than using
6+ //! `dbghelp!StackWalk*` because it does not load debug info to report inlined frames.
7+ //! We still report inlined frames during symbolization by consulting the appropriate
8+ //! `dbghelp` functions.
9+ //!
10+ //! For all other platforms, primarily `i686`, the `StackWalkEx` function is used if
11+ //! possible, but not all systems have that. Failing that the `StackWalk64` function
12+ //! is used instead. Note that `StackWalkEx` is favored because it handles debuginfo
13+ //! internally and returns inline frame information.
814//!
915//! Note that all dbghelp support is loaded dynamically, see `src/dbghelp.rs`
1016//! for more information about that.
1117
1218#![ allow( bad_style) ]
1319
14- use super :: super :: { dbghelp , windows:: * } ;
20+ use super :: super :: windows:: * ;
1521use core:: ffi:: c_void;
16- use core:: mem;
17-
18- #[ derive( Clone , Copy ) ]
19- pub enum StackFrame {
20- New ( STACKFRAME_EX ) ,
21- Old ( STACKFRAME64 ) ,
22- }
2322
2423#[ derive( Clone , Copy ) ]
2524pub struct Frame {
26- pub ( crate ) stack_frame : StackFrame ,
2725 base_address : * mut c_void ,
26+ ip : * mut c_void ,
27+ sp : * mut c_void ,
28+ #[ cfg( not( target_env = "gnu" ) ) ]
29+ inline_context : Option < DWORD > ,
2830}
2931
3032// we're just sending around raw pointers and reading them, never interpreting
@@ -34,62 +36,108 @@ unsafe impl Sync for Frame {}
3436
3537impl Frame {
3638 pub fn ip ( & self ) -> * mut c_void {
37- self . addr_pc ( ) . Offset as * mut _
39+ self . ip
3840 }
3941
4042 pub fn sp ( & self ) -> * mut c_void {
41- self . addr_stack ( ) . Offset as * mut _
43+ self . sp
4244 }
4345
4446 pub fn symbol_address ( & self ) -> * mut c_void {
45- self . ip ( )
47+ self . ip
4648 }
4749
4850 pub fn module_base_address ( & self ) -> Option < * mut c_void > {
4951 Some ( self . base_address )
5052 }
5153
52- fn addr_pc ( & self ) -> & ADDRESS64 {
53- match self . stack_frame {
54- StackFrame :: New ( ref new) => & new. AddrPC ,
55- StackFrame :: Old ( ref old) => & old. AddrPC ,
56- }
54+ #[ cfg( not( target_env = "gnu" ) ) ]
55+ pub fn inline_context ( & self ) -> Option < DWORD > {
56+ self . inline_context
5757 }
58+ }
5859
59- fn addr_pc_mut ( & mut self ) -> & mut ADDRESS64 {
60- match self . stack_frame {
61- StackFrame :: New ( ref mut new) => & mut new. AddrPC ,
62- StackFrame :: Old ( ref mut old) => & mut old. AddrPC ,
63- }
60+ #[ repr( C , align( 16 ) ) ] // required by `CONTEXT`, is a FIXME in winapi right now
61+ struct MyContext ( CONTEXT ) ;
62+
63+ #[ cfg( target_arch = "x86_64" ) ]
64+ impl MyContext {
65+ #[ inline( always) ]
66+ fn ip ( & self ) -> DWORD64 {
67+ self . 0 . Rip
6468 }
6569
66- fn addr_frame_mut ( & mut self ) -> & mut ADDRESS64 {
67- match self . stack_frame {
68- StackFrame :: New ( ref mut new) => & mut new. AddrFrame ,
69- StackFrame :: Old ( ref mut old) => & mut old. AddrFrame ,
70- }
70+ #[ inline( always) ]
71+ fn sp ( & self ) -> DWORD64 {
72+ self . 0 . Rsp
7173 }
74+ }
7275
73- fn addr_stack ( & self ) -> & ADDRESS64 {
74- match self . stack_frame {
75- StackFrame :: New ( ref new ) => & new . AddrStack ,
76- StackFrame :: Old ( ref old ) => & old . AddrStack ,
77- }
76+ # [ cfg ( target_arch = "aarch64" ) ]
77+ impl MyContext {
78+ # [ inline ( always ) ]
79+ fn ip ( & self ) -> DWORD64 {
80+ self . 0 . Pc
7881 }
7982
80- fn addr_stack_mut ( & mut self ) -> & mut ADDRESS64 {
81- match self . stack_frame {
82- StackFrame :: New ( ref mut new) => & mut new. AddrStack ,
83- StackFrame :: Old ( ref mut old) => & mut old. AddrStack ,
84- }
83+ #[ inline( always) ]
84+ fn sp ( & self ) -> DWORD64 {
85+ self . 0 . Sp
8586 }
8687}
8788
88- #[ repr( C , align( 16 ) ) ] // required by `CONTEXT`, is a FIXME in winapi right now
89- struct MyContext ( CONTEXT ) ;
89+ #[ cfg( any( target_arch = "x86_64" , target_arch = "aarch64" ) ) ]
90+ #[ inline( always) ]
91+ pub unsafe fn trace ( cb : & mut dyn FnMut ( & super :: Frame ) -> bool ) {
92+ use core:: ptr;
93+
94+ let mut context = core:: mem:: zeroed :: < MyContext > ( ) ;
95+ RtlCaptureContext ( & mut context. 0 ) ;
96+
97+ // Call `RtlVirtualUnwind` to find the previous stack frame, walking until we hit ip = 0.
98+ while context. ip ( ) != 0 {
99+ let mut base = 0 ;
100+
101+ let fn_entry = RtlLookupFunctionEntry ( context. ip ( ) , & mut base, ptr:: null_mut ( ) ) ;
102+ if fn_entry. is_null ( ) {
103+ break ;
104+ }
90105
106+ let frame = super :: Frame {
107+ inner : Frame {
108+ base_address : fn_entry as * mut c_void ,
109+ ip : context. 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+ if !cb ( & frame) {
117+ break ;
118+ }
119+
120+ let mut handler_data = 0usize ;
121+ let mut establisher_frame = 0 ;
122+
123+ RtlVirtualUnwind (
124+ 0 ,
125+ base,
126+ context. ip ( ) ,
127+ fn_entry,
128+ & mut context. 0 ,
129+ & mut handler_data as * mut usize as * mut PVOID ,
130+ & mut establisher_frame,
131+ ptr:: null_mut ( ) ,
132+ ) ;
133+ }
134+ }
135+
136+ #[ cfg( not( any( target_arch = "x86_64" , target_arch = "aarch64" ) ) ) ]
91137#[ inline( always) ]
92138pub unsafe fn trace ( cb : & mut dyn FnMut ( & super :: Frame ) -> bool ) {
139+ use core:: mem;
140+
93141 // Allocate necessary structures for doing the stack walk
94142 let process = GetCurrentProcess ( ) ;
95143 let thread = GetCurrentThread ( ) ;
@@ -98,105 +146,89 @@ pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) {
98146 RtlCaptureContext ( & mut context. 0 ) ;
99147
100148 // Ensure this process's symbols are initialized
101- let dbghelp = match dbghelp:: init ( ) {
149+ let dbghelp = match super :: super :: dbghelp:: init ( ) {
102150 Ok ( dbghelp) => dbghelp,
103151 Err ( ( ) ) => return , // oh well...
104152 } ;
105153
106- // On x86_64 and ARM64 we opt to not use the default `Sym*` functions from
107- // dbghelp for getting the function table and module base. Instead we use
108- // the `RtlLookupFunctionEntry` function in kernel32 which will account for
109- // JIT compiler frames as well. These should be equivalent, but using
110- // `Rtl*` allows us to backtrace through JIT frames.
111- //
112- // Note that `RtlLookupFunctionEntry` only works for in-process backtraces,
113- // but that's all we support anyway, so it all lines up well.
114- cfg_if:: cfg_if! {
115- if #[ cfg( target_pointer_width = "64" ) ] {
116- use core:: ptr;
117-
118- unsafe extern "system" fn function_table_access( _process: HANDLE , addr: DWORD64 ) -> PVOID {
119- let mut base = 0 ;
120- RtlLookupFunctionEntry ( addr, & mut base, ptr:: null_mut( ) ) . cast( )
121- }
122-
123- unsafe extern "system" fn get_module_base( _process: HANDLE , addr: DWORD64 ) -> DWORD64 {
124- let mut base = 0 ;
125- RtlLookupFunctionEntry ( addr, & mut base, ptr:: null_mut( ) ) ;
126- base
127- }
128- } else {
129- let function_table_access = dbghelp. SymFunctionTableAccess64 ( ) ;
130- let get_module_base = dbghelp. SymGetModuleBase64 ( ) ;
131- }
132- }
154+ let function_table_access = dbghelp. SymFunctionTableAccess64 ( ) ;
155+ let get_module_base = dbghelp. SymGetModuleBase64 ( ) ;
133156
134157 let process_handle = GetCurrentProcess ( ) ;
135158
136159 // Attempt to use `StackWalkEx` if we can, but fall back to `StackWalk64`
137160 // since it's in theory supported on more systems.
138161 match ( * dbghelp. dbghelp ( ) ) . StackWalkEx ( ) {
139162 Some ( StackWalkEx ) => {
140- let mut inner: STACKFRAME_EX = mem:: zeroed ( ) ;
141- inner. StackFrameSize = mem:: size_of :: < STACKFRAME_EX > ( ) as DWORD ;
142- let mut frame = super :: Frame {
143- inner : Frame {
144- stack_frame : StackFrame :: New ( inner) ,
145- base_address : 0 as _ ,
146- } ,
147- } ;
148- let image = init_frame ( & mut frame. inner , & context. 0 ) ;
149- let frame_ptr = match & mut frame. inner . stack_frame {
150- StackFrame :: New ( ptr) => ptr as * mut STACKFRAME_EX ,
151- _ => unreachable ! ( ) ,
152- } ;
163+ let mut stack_frame_ex: STACKFRAME_EX = mem:: zeroed ( ) ;
164+ stack_frame_ex. StackFrameSize = mem:: size_of :: < STACKFRAME_EX > ( ) as DWORD ;
165+ stack_frame_ex. AddrPC . Offset = context. 0 . Eip as u64 ;
166+ stack_frame_ex. AddrPC . Mode = AddrModeFlat ;
167+ stack_frame_ex. AddrStack . Offset = context. 0 . Esp as u64 ;
168+ stack_frame_ex. AddrStack . Mode = AddrModeFlat ;
169+ stack_frame_ex. AddrFrame . Offset = context. 0 . Ebp as u64 ;
170+ stack_frame_ex. AddrFrame . Mode = AddrModeFlat ;
153171
154172 while StackWalkEx (
155- image as DWORD ,
173+ IMAGE_FILE_MACHINE_I386 as DWORD ,
156174 process,
157175 thread,
158- frame_ptr ,
159- & mut context. 0 as * mut CONTEXT as * mut _ ,
176+ & mut stack_frame_ex ,
177+ & mut context. 0 as * mut CONTEXT as PVOID ,
160178 None ,
161179 Some ( function_table_access) ,
162180 Some ( get_module_base) ,
163181 None ,
164182 0 ,
165183 ) == TRUE
166184 {
167- frame. inner . base_address = get_module_base ( process_handle, frame. ip ( ) as _ ) as _ ;
185+ let frame = super :: Frame {
186+ inner : Frame {
187+ base_address : get_module_base ( process_handle, stack_frame_ex. AddrPC . Offset )
188+ as * mut c_void ,
189+ ip : stack_frame_ex. AddrPC . Offset as * mut c_void ,
190+ sp : stack_frame_ex. AddrStack . Offset as * mut c_void ,
191+ #[ cfg( not( target_env = "gnu" ) ) ]
192+ inline_context : Some ( stack_frame_ex. InlineFrameContext ) ,
193+ } ,
194+ } ;
168195
169196 if !cb ( & frame) {
170197 break ;
171198 }
172199 }
173200 }
174201 None => {
175- let mut frame = super :: Frame {
176- inner : Frame {
177- stack_frame : StackFrame :: Old ( mem:: zeroed ( ) ) ,
178- base_address : 0 as _ ,
179- } ,
180- } ;
181- let image = init_frame ( & mut frame. inner , & context. 0 ) ;
182- let frame_ptr = match & mut frame. inner . stack_frame {
183- StackFrame :: Old ( ptr) => ptr as * mut STACKFRAME64 ,
184- _ => unreachable ! ( ) ,
185- } ;
202+ let mut stack_frame64: STACKFRAME64 = mem:: zeroed ( ) ;
203+ stack_frame64. AddrPC . Offset = context. 0 . Eip as u64 ;
204+ stack_frame64. AddrPC . Mode = AddrModeFlat ;
205+ stack_frame64. AddrStack . Offset = context. 0 . Esp as u64 ;
206+ stack_frame64. AddrStack . Mode = AddrModeFlat ;
207+ stack_frame64. AddrFrame . Offset = context. 0 . Ebp as u64 ;
208+ stack_frame64. AddrFrame . Mode = AddrModeFlat ;
186209
187210 while dbghelp. StackWalk64 ( ) (
188- image as DWORD ,
211+ IMAGE_FILE_MACHINE_I386 as DWORD ,
189212 process,
190213 thread,
191- frame_ptr ,
192- & mut context. 0 as * mut CONTEXT as * mut _ ,
214+ & mut stack_frame64 ,
215+ & mut context. 0 as * mut CONTEXT as PVOID ,
193216 None ,
194217 Some ( function_table_access) ,
195218 Some ( get_module_base) ,
196219 None ,
197220 ) == TRUE
198221 {
199- frame. inner . base_address = get_module_base ( process_handle, frame. ip ( ) as _ ) as _ ;
222+ let frame = super :: Frame {
223+ inner : Frame {
224+ base_address : get_module_base ( process_handle, stack_frame64. AddrPC . Offset )
225+ as * mut c_void ,
226+ ip : stack_frame64. AddrPC . Offset as * mut c_void ,
227+ sp : stack_frame64. AddrStack . Offset as * mut c_void ,
228+ #[ cfg( not( target_env = "gnu" ) ) ]
229+ inline_context : None ,
230+ } ,
231+ } ;
200232
201233 if !cb ( & frame) {
202234 break ;
@@ -205,53 +237,3 @@ pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) {
205237 }
206238 }
207239}
208-
209- #[ cfg( target_arch = "x86_64" ) ]
210- fn init_frame ( frame : & mut Frame , ctx : & CONTEXT ) -> WORD {
211- frame. addr_pc_mut ( ) . Offset = ctx. Rip as u64 ;
212- frame. addr_pc_mut ( ) . Mode = AddrModeFlat ;
213- frame. addr_stack_mut ( ) . Offset = ctx. Rsp as u64 ;
214- frame. addr_stack_mut ( ) . Mode = AddrModeFlat ;
215- frame. addr_frame_mut ( ) . Offset = ctx. Rbp as u64 ;
216- frame. addr_frame_mut ( ) . Mode = AddrModeFlat ;
217-
218- IMAGE_FILE_MACHINE_AMD64
219- }
220-
221- #[ cfg( target_arch = "x86" ) ]
222- fn init_frame ( frame : & mut Frame , ctx : & CONTEXT ) -> WORD {
223- frame. addr_pc_mut ( ) . Offset = ctx. Eip as u64 ;
224- frame. addr_pc_mut ( ) . Mode = AddrModeFlat ;
225- frame. addr_stack_mut ( ) . Offset = ctx. Esp as u64 ;
226- frame. addr_stack_mut ( ) . Mode = AddrModeFlat ;
227- frame. addr_frame_mut ( ) . Offset = ctx. Ebp as u64 ;
228- frame. addr_frame_mut ( ) . Mode = AddrModeFlat ;
229-
230- IMAGE_FILE_MACHINE_I386
231- }
232-
233- #[ cfg( target_arch = "aarch64" ) ]
234- fn init_frame ( frame : & mut Frame , ctx : & CONTEXT ) -> WORD {
235- frame. addr_pc_mut ( ) . Offset = ctx. Pc as u64 ;
236- frame. addr_pc_mut ( ) . Mode = AddrModeFlat ;
237- frame. addr_stack_mut ( ) . Offset = ctx. Sp as u64 ;
238- frame. addr_stack_mut ( ) . Mode = AddrModeFlat ;
239- unsafe {
240- frame. addr_frame_mut ( ) . Offset = ctx. u . s ( ) . Fp as u64 ;
241- }
242- frame. addr_frame_mut ( ) . Mode = AddrModeFlat ;
243- IMAGE_FILE_MACHINE_ARM64
244- }
245-
246- #[ cfg( target_arch = "arm" ) ]
247- fn init_frame ( frame : & mut Frame , ctx : & CONTEXT ) -> WORD {
248- frame. addr_pc_mut ( ) . Offset = ctx. Pc as u64 ;
249- frame. addr_pc_mut ( ) . Mode = AddrModeFlat ;
250- frame. addr_stack_mut ( ) . Offset = ctx. Sp as u64 ;
251- frame. addr_stack_mut ( ) . Mode = AddrModeFlat ;
252- unsafe {
253- frame. addr_frame_mut ( ) . Offset = ctx. R11 as u64 ;
254- }
255- frame. addr_frame_mut ( ) . Mode = AddrModeFlat ;
256- IMAGE_FILE_MACHINE_ARMNT
257- }
0 commit comments