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://monoinfinito.wordpress.com/series/exception-handling-in-c/> |
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 | |
39 | use super::dwarf::eh::{self, EHAction, EHContext}; |
40 | use crate::ffi::c_int; |
41 | use unwind as uw; |
42 | |
43 | // Register ids were lifted from LLVM's TargetLowering::getExceptionPointerRegister() |
44 | // and TargetLowering::getExceptionSelectorRegister() for each architecture, |
45 | // then mapped to DWARF register numbers via register definition tables |
46 | // (typically <arch>RegisterInfo.td, search for "DwarfRegNum"). |
47 | // See also https://llvm.org/docs/WritingAnLLVMBackend.html#defining-a-register. |
48 | |
49 | #[cfg (target_arch = "x86" )] |
50 | const UNWIND_DATA_REG: (i32, i32) = (0, 2); // EAX, EDX |
51 | |
52 | #[cfg (target_arch = "x86_64" )] |
53 | const UNWIND_DATA_REG: (i32, i32) = (0, 1); // RAX, RDX |
54 | |
55 | #[cfg (any(target_arch = "arm" , target_arch = "aarch64" ))] |
56 | const UNWIND_DATA_REG: (i32, i32) = (0, 1); // R0, R1 / X0, X1 |
57 | |
58 | #[cfg (target_arch = "m68k" )] |
59 | const UNWIND_DATA_REG: (i32, i32) = (0, 1); // D0, D1 |
60 | |
61 | #[cfg (any( |
62 | target_arch = "mips" , |
63 | target_arch = "mips32r6" , |
64 | target_arch = "mips64" , |
65 | target_arch = "mips64r6" |
66 | ))] |
67 | const UNWIND_DATA_REG: (i32, i32) = (4, 5); // A0, A1 |
68 | |
69 | #[cfg (target_arch = "csky" )] |
70 | const UNWIND_DATA_REG: (i32, i32) = (0, 1); // R0, R1 |
71 | |
72 | #[cfg (any(target_arch = "powerpc" , target_arch = "powerpc64" ))] |
73 | const UNWIND_DATA_REG: (i32, i32) = (3, 4); // R3, R4 / X3, X4 |
74 | |
75 | #[cfg (target_arch = "s390x" )] |
76 | const UNWIND_DATA_REG: (i32, i32) = (6, 7); // R6, R7 |
77 | |
78 | #[cfg (any(target_arch = "sparc" , target_arch = "sparc64" ))] |
79 | const UNWIND_DATA_REG: (i32, i32) = (24, 25); // I0, I1 |
80 | |
81 | #[cfg (target_arch = "hexagon" )] |
82 | const UNWIND_DATA_REG: (i32, i32) = (0, 1); // R0, R1 |
83 | |
84 | #[cfg (any(target_arch = "riscv64" , target_arch = "riscv32" ))] |
85 | const UNWIND_DATA_REG: (i32, i32) = (10, 11); // x10, x11 |
86 | |
87 | #[cfg (target_arch = "loongarch64" )] |
88 | const UNWIND_DATA_REG: (i32, i32) = (4, 5); // a0, a1 |
89 | |
90 | // The following code is based on GCC's C and C++ personality routines. For reference, see: |
91 | // https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/libsupc++/eh_personality.cc |
92 | // https://github.com/gcc-mirror/gcc/blob/trunk/libgcc/unwind-c.c |
93 | |
94 | cfg_if::cfg_if! { |
95 | if #[cfg(all(target_arch = "arm" , not(target_os = "ios" ), not(target_os = "tvos" ), not(target_os = "watchos" ), not(target_os = "visionos" ), not(target_os = "netbsd" )))] { |
96 | // ARM EHABI personality routine. |
97 | // https://web.archive.org/web/20190728160938/https://infocenter.arm.com/help/topic/com.arm.doc.ihi0038b/IHI0038B_ehabi.pdf |
98 | // |
99 | // iOS uses the default routine instead since it uses SjLj unwinding. |
100 | #[lang = "eh_personality" ] |
101 | unsafe extern "C" fn rust_eh_personality( |
102 | state: uw::_Unwind_State, |
103 | exception_object: *mut uw::_Unwind_Exception, |
104 | context: *mut uw::_Unwind_Context, |
105 | ) -> uw::_Unwind_Reason_Code { |
106 | let state = state as c_int; |
107 | let action = state & uw::_US_ACTION_MASK as c_int; |
108 | let search_phase = if action == uw::_US_VIRTUAL_UNWIND_FRAME as c_int { |
109 | // Backtraces on ARM will call the personality routine with |
110 | // state == _US_VIRTUAL_UNWIND_FRAME | _US_FORCE_UNWIND. In those cases |
111 | // we want to continue unwinding the stack, otherwise all our backtraces |
112 | // would end at __rust_try |
113 | if state & uw::_US_FORCE_UNWIND as c_int != 0 { |
114 | return continue_unwind(exception_object, context); |
115 | } |
116 | true |
117 | } else if action == uw::_US_UNWIND_FRAME_STARTING as c_int { |
118 | false |
119 | } else if action == uw::_US_UNWIND_FRAME_RESUME as c_int { |
120 | return continue_unwind(exception_object, context); |
121 | } else { |
122 | return uw::_URC_FAILURE; |
123 | }; |
124 | |
125 | // The DWARF unwinder assumes that _Unwind_Context holds things like the function |
126 | // and LSDA pointers, however ARM EHABI places them into the exception object. |
127 | // To preserve signatures of functions like _Unwind_GetLanguageSpecificData(), which |
128 | // take only the context pointer, GCC personality routines stash a pointer to |
129 | // exception_object in the context, using location reserved for ARM's |
130 | // "scratch register" (r12). |
131 | uw::_Unwind_SetGR(context, uw::UNWIND_POINTER_REG, exception_object as uw::_Unwind_Ptr); |
132 | // ...A more principled approach would be to provide the full definition of ARM's |
133 | // _Unwind_Context in our libunwind bindings and fetch the required data from there |
134 | // directly, bypassing DWARF compatibility functions. |
135 | |
136 | let eh_action = match find_eh_action(context) { |
137 | Ok(action) => action, |
138 | Err(_) => return uw::_URC_FAILURE, |
139 | }; |
140 | if search_phase { |
141 | match eh_action { |
142 | EHAction::None | EHAction::Cleanup(_) => { |
143 | return continue_unwind(exception_object, context); |
144 | } |
145 | EHAction::Catch(_) | EHAction::Filter(_) => { |
146 | // EHABI requires the personality routine to update the |
147 | // SP value in the barrier cache of the exception object. |
148 | (*exception_object).private[5] = |
149 | uw::_Unwind_GetGR(context, uw::UNWIND_SP_REG); |
150 | return uw::_URC_HANDLER_FOUND; |
151 | } |
152 | EHAction::Terminate => return uw::_URC_FAILURE, |
153 | } |
154 | } else { |
155 | match eh_action { |
156 | EHAction::None => return continue_unwind(exception_object, context), |
157 | EHAction::Filter(_) if state & uw::_US_FORCE_UNWIND as c_int != 0 => return continue_unwind(exception_object, context), |
158 | EHAction::Cleanup(lpad) | EHAction::Catch(lpad) | EHAction::Filter(lpad) => { |
159 | uw::_Unwind_SetGR( |
160 | context, |
161 | UNWIND_DATA_REG.0, |
162 | exception_object as uw::_Unwind_Ptr, |
163 | ); |
164 | uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, core::ptr::null()); |
165 | uw::_Unwind_SetIP(context, lpad); |
166 | return uw::_URC_INSTALL_CONTEXT; |
167 | } |
168 | EHAction::Terminate => return uw::_URC_FAILURE, |
169 | } |
170 | } |
171 | |
172 | // On ARM EHABI the personality routine is responsible for actually |
173 | // unwinding a single stack frame before returning (ARM EHABI Sec. 6.1). |
174 | unsafe fn continue_unwind( |
175 | exception_object: *mut uw::_Unwind_Exception, |
176 | context: *mut uw::_Unwind_Context, |
177 | ) -> uw::_Unwind_Reason_Code { |
178 | if __gnu_unwind_frame(exception_object, context) == uw::_URC_NO_REASON { |
179 | uw::_URC_CONTINUE_UNWIND |
180 | } else { |
181 | uw::_URC_FAILURE |
182 | } |
183 | } |
184 | // defined in libgcc |
185 | extern "C" { |
186 | fn __gnu_unwind_frame( |
187 | exception_object: *mut uw::_Unwind_Exception, |
188 | context: *mut uw::_Unwind_Context, |
189 | ) -> uw::_Unwind_Reason_Code; |
190 | } |
191 | } |
192 | } else { |
193 | // Default personality routine, which is used directly on most targets |
194 | // and indirectly on Windows x86_64 via SEH. |
195 | unsafe extern "C" fn rust_eh_personality_impl( |
196 | version: c_int, |
197 | actions: uw::_Unwind_Action, |
198 | _exception_class: uw::_Unwind_Exception_Class, |
199 | exception_object: *mut uw::_Unwind_Exception, |
200 | context: *mut uw::_Unwind_Context, |
201 | ) -> uw::_Unwind_Reason_Code { |
202 | if version != 1 { |
203 | return uw::_URC_FATAL_PHASE1_ERROR; |
204 | } |
205 | let eh_action = match find_eh_action(context) { |
206 | Ok(action) => action, |
207 | Err(_) => return uw::_URC_FATAL_PHASE1_ERROR, |
208 | }; |
209 | if actions as i32 & uw::_UA_SEARCH_PHASE as i32 != 0 { |
210 | match eh_action { |
211 | EHAction::None | EHAction::Cleanup(_) => uw::_URC_CONTINUE_UNWIND, |
212 | EHAction::Catch(_) | EHAction::Filter(_) => uw::_URC_HANDLER_FOUND, |
213 | EHAction::Terminate => uw::_URC_FATAL_PHASE1_ERROR, |
214 | } |
215 | } else { |
216 | match eh_action { |
217 | EHAction::None => uw::_URC_CONTINUE_UNWIND, |
218 | // Forced unwinding hits a terminate action. |
219 | EHAction::Filter(_) if actions as i32 & uw::_UA_FORCE_UNWIND as i32 != 0 => uw::_URC_CONTINUE_UNWIND, |
220 | EHAction::Cleanup(lpad) | EHAction::Catch(lpad) | EHAction::Filter(lpad) => { |
221 | uw::_Unwind_SetGR( |
222 | context, |
223 | UNWIND_DATA_REG.0, |
224 | exception_object.cast(), |
225 | ); |
226 | uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, core::ptr::null()); |
227 | uw::_Unwind_SetIP(context, lpad); |
228 | uw::_URC_INSTALL_CONTEXT |
229 | } |
230 | EHAction::Terminate => uw::_URC_FATAL_PHASE2_ERROR, |
231 | } |
232 | } |
233 | } |
234 | |
235 | cfg_if::cfg_if! { |
236 | if #[cfg(all(windows, any(target_arch = "aarch64" , target_arch = "x86_64" ), target_env = "gnu" ))] { |
237 | // On x86_64 MinGW targets, the unwinding mechanism is SEH however the unwind |
238 | // handler data (aka LSDA) uses GCC-compatible encoding. |
239 | #[lang = "eh_personality" ] |
240 | #[allow (nonstandard_style)] |
241 | unsafe extern "C" fn rust_eh_personality( |
242 | exceptionRecord: *mut uw::EXCEPTION_RECORD, |
243 | establisherFrame: uw::LPVOID, |
244 | contextRecord: *mut uw::CONTEXT, |
245 | dispatcherContext: *mut uw::DISPATCHER_CONTEXT, |
246 | ) -> uw::EXCEPTION_DISPOSITION { |
247 | uw::_GCC_specific_handler( |
248 | exceptionRecord, |
249 | establisherFrame, |
250 | contextRecord, |
251 | dispatcherContext, |
252 | rust_eh_personality_impl, |
253 | ) |
254 | } |
255 | } else { |
256 | // The personality routine for most of our targets. |
257 | #[lang = "eh_personality" ] |
258 | unsafe extern "C" fn rust_eh_personality( |
259 | version: c_int, |
260 | actions: uw::_Unwind_Action, |
261 | exception_class: uw::_Unwind_Exception_Class, |
262 | exception_object: *mut uw::_Unwind_Exception, |
263 | context: *mut uw::_Unwind_Context, |
264 | ) -> uw::_Unwind_Reason_Code { |
265 | rust_eh_personality_impl( |
266 | version, |
267 | actions, |
268 | exception_class, |
269 | exception_object, |
270 | context, |
271 | ) |
272 | } |
273 | } |
274 | } |
275 | } |
276 | } |
277 | |
278 | unsafe fn find_eh_action(context: *mut uw::_Unwind_Context) -> Result<EHAction, ()> { |
279 | let lsda: *const u8 = uw::_Unwind_GetLanguageSpecificData(context) as *const u8; |
280 | let mut ip_before_instr: c_int = 0; |
281 | let ip: *const u8 = uw::_Unwind_GetIPInfo(context, &mut ip_before_instr); |
282 | let eh_context: EHContext<'_> = EHContext { |
283 | // The return address points 1 byte past the call instruction, |
284 | // which could be in the next IP range in LSDA range table. |
285 | // |
286 | // `ip = -1` has special meaning, so use wrapping sub to allow for that |
287 | ip: if ip_before_instr != 0 { ip } else { ip.wrapping_sub(count:1) }, |
288 | func_start: uw::_Unwind_GetRegionStart(context), |
289 | get_text_start: &|| uw::_Unwind_GetTextRelBase(context), |
290 | get_data_start: &|| uw::_Unwind_GetDataRelBase(context), |
291 | }; |
292 | eh::find_eh_action(lsda, &eh_context) |
293 | } |
294 | |