| 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 | |