1
1
//! Backtrace strategy for MSVC platforms.
2
2
//!
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.
8
14
//!
9
15
//! Note that all dbghelp support is loaded dynamically, see `src/dbghelp.rs`
10
16
//! for more information about that.
11
17
12
18
#![ allow( bad_style) ]
13
19
14
- use super :: super :: { dbghelp , windows:: * } ;
20
+ use super :: super :: windows:: * ;
15
21
use 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
- }
23
22
24
23
#[ derive( Clone , Copy ) ]
25
24
pub struct Frame {
26
- pub ( crate ) stack_frame : StackFrame ,
27
25
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 > ,
28
30
}
29
31
30
32
// we're just sending around raw pointers and reading them, never interpreting
@@ -34,62 +36,108 @@ unsafe impl Sync for Frame {}
34
36
35
37
impl Frame {
36
38
pub fn ip ( & self ) -> * mut c_void {
37
- self . addr_pc ( ) . Offset as * mut _
39
+ self . ip
38
40
}
39
41
40
42
pub fn sp ( & self ) -> * mut c_void {
41
- self . addr_stack ( ) . Offset as * mut _
43
+ self . sp
42
44
}
43
45
44
46
pub fn symbol_address ( & self ) -> * mut c_void {
45
- self . ip ( )
47
+ self . ip
46
48
}
47
49
48
50
pub fn module_base_address ( & self ) -> Option < * mut c_void > {
49
51
Some ( self . base_address )
50
52
}
51
53
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
57
57
}
58
+ }
58
59
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
64
68
}
65
69
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
71
73
}
74
+ }
72
75
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
78
81
}
79
82
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
85
86
}
86
87
}
87
88
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
+ }
90
105
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" ) ) ) ]
91
137
#[ inline( always) ]
92
138
pub unsafe fn trace ( cb : & mut dyn FnMut ( & super :: Frame ) -> bool ) {
139
+ use core:: mem;
140
+
93
141
// Allocate necessary structures for doing the stack walk
94
142
let process = GetCurrentProcess ( ) ;
95
143
let thread = GetCurrentThread ( ) ;
@@ -98,105 +146,89 @@ pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) {
98
146
RtlCaptureContext ( & mut context. 0 ) ;
99
147
100
148
// Ensure this process's symbols are initialized
101
- let dbghelp = match dbghelp:: init ( ) {
149
+ let dbghelp = match super :: super :: dbghelp:: init ( ) {
102
150
Ok ( dbghelp) => dbghelp,
103
151
Err ( ( ) ) => return , // oh well...
104
152
} ;
105
153
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 ( ) ;
133
156
134
157
let process_handle = GetCurrentProcess ( ) ;
135
158
136
159
// Attempt to use `StackWalkEx` if we can, but fall back to `StackWalk64`
137
160
// since it's in theory supported on more systems.
138
161
match ( * dbghelp. dbghelp ( ) ) . StackWalkEx ( ) {
139
162
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 ;
153
171
154
172
while StackWalkEx (
155
- image as DWORD ,
173
+ IMAGE_FILE_MACHINE_I386 as DWORD ,
156
174
process,
157
175
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 ,
160
178
None ,
161
179
Some ( function_table_access) ,
162
180
Some ( get_module_base) ,
163
181
None ,
164
182
0 ,
165
183
) == TRUE
166
184
{
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
+ } ;
168
195
169
196
if !cb ( & frame) {
170
197
break ;
171
198
}
172
199
}
173
200
}
174
201
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 ;
186
209
187
210
while dbghelp. StackWalk64 ( ) (
188
- image as DWORD ,
211
+ IMAGE_FILE_MACHINE_I386 as DWORD ,
189
212
process,
190
213
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 ,
193
216
None ,
194
217
Some ( function_table_access) ,
195
218
Some ( get_module_base) ,
196
219
None ,
197
220
) == TRUE
198
221
{
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
+ } ;
200
232
201
233
if !cb ( & frame) {
202
234
break ;
@@ -205,53 +237,3 @@ pub unsafe fn trace(cb: &mut dyn FnMut(&super::Frame) -> bool) {
205
237
}
206
238
}
207
239
}
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