]>
Commit | Line | Data |
---|---|---|
dc9dc135 XL |
1 | //! Backtrace support using libunwind/gcc_s/etc APIs. |
2 | //! | |
3 | //! This module contains the ability to unwind the stack using libunwind-style | |
4 | //! APIs. Note that there's a whole bunch of implementations of the | |
5 | //! libunwind-like API, and this is just trying to be compatible with most of | |
6 | //! them all at once instead of being picky. | |
7 | //! | |
8 | //! The libunwind API is powered by `_Unwind_Backtrace` and is in practice very | |
9 | //! reliable at generating a backtrace. It's not entirely clear how it does it | |
10 | //! (frame pointers? eh_frame info? both?) but it seems to work! | |
11 | //! | |
12 | //! Most of the complexity of this module is handling the various platform | |
13 | //! differences across libunwind implementations. Otherwise this is a pretty | |
14 | //! straightforward Rust binding to the libunwind APIs. | |
15 | //! | |
16 | //! This is the default unwinding API for all non-Windows platforms currently. | |
17 | ||
3dfed10e | 18 | use super::super::Bomb; |
416331ca | 19 | use core::ffi::c_void; |
7cac9316 | 20 | |
dc9dc135 XL |
21 | pub enum Frame { |
22 | Raw(*mut uw::_Unwind_Context), | |
23 | Cloned { | |
24 | ip: *mut c_void, | |
f035d41b | 25 | sp: *mut c_void, |
dc9dc135 XL |
26 | symbol_address: *mut c_void, |
27 | }, | |
7cac9316 XL |
28 | } |
29 | ||
dc9dc135 XL |
30 | // With a raw libunwind pointer it should only ever be access in a readonly |
31 | // threadsafe fashion, so it's `Sync`. When sending to other threads via `Clone` | |
32 | // we always switch to a version which doesn't retain interior pointers, so we | |
33 | // should be `Send` as well. | |
34 | unsafe impl Send for Frame {} | |
35 | unsafe impl Sync for Frame {} | |
36 | ||
7cac9316 XL |
37 | impl Frame { |
38 | pub fn ip(&self) -> *mut c_void { | |
dc9dc135 XL |
39 | let ctx = match *self { |
40 | Frame::Raw(ctx) => ctx, | |
41 | Frame::Cloned { ip, .. } => return ip, | |
7cac9316 | 42 | }; |
74b04a01 | 43 | unsafe { uw::_Unwind_GetIP(ctx) as *mut c_void } |
7cac9316 XL |
44 | } |
45 | ||
f035d41b XL |
46 | pub fn sp(&self) -> *mut c_void { |
47 | match *self { | |
48 | Frame::Raw(ctx) => unsafe { uw::get_sp(ctx) as *mut c_void }, | |
49 | Frame::Cloned { sp, .. } => sp, | |
50 | } | |
51 | } | |
52 | ||
7cac9316 | 53 | pub fn symbol_address(&self) -> *mut c_void { |
dc9dc135 XL |
54 | if let Frame::Cloned { symbol_address, .. } = *self { |
55 | return symbol_address; | |
56 | } | |
57 | ||
58 | // It seems that on OSX `_Unwind_FindEnclosingFunction` returns a | |
59 | // pointer to... something that's unclear. It's definitely not always | |
60 | // the enclosing function for whatever reason. It's not entirely clear | |
61 | // to me what's going on here, so pessimize this for now and just always | |
62 | // return the ip. | |
7cac9316 | 63 | // |
dc9dc135 XL |
64 | // Note the `skip_inner_frames.rs` test is skipped on OSX due to this |
65 | // clause, and if this is fixed that test in theory can be run on OSX! | |
7cac9316 XL |
66 | if cfg!(target_os = "macos") || cfg!(target_os = "ios") { |
67 | self.ip() | |
68 | } else { | |
69 | unsafe { uw::_Unwind_FindEnclosingFunction(self.ip()) } | |
70 | } | |
71 | } | |
29967ef6 XL |
72 | |
73 | pub fn module_base_address(&self) -> Option<*mut c_void> { | |
74 | None | |
75 | } | |
7cac9316 XL |
76 | } |
77 | ||
dc9dc135 XL |
78 | impl Clone for Frame { |
79 | fn clone(&self) -> Frame { | |
80 | Frame::Cloned { | |
81 | ip: self.ip(), | |
f035d41b | 82 | sp: self.sp(), |
dc9dc135 XL |
83 | symbol_address: self.symbol_address(), |
84 | } | |
85 | } | |
86 | } | |
87 | ||
7cac9316 | 88 | #[inline(always)] |
f035d41b | 89 | pub unsafe fn trace(mut cb: &mut dyn FnMut(&super::Frame) -> bool) { |
0731742a | 90 | uw::_Unwind_Backtrace(trace_fn, &mut cb as *mut _ as *mut _); |
7cac9316 | 91 | |
74b04a01 XL |
92 | extern "C" fn trace_fn( |
93 | ctx: *mut uw::_Unwind_Context, | |
94 | arg: *mut c_void, | |
95 | ) -> uw::_Unwind_Reason_Code { | |
f035d41b | 96 | let cb = unsafe { &mut *(arg as *mut &mut dyn FnMut(&super::Frame) -> bool) }; |
7cac9316 | 97 | let cx = super::Frame { |
dc9dc135 | 98 | inner: Frame::Raw(ctx), |
7cac9316 XL |
99 | }; |
100 | ||
3dfed10e | 101 | let mut bomb = Bomb { enabled: true }; |
7cac9316 XL |
102 | let keep_going = cb(&cx); |
103 | bomb.enabled = false; | |
104 | ||
105 | if keep_going { | |
106 | uw::_URC_NO_REASON | |
107 | } else { | |
108 | uw::_URC_FAILURE | |
109 | } | |
110 | } | |
111 | } | |
112 | ||
113 | /// Unwind library interface used for backtraces | |
114 | /// | |
115 | /// Note that dead code is allowed as here are just bindings | |
116 | /// iOS doesn't use all of them it but adding more | |
117 | /// platform-specific configs pollutes the code too much | |
118 | #[allow(non_camel_case_types)] | |
119 | #[allow(non_snake_case)] | |
120 | #[allow(dead_code)] | |
121 | mod uw { | |
122 | pub use self::_Unwind_Reason_Code::*; | |
123 | ||
416331ca | 124 | use core::ffi::c_void; |
7cac9316 XL |
125 | |
126 | #[repr(C)] | |
127 | pub enum _Unwind_Reason_Code { | |
128 | _URC_NO_REASON = 0, | |
129 | _URC_FOREIGN_EXCEPTION_CAUGHT = 1, | |
130 | _URC_FATAL_PHASE2_ERROR = 2, | |
131 | _URC_FATAL_PHASE1_ERROR = 3, | |
132 | _URC_NORMAL_STOP = 4, | |
133 | _URC_END_OF_STACK = 5, | |
134 | _URC_HANDLER_FOUND = 6, | |
135 | _URC_INSTALL_CONTEXT = 7, | |
136 | _URC_CONTINUE_UNWIND = 8, | |
137 | _URC_FAILURE = 9, // used only by ARM EABI | |
138 | } | |
139 | ||
140 | pub enum _Unwind_Context {} | |
141 | ||
142 | pub type _Unwind_Trace_Fn = | |
74b04a01 | 143 | extern "C" fn(ctx: *mut _Unwind_Context, arg: *mut c_void) -> _Unwind_Reason_Code; |
7cac9316 | 144 | |
74b04a01 | 145 | extern "C" { |
7cac9316 XL |
146 | // No native _Unwind_Backtrace on iOS |
147 | #[cfg(not(all(target_os = "ios", target_arch = "arm")))] | |
74b04a01 XL |
148 | pub fn _Unwind_Backtrace( |
149 | trace: _Unwind_Trace_Fn, | |
150 | trace_argument: *mut c_void, | |
151 | ) -> _Unwind_Reason_Code; | |
7cac9316 XL |
152 | |
153 | // available since GCC 4.2.0, should be fine for our purpose | |
74b04a01 XL |
154 | #[cfg(all( |
155 | not(all(target_os = "android", target_arch = "arm")), | |
156 | not(all(target_os = "freebsd", target_arch = "arm")), | |
157 | not(all(target_os = "linux", target_arch = "arm")) | |
158 | ))] | |
159 | pub fn _Unwind_GetIP(ctx: *mut _Unwind_Context) -> libc::uintptr_t; | |
160 | ||
161 | #[cfg(all( | |
f035d41b | 162 | not(all(target_os = "android", target_arch = "arm")), |
74b04a01 XL |
163 | not(all(target_os = "freebsd", target_arch = "arm")), |
164 | not(all(target_os = "linux", target_arch = "arm")) | |
165 | ))] | |
166 | pub fn _Unwind_FindEnclosingFunction(pc: *mut c_void) -> *mut c_void; | |
f035d41b XL |
167 | |
168 | #[cfg(all( | |
169 | not(all(target_os = "android", target_arch = "arm")), | |
170 | not(all(target_os = "freebsd", target_arch = "arm")), | |
fc512014 XL |
171 | not(all(target_os = "linux", target_arch = "arm")), |
172 | not(all(target_os = "linux", target_arch = "s390x")) | |
f035d41b XL |
173 | ))] |
174 | // This function is a misnomer: rather than getting this frame's | |
175 | // Canonical Frame Address (aka the caller frame's SP) it | |
176 | // returns this frame's SP. | |
177 | // | |
178 | // https://github.com/libunwind/libunwind/blob/d32956507cf29d9b1a98a8bce53c78623908f4fe/src/unwind/GetCFA.c#L28-L35 | |
179 | #[link_name = "_Unwind_GetCFA"] | |
180 | pub fn get_sp(ctx: *mut _Unwind_Context) -> libc::uintptr_t; | |
7cac9316 XL |
181 | } |
182 | ||
fc512014 XL |
183 | // s390x uses a biased CFA value, therefore we need to use |
184 | // _Unwind_GetGR to get the stack pointer register (%r15) | |
185 | // instead of relying on _Unwind_GetCFA. | |
186 | #[cfg(all(target_os = "linux", target_arch = "s390x"))] | |
187 | pub unsafe fn get_sp(ctx: *mut _Unwind_Context) -> libc::uintptr_t { | |
188 | extern "C" { | |
189 | pub fn _Unwind_GetGR(ctx: *mut _Unwind_Context, index: libc::c_int) -> libc::uintptr_t; | |
190 | } | |
191 | _Unwind_GetGR(ctx, 15) | |
192 | } | |
193 | ||
f035d41b XL |
194 | // On android and arm, the function `_Unwind_GetIP` and a bunch of others |
195 | // are macros, so we define functions containing the expansion of the | |
196 | // macros. | |
197 | // | |
198 | // TODO: link to the header file that defines these macros, if you can find | |
199 | // it. (I, fitzgen, cannot find the header file that some of these macro | |
200 | // expansions were originally borrowed from.) | |
201 | #[cfg(any( | |
202 | all(target_os = "android", target_arch = "arm"), | |
203 | all(target_os = "freebsd", target_arch = "arm"), | |
204 | all(target_os = "linux", target_arch = "arm") | |
205 | ))] | |
206 | pub use self::arm::*; | |
74b04a01 XL |
207 | #[cfg(any( |
208 | all(target_os = "android", target_arch = "arm"), | |
209 | all(target_os = "freebsd", target_arch = "arm"), | |
210 | all(target_os = "linux", target_arch = "arm") | |
211 | ))] | |
f035d41b XL |
212 | mod arm { |
213 | pub use super::*; | |
7cac9316 XL |
214 | #[repr(C)] |
215 | enum _Unwind_VRS_Result { | |
216 | _UVRSR_OK = 0, | |
217 | _UVRSR_NOT_IMPLEMENTED = 1, | |
218 | _UVRSR_FAILED = 2, | |
219 | } | |
220 | #[repr(C)] | |
221 | enum _Unwind_VRS_RegClass { | |
222 | _UVRSC_CORE = 0, | |
223 | _UVRSC_VFP = 1, | |
224 | _UVRSC_FPA = 2, | |
225 | _UVRSC_WMMXD = 3, | |
226 | _UVRSC_WMMXC = 4, | |
227 | } | |
228 | #[repr(C)] | |
229 | enum _Unwind_VRS_DataRepresentation { | |
230 | _UVRSD_UINT32 = 0, | |
231 | _UVRSD_VFPX = 1, | |
232 | _UVRSD_FPAX = 2, | |
233 | _UVRSD_UINT64 = 3, | |
234 | _UVRSD_FLOAT = 4, | |
235 | _UVRSD_DOUBLE = 5, | |
236 | } | |
237 | ||
238 | type _Unwind_Word = libc::c_uint; | |
74b04a01 XL |
239 | extern "C" { |
240 | fn _Unwind_VRS_Get( | |
241 | ctx: *mut _Unwind_Context, | |
242 | klass: _Unwind_VRS_RegClass, | |
243 | word: _Unwind_Word, | |
244 | repr: _Unwind_VRS_DataRepresentation, | |
245 | data: *mut c_void, | |
246 | ) -> _Unwind_VRS_Result; | |
7cac9316 XL |
247 | } |
248 | ||
f035d41b XL |
249 | pub unsafe fn _Unwind_GetIP(ctx: *mut _Unwind_Context) -> libc::uintptr_t { |
250 | let mut val: _Unwind_Word = 0; | |
251 | let ptr = &mut val as *mut _Unwind_Word; | |
252 | let _ = _Unwind_VRS_Get( | |
253 | ctx, | |
254 | _Unwind_VRS_RegClass::_UVRSC_CORE, | |
255 | 15, | |
256 | _Unwind_VRS_DataRepresentation::_UVRSD_UINT32, | |
257 | ptr as *mut c_void, | |
258 | ); | |
259 | (val & !1) as libc::uintptr_t | |
260 | } | |
7cac9316 | 261 | |
f035d41b XL |
262 | // R13 is the stack pointer on arm. |
263 | const SP: _Unwind_Word = 13; | |
264 | ||
265 | pub unsafe fn get_sp(ctx: *mut _Unwind_Context) -> libc::uintptr_t { | |
266 | let mut val: _Unwind_Word = 0; | |
267 | let ptr = &mut val as *mut _Unwind_Word; | |
268 | let _ = _Unwind_VRS_Get( | |
269 | ctx, | |
270 | _Unwind_VRS_RegClass::_UVRSC_CORE, | |
271 | SP, | |
272 | _Unwind_VRS_DataRepresentation::_UVRSD_UINT32, | |
273 | ptr as *mut c_void, | |
274 | ); | |
275 | val as libc::uintptr_t | |
276 | } | |
277 | ||
278 | // This function also doesn't exist on Android or ARM/Linux, so make it | |
279 | // a no-op. | |
280 | pub unsafe fn _Unwind_FindEnclosingFunction(pc: *mut c_void) -> *mut c_void { | |
281 | pc | |
282 | } | |
7cac9316 XL |
283 | } |
284 | } |