Allow foreign exceptions to unwind through Rust code
This commit is contained in:
parent
9a8bb3a26e
commit
5f1a0afd22
@ -249,9 +249,8 @@ the source code.
|
||||
- Runtime
|
||||
- `start`: `libstd/rt.rs`
|
||||
- `eh_personality`: `libpanic_unwind/emcc.rs` (EMCC)
|
||||
- `eh_personality`: `libpanic_unwind/seh64_gnu.rs` (SEH64 GNU)
|
||||
- `eh_personality`: `libpanic_unwind/gcc.rs` (GNU)
|
||||
- `eh_personality`: `libpanic_unwind/seh.rs` (SEH)
|
||||
- `eh_unwind_resume`: `libpanic_unwind/seh64_gnu.rs` (SEH64 GNU)
|
||||
- `eh_unwind_resume`: `libpanic_unwind/gcc.rs` (GCC)
|
||||
- `msvc_try_filter`: `libpanic_unwind/seh.rs` (SEH)
|
||||
- `panic`: `libcore/panicking.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)
|
||||
}
|
||||
}
|
||||
|
@ -133,133 +133,176 @@ fn rust_exception_class() -> uw::_Unwind_Exception_Class {
|
||||
// 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,
|
||||
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;
|
||||
};
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 => 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
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
EHAction::Terminate => uw::_URC_FATAL_PHASE2_ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
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,
|
||||
}
|
||||
} 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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
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) -> ! {
|
||||
|
@ -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 @@
|
||||
} 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"))]
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
@ -244,3 +244,30 @@ pub unsafe fn _Unwind_RaiseException(exc: *mut _Unwind_Exception) -> _Unwind_Rea
|
||||
}
|
||||
}
|
||||
} // cfg_if!
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(all(windows, target_arch = "x86_64", target_env = "gnu"))] {
|
||||
// We declare these as opaque types. This is fine since you just need to
|
||||
// pass them to _GCC_specific_handler and forget about them.
|
||||
pub enum EXCEPTION_RECORD {}
|
||||
pub type LPVOID = *mut c_void;
|
||||
pub enum CONTEXT {}
|
||||
pub enum DISPATCHER_CONTEXT {}
|
||||
pub type EXCEPTION_DISPOSITION = c_int;
|
||||
type PersonalityFn = unsafe extern "C" fn(version: c_int,
|
||||
actions: _Unwind_Action,
|
||||
exception_class: _Unwind_Exception_Class,
|
||||
exception_object: *mut _Unwind_Exception,
|
||||
context: *mut _Unwind_Context)
|
||||
-> _Unwind_Reason_Code;
|
||||
|
||||
extern "C" {
|
||||
pub fn _GCC_specific_handler(exceptionRecord: *mut EXCEPTION_RECORD,
|
||||
establisherFrame: LPVOID,
|
||||
contextRecord: *mut CONTEXT,
|
||||
dispatcherContext: *mut DISPATCHER_CONTEXT,
|
||||
personality: PersonalityFn)
|
||||
-> EXCEPTION_DISPOSITION;
|
||||
}
|
||||
}
|
||||
} // cfg_if!
|
||||
|
10
src/test/run-make-fulldeps/foreign-exceptions/Makefile
Normal file
10
src/test/run-make-fulldeps/foreign-exceptions/Makefile
Normal file
@ -0,0 +1,10 @@
|
||||
-include ../tools.mk
|
||||
|
||||
all: foo
|
||||
$(call RUN,foo)
|
||||
|
||||
foo: foo.rs $(call NATIVE_STATICLIB,foo)
|
||||
$(RUSTC) $< -lfoo $(EXTRACXXFLAGS)
|
||||
|
||||
$(TMPDIR)/libfoo.o: foo.cpp
|
||||
$(call COMPILE_OBJ_CXX,$@,$<)
|
59
src/test/run-make-fulldeps/foreign-exceptions/foo.cpp
Normal file
59
src/test/run-make-fulldeps/foreign-exceptions/foo.cpp
Normal file
@ -0,0 +1,59 @@
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void println(const char* s) {
|
||||
puts(s);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
struct exception {};
|
||||
|
||||
struct drop_check {
|
||||
bool* ok;
|
||||
~drop_check() {
|
||||
println("~drop_check");
|
||||
|
||||
if (ok)
|
||||
*ok = true;
|
||||
}
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
void rust_catch_callback(void (*cb)(), bool* rust_ok);
|
||||
|
||||
static void callback() {
|
||||
println("throwing C++ exception");
|
||||
throw exception();
|
||||
}
|
||||
|
||||
void throw_cxx_exception() {
|
||||
bool rust_ok = false;
|
||||
try {
|
||||
rust_catch_callback(callback, &rust_ok);
|
||||
assert(false && "unreachable");
|
||||
} catch (exception e) {
|
||||
println("caught C++ exception");
|
||||
assert(rust_ok);
|
||||
return;
|
||||
}
|
||||
assert(false && "did not catch thrown C++ exception");
|
||||
}
|
||||
|
||||
void cxx_catch_callback(void (*cb)(), bool* cxx_ok) {
|
||||
drop_check x;
|
||||
x.ok = NULL;
|
||||
try {
|
||||
cb();
|
||||
} catch (exception e) {
|
||||
assert(false && "shouldn't be able to catch a rust panic");
|
||||
} catch (...) {
|
||||
println("caught foreign exception in catch (...)");
|
||||
// Foreign exceptions are caught by catch (...). We only set the ok
|
||||
// flag if we successfully caught the panic. The destructor of
|
||||
// drop_check will then set the flag to true if it is executed.
|
||||
x.ok = cxx_ok;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
63
src/test/run-make-fulldeps/foreign-exceptions/foo.rs
Normal file
63
src/test/run-make-fulldeps/foreign-exceptions/foo.rs
Normal file
@ -0,0 +1,63 @@
|
||||
// Tests that C++ exceptions can unwind through Rust code, run destructors and
|
||||
// are ignored by catch_unwind. Also tests that Rust panics can unwind through
|
||||
// C++ code.
|
||||
|
||||
#![feature(unwind_attributes)]
|
||||
|
||||
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||
|
||||
struct DropCheck<'a>(&'a mut bool);
|
||||
impl<'a> Drop for DropCheck<'a> {
|
||||
fn drop(&mut self) {
|
||||
println!("DropCheck::drop");
|
||||
*self.0 = true;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn throw_cxx_exception();
|
||||
|
||||
#[unwind(allowed)]
|
||||
fn cxx_catch_callback(cb: extern "C" fn(), ok: *mut bool);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[unwind(allowed)]
|
||||
extern "C" fn rust_catch_callback(cb: extern "C" fn(), rust_ok: &mut bool) {
|
||||
let _caught_unwind = catch_unwind(AssertUnwindSafe(|| {
|
||||
let _drop = DropCheck(rust_ok);
|
||||
cb();
|
||||
unreachable!("should have unwound instead of returned");
|
||||
}));
|
||||
unreachable!("catch_unwind should not have caught foreign exception");
|
||||
}
|
||||
|
||||
fn throw_rust_panic() {
|
||||
#[unwind(allowed)]
|
||||
extern "C" fn callback() {
|
||||
println!("throwing rust panic");
|
||||
panic!(1234i32);
|
||||
}
|
||||
|
||||
let mut dropped = false;
|
||||
let mut cxx_ok = false;
|
||||
let caught_unwind = catch_unwind(AssertUnwindSafe(|| {
|
||||
let _drop = DropCheck(&mut dropped);
|
||||
unsafe {
|
||||
cxx_catch_callback(callback, &mut cxx_ok);
|
||||
}
|
||||
unreachable!("should have unwound instead of returned");
|
||||
}));
|
||||
println!("caught rust panic");
|
||||
assert!(dropped);
|
||||
assert!(caught_unwind.is_err());
|
||||
let panic_obj = caught_unwind.unwrap_err();
|
||||
let panic_int = *panic_obj.downcast_ref::<i32>().unwrap();
|
||||
assert_eq!(panic_int, 1234);
|
||||
assert!(cxx_ok);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
unsafe { throw_cxx_exception() };
|
||||
throw_rust_panic();
|
||||
}
|
Loading…
Reference in New Issue
Block a user