diff options
| author | Amanieu d'Antras <amanieu@gmail.com> | 2019-10-21 02:46:04 +0100 |
|---|---|---|
| committer | Amanieu d'Antras <amanieu@gmail.com> | 2019-11-03 14:03:21 +0000 |
| commit | 5f1a0afd22c7f1e26782467503d32d026c55fc0c (patch) | |
| tree | 7e7fc14e9dadb4770e40294ec56cdc54642e7db4 /src/libpanic_unwind | |
| parent | 9a8bb3a26e8a058be64a0d778b3b168bc9b74caa (diff) | |
| download | rust-5f1a0afd22c7f1e26782467503d32d026c55fc0c.tar.gz rust-5f1a0afd22c7f1e26782467503d32d026c55fc0c.zip | |
Allow foreign exceptions to unwind through Rust code
Diffstat (limited to 'src/libpanic_unwind')
| -rw-r--r-- | src/libpanic_unwind/dwarf/eh.rs | 15 | ||||
| -rw-r--r-- | src/libpanic_unwind/gcc.rs | 277 | ||||
| -rw-r--r-- | src/libpanic_unwind/lib.rs | 8 | ||||
| -rw-r--r-- | src/libpanic_unwind/seh64_gnu.rs | 127 |
4 files changed, 173 insertions, 254 deletions
diff --git a/src/libpanic_unwind/dwarf/eh.rs b/src/libpanic_unwind/dwarf/eh.rs index 07fa2971847..1e9e7e4b835 100644 --- a/src/libpanic_unwind/dwarf/eh.rs +++ b/src/libpanic_unwind/dwarf/eh.rs @@ -51,7 +51,7 @@ pub enum EHAction { pub const USING_SJLJ_EXCEPTIONS: bool = cfg!(all(target_os = "ios", target_arch = "arm")); -pub unsafe fn find_eh_action(lsda: *const u8, context: &EHContext<'_>) +pub unsafe fn find_eh_action(lsda: *const u8, context: &EHContext<'_>, foreign_exception: bool) -> Result<EHAction, ()> { if lsda.is_null() { @@ -96,7 +96,7 @@ pub unsafe fn find_eh_action(lsda: *const u8, context: &EHContext<'_>) return Ok(EHAction::None) } else { let lpad = lpad_base + cs_lpad; - return Ok(interpret_cs_action(cs_action, lpad)) + return Ok(interpret_cs_action(cs_action, lpad, foreign_exception)) } } } @@ -121,16 +121,23 @@ pub unsafe fn find_eh_action(lsda: *const u8, context: &EHContext<'_>) // Can never have null landing pad for sjlj -- that would have // been indicated by a -1 call site index. let lpad = (cs_lpad + 1) as usize; - return Ok(interpret_cs_action(cs_action, lpad)) + return Ok(interpret_cs_action(cs_action, lpad, foreign_exception)) } } } } -fn interpret_cs_action(cs_action: u64, lpad: usize) -> EHAction { +fn interpret_cs_action(cs_action: u64, lpad: usize, foreign_exception: bool) -> EHAction { if cs_action == 0 { + // If cs_action is 0 then this is a cleanup (Drop::drop). We run these + // for both Rust panics and foriegn exceptions. EHAction::Cleanup(lpad) + } else if foreign_exception { + // catch_unwind should not catch foreign exceptions, only Rust panics. + // Instead just continue unwinding. + EHAction::None } else { + // Stop unwinding Rust panics at catch_unwind. EHAction::Catch(lpad) } } diff --git a/src/libpanic_unwind/gcc.rs b/src/libpanic_unwind/gcc.rs index a35847c85fc..4f572fe21b3 100644 --- a/src/libpanic_unwind/gcc.rs +++ b/src/libpanic_unwind/gcc.rs @@ -133,133 +133,176 @@ const UNWIND_DATA_REG: (i32, i32) = (0, 1); // R0, R1 // https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/libsupc++/eh_personality.cc // https://github.com/gcc-mirror/gcc/blob/trunk/libgcc/unwind-c.c -// The personality routine for most of our targets, except ARM, which has a slightly different ABI -// (however, iOS goes here as it uses SjLj unwinding). Also, the 64-bit Windows implementation -// lives in seh64_gnu.rs -#[cfg(all(any(target_os = "ios", target_os = "netbsd", not(target_arch = "arm"))))] -#[lang = "eh_personality"] -#[no_mangle] -#[allow(unused)] -unsafe extern "C" fn rust_eh_personality(version: c_int, - actions: uw::_Unwind_Action, - exception_class: uw::_Unwind_Exception_Class, - exception_object: *mut uw::_Unwind_Exception, - context: *mut uw::_Unwind_Context) - -> uw::_Unwind_Reason_Code { - if version != 1 { - return uw::_URC_FATAL_PHASE1_ERROR; - } - let eh_action = match find_eh_action(context) { - Ok(action) => action, - Err(_) => return uw::_URC_FATAL_PHASE1_ERROR, - }; - if actions as i32 & uw::_UA_SEARCH_PHASE as i32 != 0 { - match eh_action { - EHAction::None | - EHAction::Cleanup(_) => uw::_URC_CONTINUE_UNWIND, - EHAction::Catch(_) => uw::_URC_HANDLER_FOUND, - EHAction::Terminate => uw::_URC_FATAL_PHASE1_ERROR, - } - } else { - match eh_action { - EHAction::None => uw::_URC_CONTINUE_UNWIND, - EHAction::Cleanup(lpad) | - EHAction::Catch(lpad) => { - uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0, exception_object as uintptr_t); - uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, 0); - uw::_Unwind_SetIP(context, lpad); - uw::_URC_INSTALL_CONTEXT - } - EHAction::Terminate => uw::_URC_FATAL_PHASE2_ERROR, - } - } -} +cfg_if::cfg_if! { + if #[cfg(all(target_arch = "arm", not(target_os = "ios"), not(target_os = "netbsd")))] { + // ARM EHABI personality routine. + // http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038b/IHI0038B_ehabi.pdf + // + // iOS uses the default routine instead since it uses SjLj unwinding. + #[lang = "eh_personality"] + #[no_mangle] + unsafe extern "C" fn rust_eh_personality(state: uw::_Unwind_State, + exception_object: *mut uw::_Unwind_Exception, + context: *mut uw::_Unwind_Context) + -> uw::_Unwind_Reason_Code { + let state = state as c_int; + let action = state & uw::_US_ACTION_MASK as c_int; + let search_phase = if action == uw::_US_VIRTUAL_UNWIND_FRAME as c_int { + // Backtraces on ARM will call the personality routine with + // state == _US_VIRTUAL_UNWIND_FRAME | _US_FORCE_UNWIND. In those cases + // we want to continue unwinding the stack, otherwise all our backtraces + // would end at __rust_try + if state & uw::_US_FORCE_UNWIND as c_int != 0 { + return continue_unwind(exception_object, context); + } + true + } else if action == uw::_US_UNWIND_FRAME_STARTING as c_int { + false + } else if action == uw::_US_UNWIND_FRAME_RESUME as c_int { + return continue_unwind(exception_object, context); + } else { + return uw::_URC_FAILURE; + }; -// ARM EHABI personality routine. -// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038b/IHI0038B_ehabi.pdf -#[cfg(all(target_arch = "arm", not(target_os = "ios"), not(target_os = "netbsd")))] -#[lang = "eh_personality"] -#[no_mangle] -unsafe extern "C" fn rust_eh_personality(state: uw::_Unwind_State, - exception_object: *mut uw::_Unwind_Exception, - context: *mut uw::_Unwind_Context) - -> uw::_Unwind_Reason_Code { - let state = state as c_int; - let action = state & uw::_US_ACTION_MASK as c_int; - let search_phase = if action == uw::_US_VIRTUAL_UNWIND_FRAME as c_int { - // Backtraces on ARM will call the personality routine with - // state == _US_VIRTUAL_UNWIND_FRAME | _US_FORCE_UNWIND. In those cases - // we want to continue unwinding the stack, otherwise all our backtraces - // would end at __rust_try - if state & uw::_US_FORCE_UNWIND as c_int != 0 { - return continue_unwind(exception_object, context); - } - true - } else if action == uw::_US_UNWIND_FRAME_STARTING as c_int { - false - } else if action == uw::_US_UNWIND_FRAME_RESUME as c_int { - return continue_unwind(exception_object, context); - } else { - return uw::_URC_FAILURE; - }; + // The DWARF unwinder assumes that _Unwind_Context holds things like the function + // and LSDA pointers, however ARM EHABI places them into the exception object. + // To preserve signatures of functions like _Unwind_GetLanguageSpecificData(), which + // take only the context pointer, GCC personality routines stash a pointer to + // exception_object in the context, using location reserved for ARM's + // "scratch register" (r12). + uw::_Unwind_SetGR(context, + uw::UNWIND_POINTER_REG, + exception_object as uw::_Unwind_Ptr); + // ...A more principled approach would be to provide the full definition of ARM's + // _Unwind_Context in our libunwind bindings and fetch the required data from there + // directly, bypassing DWARF compatibility functions. - // The DWARF unwinder assumes that _Unwind_Context holds things like the function - // and LSDA pointers, however ARM EHABI places them into the exception object. - // To preserve signatures of functions like _Unwind_GetLanguageSpecificData(), which - // take only the context pointer, GCC personality routines stash a pointer to exception_object - // in the context, using location reserved for ARM's "scratch register" (r12). - uw::_Unwind_SetGR(context, - uw::UNWIND_POINTER_REG, - exception_object as uw::_Unwind_Ptr); - // ...A more principled approach would be to provide the full definition of ARM's - // _Unwind_Context in our libunwind bindings and fetch the required data from there directly, - // bypassing DWARF compatibility functions. + let exception_class = (*exception_object).exception_class; + let foreign_exception = exception_class != rust_exception_class(); + let eh_action = match find_eh_action(context, foreign_exception) { + Ok(action) => action, + Err(_) => return uw::_URC_FAILURE, + }; + if search_phase { + match eh_action { + EHAction::None | + EHAction::Cleanup(_) => return continue_unwind(exception_object, context), + EHAction::Catch(_) => return uw::_URC_HANDLER_FOUND, + EHAction::Terminate => return uw::_URC_FAILURE, + } + } else { + match eh_action { + EHAction::None => return continue_unwind(exception_object, context), + EHAction::Cleanup(lpad) | + EHAction::Catch(lpad) => { + uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0, + exception_object as uintptr_t); + uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, 0); + uw::_Unwind_SetIP(context, lpad); + return uw::_URC_INSTALL_CONTEXT; + } + EHAction::Terminate => return uw::_URC_FAILURE, + } + } - let eh_action = match find_eh_action(context) { - Ok(action) => action, - Err(_) => return uw::_URC_FAILURE, - }; - if search_phase { - match eh_action { - EHAction::None | - EHAction::Cleanup(_) => return continue_unwind(exception_object, context), - EHAction::Catch(_) => return uw::_URC_HANDLER_FOUND, - EHAction::Terminate => return uw::_URC_FAILURE, + // On ARM EHABI the personality routine is responsible for actually + // unwinding a single stack frame before returning (ARM EHABI Sec. 6.1). + unsafe fn continue_unwind(exception_object: *mut uw::_Unwind_Exception, + context: *mut uw::_Unwind_Context) + -> uw::_Unwind_Reason_Code { + if __gnu_unwind_frame(exception_object, context) == uw::_URC_NO_REASON { + uw::_URC_CONTINUE_UNWIND + } else { + uw::_URC_FAILURE + } + } + // defined in libgcc + extern "C" { + fn __gnu_unwind_frame(exception_object: *mut uw::_Unwind_Exception, + context: *mut uw::_Unwind_Context) + -> uw::_Unwind_Reason_Code; + } } } else { - match eh_action { - EHAction::None => return continue_unwind(exception_object, context), - EHAction::Cleanup(lpad) | - EHAction::Catch(lpad) => { - uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0, exception_object as uintptr_t); - uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, 0); - uw::_Unwind_SetIP(context, lpad); - return uw::_URC_INSTALL_CONTEXT; + // Default personality routine, which is used directly on most targets + // and indirectly on Windows x86_64 via SEH. + unsafe extern "C" fn rust_eh_personality_impl(version: c_int, + actions: uw::_Unwind_Action, + exception_class: uw::_Unwind_Exception_Class, + exception_object: *mut uw::_Unwind_Exception, + context: *mut uw::_Unwind_Context) + -> uw::_Unwind_Reason_Code { + if version != 1 { + return uw::_URC_FATAL_PHASE1_ERROR; + } + let foreign_exception = exception_class != rust_exception_class(); + let eh_action = match find_eh_action(context, foreign_exception) { + Ok(action) => action, + Err(_) => return uw::_URC_FATAL_PHASE1_ERROR, + }; + if actions as i32 & uw::_UA_SEARCH_PHASE as i32 != 0 { + match eh_action { + EHAction::None | + EHAction::Cleanup(_) => uw::_URC_CONTINUE_UNWIND, + EHAction::Catch(_) => uw::_URC_HANDLER_FOUND, + EHAction::Terminate => uw::_URC_FATAL_PHASE1_ERROR, + } + } else { + match eh_action { + EHAction::None => uw::_URC_CONTINUE_UNWIND, + EHAction::Cleanup(lpad) | + EHAction::Catch(lpad) => { + uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0, + exception_object as uintptr_t); + uw::_Unwind_SetGR(context, UNWIND_DATA_REG.1, 0); + uw::_Unwind_SetIP(context, lpad); + uw::_URC_INSTALL_CONTEXT + } + EHAction::Terminate => uw::_URC_FATAL_PHASE2_ERROR, + } } - EHAction::Terminate => return uw::_URC_FAILURE, } - } - // On ARM EHABI the personality routine is responsible for actually - // unwinding a single stack frame before returning (ARM EHABI Sec. 6.1). - unsafe fn continue_unwind(exception_object: *mut uw::_Unwind_Exception, - context: *mut uw::_Unwind_Context) - -> uw::_Unwind_Reason_Code { - if __gnu_unwind_frame(exception_object, context) == uw::_URC_NO_REASON { - uw::_URC_CONTINUE_UNWIND - } else { - uw::_URC_FAILURE + cfg_if::cfg_if! { + if #[cfg(all(windows, target_arch = "x86_64", target_env = "gnu"))] { + // On x86_64 MinGW targets, the unwinding mechanism is SEH however the unwind + // handler data (aka LSDA) uses GCC-compatible encoding. + #[lang = "eh_personality"] + #[no_mangle] + #[allow(nonstandard_style)] + unsafe extern "C" fn rust_eh_personality(exceptionRecord: *mut uw::EXCEPTION_RECORD, + establisherFrame: uw::LPVOID, + contextRecord: *mut uw::CONTEXT, + dispatcherContext: *mut uw::DISPATCHER_CONTEXT) + -> uw::EXCEPTION_DISPOSITION { + uw::_GCC_specific_handler(exceptionRecord, + establisherFrame, + contextRecord, + dispatcherContext, + rust_eh_personality_impl) + } + } else { + // The personality routine for most of our targets. + #[lang = "eh_personality"] + #[no_mangle] + unsafe extern "C" fn rust_eh_personality(version: c_int, + actions: uw::_Unwind_Action, + exception_class: uw::_Unwind_Exception_Class, + exception_object: *mut uw::_Unwind_Exception, + context: *mut uw::_Unwind_Context) + -> uw::_Unwind_Reason_Code { + rust_eh_personality_impl(version, + actions, + exception_class, + exception_object, + context) + } + } } } - // defined in libgcc - extern "C" { - fn __gnu_unwind_frame(exception_object: *mut uw::_Unwind_Exception, - context: *mut uw::_Unwind_Context) - -> uw::_Unwind_Reason_Code; - } } -unsafe fn find_eh_action(context: *mut uw::_Unwind_Context) +unsafe fn find_eh_action(context: *mut uw::_Unwind_Context, foreign_exception: bool) -> Result<EHAction, ()> { let lsda = uw::_Unwind_GetLanguageSpecificData(context) as *const u8; @@ -273,11 +316,11 @@ unsafe fn find_eh_action(context: *mut uw::_Unwind_Context) get_text_start: &|| uw::_Unwind_GetTextRelBase(context), get_data_start: &|| uw::_Unwind_GetDataRelBase(context), }; - eh::find_eh_action(lsda, &eh_context) + eh::find_eh_action(lsda, &eh_context, foreign_exception) } // See docs in the `unwind` module. -#[cfg(all(target_os="windows", target_arch = "x86", target_env="gnu"))] +#[cfg(all(target_os="windows", any(target_arch = "x86", target_arch = "x86_64"), target_env="gnu"))] #[lang = "eh_unwind_resume"] #[unwind(allowed)] unsafe extern "C" fn rust_eh_unwind_resume(panic_ctx: *mut u8) -> ! { diff --git a/src/libpanic_unwind/lib.rs b/src/libpanic_unwind/lib.rs index 2089a02083c..50d067b56e4 100644 --- a/src/libpanic_unwind/lib.rs +++ b/src/libpanic_unwind/lib.rs @@ -5,9 +5,8 @@ //! essentially gets categorized into three buckets currently: //! //! 1. MSVC targets use SEH in the `seh.rs` file. -//! 2. The 64-bit MinGW target half-uses SEH and half-use gcc-like information -//! in the `seh64_gnu.rs` module. -//! 3. All other targets use libunwind/libgcc in the `gcc/mod.rs` module. +//! 2. Emscripten uses C++ exceptions in the `emcc.rs` file. +//! 3. All other targets use libunwind/libgcc in the `gcc.rs` file. //! //! More documentation about each implementation can be found in the respective //! module. @@ -52,9 +51,6 @@ cfg_if::cfg_if! { } else if #[cfg(target_env = "msvc")] { #[path = "seh.rs"] mod imp; - } else if #[cfg(all(windows, target_arch = "x86_64", target_env = "gnu"))] { - #[path = "seh64_gnu.rs"] - mod imp; } else { // Rust runtime's startup objects depend on these symbols, so make them public. #[cfg(all(target_os="windows", target_arch = "x86", target_env="gnu"))] diff --git a/src/libpanic_unwind/seh64_gnu.rs b/src/libpanic_unwind/seh64_gnu.rs deleted file mode 100644 index 16b699a4437..00000000000 --- a/src/libpanic_unwind/seh64_gnu.rs +++ /dev/null @@ -1,127 +0,0 @@ -//! Unwinding implementation of top of native Win64 SEH, -//! however the unwind handler data (aka LSDA) uses GCC-compatible encoding. - -#![allow(nonstandard_style)] -#![allow(private_no_mangle_fns)] - -use alloc::boxed::Box; - -use core::any::Any; -use core::intrinsics; -use core::ptr; -use crate::dwarf::eh::{EHContext, EHAction, find_eh_action}; -use crate::windows as c; - -// Define our exception codes: -// according to http://msdn.microsoft.com/en-us/library/het71c37(v=VS.80).aspx, -// [31:30] = 3 (error), 2 (warning), 1 (info), 0 (success) -// [29] = 1 (user-defined) -// [28] = 0 (reserved) -// we define bits: -// [24:27] = type -// [0:23] = magic -const ETYPE: c::DWORD = 0b1110_u32 << 28; -const MAGIC: c::DWORD = 0x525354; // "RST" - -const RUST_PANIC: c::DWORD = ETYPE | (1 << 24) | MAGIC; - -#[repr(C)] -struct PanicData { - data: Box<dyn Any + Send>, -} - -pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 { - let panic_ctx = Box::new(PanicData { data }); - let params = [Box::into_raw(panic_ctx) as c::ULONG_PTR]; - c::RaiseException(RUST_PANIC, - c::EXCEPTION_NONCONTINUABLE, - params.len() as c::DWORD, - ¶ms as *const c::ULONG_PTR); - u32::max_value() -} - -pub fn payload() -> *mut u8 { - ptr::null_mut() -} - -pub unsafe fn cleanup(ptr: *mut u8) -> Box<dyn Any + Send> { - let panic_ctx = Box::from_raw(ptr as *mut PanicData); - panic_ctx.data -} - -// SEH doesn't support resuming unwinds after calling a landing pad like -// libunwind does. For this reason, MSVC compiler outlines landing pads into -// separate functions that can be called directly from the personality function -// but are nevertheless able to find and modify stack frame of the "parent" -// function. -// -// Since this cannot be done with libdwarf-style landing pads, -// rust_eh_personality instead catches RUST_PANICs, runs the landing pad, then -// reraises the exception. -// -// Note that it makes certain assumptions about the exception: -// -// 1. That RUST_PANIC is non-continuable, so no lower stack frame may choose to -// resume execution. -// 2. That the first parameter of the exception is a pointer to an extra data -// area (PanicData). -// Since these assumptions do not generally hold true for foreign exceptions -// (system faults, C++ exceptions, etc), we make no attempt to invoke our -// landing pads (and, thus, destructors!) for anything other than RUST_PANICs. -// This is considered acceptable, because the behavior of throwing exceptions -// through a C ABI boundary is undefined. - -#[lang = "eh_personality"] -#[cfg(not(test))] -unsafe extern "C" fn rust_eh_personality(exceptionRecord: *mut c::EXCEPTION_RECORD, - establisherFrame: c::LPVOID, - contextRecord: *mut c::CONTEXT, - dispatcherContext: *mut c::DISPATCHER_CONTEXT) - -> c::EXCEPTION_DISPOSITION { - let er = &*exceptionRecord; - let dc = &*dispatcherContext; - - if er.ExceptionFlags & c::EXCEPTION_UNWIND == 0 { - // we are in the dispatch phase - if er.ExceptionCode == RUST_PANIC { - if let Some(lpad) = find_landing_pad(dc) { - c::RtlUnwindEx(establisherFrame, - lpad as c::LPVOID, - exceptionRecord, - er.ExceptionInformation[0] as c::LPVOID, // pointer to PanicData - contextRecord, - dc.HistoryTable); - } - } - } - c::ExceptionContinueSearch -} - -#[lang = "eh_unwind_resume"] -#[unwind(allowed)] -unsafe extern "C" fn rust_eh_unwind_resume(panic_ctx: c::LPVOID) -> ! { - let params = [panic_ctx as c::ULONG_PTR]; - c::RaiseException(RUST_PANIC, - c::EXCEPTION_NONCONTINUABLE, - params.len() as c::DWORD, - ¶ms as *const c::ULONG_PTR); - intrinsics::abort(); -} - -unsafe fn find_landing_pad(dc: &c::DISPATCHER_CONTEXT) -> Option<usize> { - let eh_ctx = EHContext { - // The return address points 1 byte past the call instruction, - // which could be in the next IP range in LSDA range table. - ip: dc.ControlPc as usize - 1, - func_start: dc.ImageBase as usize + (*dc.FunctionEntry).BeginAddress as usize, - get_text_start: &|| dc.ImageBase as usize, - get_data_start: &|| unimplemented!(), - }; - match find_eh_action(dc.HandlerData, &eh_ctx) { - Err(_) | - Ok(EHAction::None) => None, - Ok(EHAction::Cleanup(lpad)) | - Ok(EHAction::Catch(lpad)) => Some(lpad), - Ok(EHAction::Terminate) => intrinsics::abort(), - } -} |
