1 | //! Implementation of panics backed by libgcc/libunwind (in some form). |
2 | //! |
3 | //! For background on exception handling and stack unwinding please see |
4 | //! "Exception Handling in LLVM" (llvm.org/docs/ExceptionHandling.html) and |
5 | //! documents linked from it. |
6 | //! These are also good reads: |
7 | //! * <https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html> |
8 | //! * <https://nicolasbrailo.github.io/blog/projects_texts/13exceptionsunderthehood.html> |
9 | //! * <https://www.airs.com/blog/index.php?s=exception+frames> |
10 | //! |
11 | //! ## A brief summary |
12 | //! |
13 | //! Exception handling happens in two phases: a search phase and a cleanup |
14 | //! phase. |
15 | //! |
16 | //! In both phases the unwinder walks stack frames from top to bottom using |
17 | //! information from the stack frame unwind sections of the current process's |
18 | //! modules ("module" here refers to an OS module, i.e., an executable or a |
19 | //! dynamic library). |
20 | //! |
21 | //! For each stack frame, it invokes the associated "personality routine", whose |
22 | //! address is also stored in the unwind info section. |
23 | //! |
24 | //! In the search phase, the job of a personality routine is to examine |
25 | //! exception object being thrown, and to decide whether it should be caught at |
26 | //! that stack frame. Once the handler frame has been identified, cleanup phase |
27 | //! begins. |
28 | //! |
29 | //! In the cleanup phase, the unwinder invokes each personality routine again. |
30 | //! This time it decides which (if any) cleanup code needs to be run for |
31 | //! the current stack frame. If so, the control is transferred to a special |
32 | //! branch in the function body, the "landing pad", which invokes destructors, |
33 | //! frees memory, etc. At the end of the landing pad, control is transferred |
34 | //! back to the unwinder and unwinding resumes. |
35 | //! |
36 | //! Once stack has been unwound down to the handler frame level, unwinding stops |
37 | //! and the last personality routine transfers control to the catch block. |
38 | #![forbid (unsafe_op_in_unsafe_fn)] |
39 | |
40 | use unwind as uw; |
41 | |
42 | use super::dwarf::eh::{self, EHAction, EHContext}; |
43 | use crate::ffi::c_int; |
44 | |
45 | // Register ids were lifted from LLVM's TargetLowering::getExceptionPointerRegister() |
46 | // and TargetLowering::getExceptionSelectorRegister() for each architecture, |
47 | // then mapped to DWARF register numbers via register definition tables |
48 | // (typically <arch>RegisterInfo.td, search for "DwarfRegNum"). |
49 | // See also https://llvm.org/docs/WritingAnLLVMBackend.html#defining-a-register. |
50 | |
51 | #[cfg (target_arch = "x86" )] |
52 | const UNWIND_DATA_REG: (i32, i32) = (0, 2); // EAX, EDX |
53 | |
54 | #[cfg (target_arch = "x86_64" )] |
55 | const UNWIND_DATA_REG: (i32, i32) = (0, 1); // RAX, RDX |
56 | |
57 | #[cfg (any(target_arch = "arm" , target_arch = "aarch64" ))] |
58 | const UNWIND_DATA_REG: (i32, i32) = (0, 1); // R0, R1 / X0, X1 |
59 | |
60 | #[cfg (target_arch = "m68k" )] |
61 | const UNWIND_DATA_REG: (i32, i32) = (0, 1); // D0, D1 |
62 | |
63 | #[cfg (any( |
64 | target_arch = "mips" , |
65 | target_arch = "mips32r6" , |
66 | target_arch = "mips64" , |
67 | target_arch = "mips64r6" |
68 | ))] |
69 | const UNWIND_DATA_REG: (i32, i32) = (4, 5); // A0, A1 |
70 | |
71 | #[cfg (target_arch = "csky" )] |
72 | const UNWIND_DATA_REG: (i32, i32) = (0, 1); // R0, R1 |
73 | |
74 | #[cfg (any(target_arch = "powerpc" , target_arch = "powerpc64" ))] |
75 | const UNWIND_DATA_REG: (i32, i32) = (3, 4); // R3, R4 / X3, X4 |
76 | |
77 | #[cfg (target_arch = "s390x" )] |
78 | const UNWIND_DATA_REG: (i32, i32) = (6, 7); // R6, R7 |
79 | |
80 | #[cfg (any(target_arch = "sparc" , target_arch = "sparc64" ))] |
81 | const UNWIND_DATA_REG: (i32, i32) = (24, 25); // I0, I1 |
82 | |
83 | #[cfg (target_arch = "hexagon" )] |
84 | const UNWIND_DATA_REG: (i32, i32) = (0, 1); // R0, R1 |
85 | |
86 | #[cfg (any(target_arch = "riscv64" , target_arch = "riscv32" ))] |
87 | const UNWIND_DATA_REG: (i32, i32) = (10, 11); // x10, x11 |
88 | |
89 | #[cfg (any(target_arch = "loongarch32" , target_arch = "loongarch64" ))] |
90 | const UNWIND_DATA_REG: (i32, i32) = (4, 5); // a0, a1 |
91 | |
92 | // The following code is based on GCC's C and C++ personality routines. For reference, see: |
93 | // https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/libsupc++/eh_personality.cc |
94 | // https://github.com/gcc-mirror/gcc/blob/trunk/libgcc/unwind-c.c |
95 | |
96 | cfg_if::cfg_if! { |
97 | if #[cfg(all( |
98 | target_arch = "arm" , |
99 | not(target_vendor = "apple" ), |
100 | not(target_os = "netbsd" ), |
101 | ))] { |
102 | /// personality fn called by [ARM EHABI][armeabi-eh] |
103 | /// |
104 | /// 32-bit ARM on iOS/tvOS/watchOS does not use ARM EHABI, it uses |
105 | /// either "setjmp-longjmp" unwinding or DWARF CFI unwinding, which is |
106 | /// handled by the default routine. |
107 | /// |
108 | /// [armeabi-eh]: https://web.archive.org/web/20190728160938/https://infocenter.arm.com/help/topic/com.arm.doc.ihi0038b/IHI0038B_ehabi.pdf |
109 | #[lang = "eh_personality" ] |
110 | unsafe extern "C" fn rust_eh_personality( |
111 | state: uw::_Unwind_State, |
112 | exception_object: *mut uw::_Unwind_Exception, |
113 | context: *mut uw::_Unwind_Context, |
114 | ) -> uw::_Unwind_Reason_Code { |
115 | unsafe { |
116 | let state = state as c_int; |
117 | let action = state & uw::_US_ACTION_MASK as c_int; |
118 | let search_phase = if action == uw::_US_VIRTUAL_UNWIND_FRAME as c_int { |
119 | // Backtraces on ARM will call the personality routine with |
120 | // state == _US_VIRTUAL_UNWIND_FRAME | _US_FORCE_UNWIND. In those cases |
121 | // we want to continue unwinding the stack, otherwise all our backtraces |
122 | // would end at __rust_try |
123 | if state & uw::_US_FORCE_UNWIND as c_int != 0 { |
124 | return continue_unwind(exception_object, context); |
125 | } |
126 | true |
127 | } else if action == uw::_US_UNWIND_FRAME_STARTING as c_int { |
128 | false |
129 | } else if action == uw::_US_UNWIND_FRAME_RESUME as c_int { |
130 | return continue_unwind(exception_object, context); |
131 | } else { |
132 | return uw::_URC_FAILURE; |
133 | }; |
134 | |
135 | // The DWARF unwinder assumes that _Unwind_Context holds things like the function |
136 | // and LSDA pointers, however ARM EHABI places them into the exception object. |
137 | // To preserve signatures of functions like _Unwind_GetLanguageSpecificData(), which |
138 | // take only the context pointer, GCC personality routines stash a pointer to |
139 | // exception_object in the context, using location reserved for ARM's |
140 | // "scratch register" (r12). |
141 | uw::_Unwind_SetGR(context, uw::UNWIND_POINTER_REG, exception_object as uw::_Unwind_Ptr); |
142 | // ...A more principled approach would be to provide the full definition of ARM's |
143 | // _Unwind_Context in our libunwind bindings and fetch the required data from there |
144 | // directly, bypassing DWARF compatibility functions. |
145 | |
146 | let eh_action = match find_eh_action(context) { |
147 | Ok(action) => action, |
148 | Err(_) => return uw::_URC_FAILURE, |
149 | }; |
150 | if search_phase { |
151 | match eh_action { |
152 | EHAction::None | EHAction::Cleanup(_) => { |
153 | return continue_unwind(exception_object, context); |
154 | } |
155 | EHAction::Catch(_) | EHAction::Filter(_) => { |
156 | // EHABI requires the personality routine to update the |
157 | // SP value in the barrier cache of the exception object. |
158 | (*exception_object).private[5] = |
159 | uw::_Unwind_GetGR(context, uw::UNWIND_SP_REG); |
160 | return uw::_URC_HANDLER_FOUND; |
161 | } |
162 | EHAction::Terminate => return uw::_URC_FAILURE, |
163 | } |
164 | } else { |
165 | match eh_action { |
166 | EHAction::None => return continue_unwind(exception_object, context), |
167 | EHAction::Filter(_) if state & uw::_US_FORCE_UNWIND as c_int != 0 => return continue_unwind(exception_object, context), |
168 | EHAction::Cleanup(lpad) | EHAction::Catch(lpad) | EHAction::Filter(lpad) => { |
169 | uw::_Unwind_SetGR( |
170 | context, |
171 | UNWIND_DATA_REG.0, |
172 | exception_object as uw::_Unwind_Ptr, |
173 | ); |
174 | uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, core::ptr::null()); |
175 | uw::_Unwind_SetIP(context, lpad); |
176 | return uw::_URC_INSTALL_CONTEXT; |
177 | } |
178 | EHAction::Terminate => return uw::_URC_FAILURE, |
179 | } |
180 | } |
181 | |
182 | // On ARM EHABI the personality routine is responsible for actually |
183 | // unwinding a single stack frame before returning (ARM EHABI Sec. 6.1). |
184 | unsafe fn continue_unwind( |
185 | exception_object: *mut uw::_Unwind_Exception, |
186 | context: *mut uw::_Unwind_Context, |
187 | ) -> uw::_Unwind_Reason_Code { |
188 | unsafe { |
189 | if __gnu_unwind_frame(exception_object, context) == uw::_URC_NO_REASON { |
190 | uw::_URC_CONTINUE_UNWIND |
191 | } else { |
192 | uw::_URC_FAILURE |
193 | } |
194 | } |
195 | } |
196 | // defined in libgcc |
197 | unsafe extern "C" { |
198 | fn __gnu_unwind_frame( |
199 | exception_object: *mut uw::_Unwind_Exception, |
200 | context: *mut uw::_Unwind_Context, |
201 | ) -> uw::_Unwind_Reason_Code; |
202 | } |
203 | } |
204 | } |
205 | } else { |
206 | /// Default personality routine, which is used directly on most targets |
207 | /// and indirectly on Windows x86_64 and AArch64 via SEH. |
208 | unsafe extern "C" fn rust_eh_personality_impl( |
209 | version: c_int, |
210 | actions: uw::_Unwind_Action, |
211 | _exception_class: uw::_Unwind_Exception_Class, |
212 | exception_object: *mut uw::_Unwind_Exception, |
213 | context: *mut uw::_Unwind_Context, |
214 | ) -> uw::_Unwind_Reason_Code { |
215 | unsafe { |
216 | if version != 1 { |
217 | return uw::_URC_FATAL_PHASE1_ERROR; |
218 | } |
219 | let eh_action = match find_eh_action(context) { |
220 | Ok(action) => action, |
221 | Err(_) => return uw::_URC_FATAL_PHASE1_ERROR, |
222 | }; |
223 | if actions & uw::_UA_SEARCH_PHASE != 0 { |
224 | match eh_action { |
225 | EHAction::None | EHAction::Cleanup(_) => uw::_URC_CONTINUE_UNWIND, |
226 | EHAction::Catch(_) | EHAction::Filter(_) => uw::_URC_HANDLER_FOUND, |
227 | EHAction::Terminate => uw::_URC_FATAL_PHASE1_ERROR, |
228 | } |
229 | } else { |
230 | match eh_action { |
231 | EHAction::None => uw::_URC_CONTINUE_UNWIND, |
232 | // Forced unwinding hits a terminate action. |
233 | EHAction::Filter(_) if actions & uw::_UA_FORCE_UNWIND != 0 => uw::_URC_CONTINUE_UNWIND, |
234 | EHAction::Cleanup(lpad) | EHAction::Catch(lpad) | EHAction::Filter(lpad) => { |
235 | uw::_Unwind_SetGR( |
236 | context, |
237 | UNWIND_DATA_REG.0, |
238 | exception_object.cast(), |
239 | ); |
240 | uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, core::ptr::null()); |
241 | uw::_Unwind_SetIP(context, lpad); |
242 | uw::_URC_INSTALL_CONTEXT |
243 | } |
244 | EHAction::Terminate => uw::_URC_FATAL_PHASE2_ERROR, |
245 | } |
246 | } |
247 | } |
248 | } |
249 | |
250 | cfg_if::cfg_if! { |
251 | if #[cfg(any( |
252 | all(windows, any(target_arch = "aarch64" , target_arch = "x86_64" ), target_env = "gnu" ), |
253 | target_os = "cygwin" , |
254 | ))] { |
255 | /// personality fn called by [Windows Structured Exception Handling][windows-eh] |
256 | /// |
257 | /// On x86_64 and AArch64 MinGW targets, the unwinding mechanism is SEH, |
258 | /// however the unwind handler data (aka LSDA) uses GCC-compatible encoding |
259 | /// |
260 | /// [windows-eh]: https://learn.microsoft.com/en-us/cpp/cpp/structured-exception-handling-c-cpp?view=msvc-170 |
261 | #[lang = "eh_personality" ] |
262 | #[allow(nonstandard_style)] |
263 | unsafe extern "C" fn rust_eh_personality( |
264 | exceptionRecord: *mut uw::EXCEPTION_RECORD, |
265 | establisherFrame: uw::LPVOID, |
266 | contextRecord: *mut uw::CONTEXT, |
267 | dispatcherContext: *mut uw::DISPATCHER_CONTEXT, |
268 | ) -> uw::EXCEPTION_DISPOSITION { |
269 | // SAFETY: the cfg is still target_os = "windows" and target_env = "gnu", |
270 | // which means that this is the correct function to call, passing our impl fn |
271 | // as the callback which gets actually used |
272 | unsafe { |
273 | uw::_GCC_specific_handler( |
274 | exceptionRecord, |
275 | establisherFrame, |
276 | contextRecord, |
277 | dispatcherContext, |
278 | rust_eh_personality_impl, |
279 | ) |
280 | } |
281 | } |
282 | } else { |
283 | /// personality fn called by [Itanium C++ ABI Exception Handling][itanium-eh] |
284 | /// |
285 | /// The personality routine for most non-Windows targets. This will be called by |
286 | /// the unwinding library: |
287 | /// - "In the search phase, the framework repeatedly calls the personality routine, |
288 | /// with the _UA_SEARCH_PHASE flag as described below, first for the current PC |
289 | /// and register state, and then unwinding a frame to a new PC at each step..." |
290 | /// - "If the search phase reports success, the framework restarts in the cleanup |
291 | /// phase. Again, it repeatedly calls the personality routine, with the |
292 | /// _UA_CLEANUP_PHASE flag as described below, first for the current PC and |
293 | /// register state, and then unwinding a frame to a new PC at each step..."i |
294 | /// |
295 | /// [itanium-eh]: https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html |
296 | #[lang = "eh_personality" ] |
297 | unsafe extern "C" fn rust_eh_personality( |
298 | version: c_int, |
299 | actions: uw::_Unwind_Action, |
300 | exception_class: uw::_Unwind_Exception_Class, |
301 | exception_object: *mut uw::_Unwind_Exception, |
302 | context: *mut uw::_Unwind_Context, |
303 | ) -> uw::_Unwind_Reason_Code { |
304 | // SAFETY: the platform support must modify the cfg for the inner fn |
305 | // if it needs something different than what is currently invoked. |
306 | unsafe { |
307 | rust_eh_personality_impl( |
308 | version, |
309 | actions, |
310 | exception_class, |
311 | exception_object, |
312 | context, |
313 | ) |
314 | } |
315 | } |
316 | } |
317 | } |
318 | } |
319 | } |
320 | |
321 | unsafe fn find_eh_action(context: *mut uw::_Unwind_Context) -> Result<EHAction, ()> { |
322 | unsafe { |
323 | let lsda: *const u8 = uw::_Unwind_GetLanguageSpecificData(ctx:context) as *const u8; |
324 | let mut ip_before_instr: c_int = 0; |
325 | let ip: *const u8 = uw::_Unwind_GetIPInfo(ctx:context, &mut ip_before_instr); |
326 | let eh_context: EHContext<'_> = EHContext { |
327 | // The return address points 1 byte past the call instruction, |
328 | // which could be in the next IP range in LSDA range table. |
329 | // |
330 | // `ip = -1` has special meaning, so use wrapping sub to allow for that |
331 | ip: if ip_before_instr != 0 { ip } else { ip.wrapping_sub(count:1) }, |
332 | func_start: uw::_Unwind_GetRegionStart(ctx:context), |
333 | get_text_start: &|| uw::_Unwind_GetTextRelBase(ctx:context), |
334 | get_data_start: &|| uw::_Unwind_GetDataRelBase(ctx:context), |
335 | }; |
336 | eh::find_eh_action(lsda, &eh_context) |
337 | } |
338 | } |
339 | |