Auto merge of #65646 - Amanieu:foreign-exceptions, r=nikomatsakis
Allow foreign exceptions to unwind through Rust code and Rust panics to unwind through FFI This PR fixes interactions between Rust panics and foreign (mainly C++) exceptions. C++ exceptions (and other FFI exceptions) can now safely unwind through Rust code: - The FFI function causing the unwind must be marked with `#[unwind(allowed)]`. If this is not the case then LLVM may optimize landing pads away with the assumption that they are unreachable. - Drop code will be executed as the exception unwinds through the stack, as with a Rust panic. - `catch_unwind` will *not* catch the exception, instead the exception will silently continue unwinding past it. Rust panics can now safely unwind through C++ code: - C++ destructors will be called as the stack unwinds. - The Rust panic can only be caught with `catch (...)`, after which it can be either rethrown or discarded. - C++ cannot name the type of the Rust exception object used for unwinding, which means that it can't be caught explicitly or have its contents inspected. Tests have been added to ensure all of the above works correctly. Some notes about non-C++ exceptions: - `pthread_cancel` and `pthread_exit` use unwinding on glibc. This has the same behavior as a C++ exception: destructors are run but it cannot be caught by `catch_unwind`. - `longjmp` on Windows is implemented using unwinding. Destructors are run on MSVC, but not on MinGW. In both cases the unwind cannot be caught by `catch_unwind`. - As with C++ exceptions, you need to mark the relevant FFI functions with `#[unwind(allowed)]`, otherwise LLVM will optimize out the destructors since they seem unreachable. I haven't updated any of the documentation, so officially unwinding through FFI is still UB. However this is a step towards making it well-defined. Fixes #65441 cc @gnzlbg r? @alexcrichton
This commit is contained in:
commit
b520af6fd5
@ -249,11 +249,11 @@ 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)
|
||||
- `eh_catch_typeinfo`: `libpanic_unwind/seh.rs` (SEH)
|
||||
- `eh_catch_typeinfo`: `libpanic_unwind/emcc.rs` (EMCC)
|
||||
- `panic`: `libcore/panicking.rs`
|
||||
- `panic_bounds_check`: `libcore/panicking.rs`
|
||||
- `panic_impl`: `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)
|
||||
}
|
||||
}
|
||||
|
@ -15,14 +15,48 @@ use alloc::boxed::Box;
|
||||
use libc::{self, c_int};
|
||||
use unwind as uw;
|
||||
|
||||
// This matches the layout of std::type_info in C++
|
||||
#[repr(C)]
|
||||
struct TypeInfo {
|
||||
vtable: *const usize,
|
||||
name: *const u8,
|
||||
}
|
||||
unsafe impl Sync for TypeInfo {}
|
||||
|
||||
extern "C" {
|
||||
// The leading `\x01` byte here is actually a magical signal to LLVM to
|
||||
// *not* apply any other mangling like prefixing with a `_` character.
|
||||
//
|
||||
// This symbol is the vtable used by C++'s `std::type_info`. Objects of type
|
||||
// `std::type_info`, type descriptors, have a pointer to this table. Type
|
||||
// descriptors are referenced by the C++ EH structures defined above and
|
||||
// that we construct below.
|
||||
//
|
||||
// Note that the real size is larger than 3 usize, but we only need our
|
||||
// vtable to point to the third element.
|
||||
#[link_name = "\x01_ZTVN10__cxxabiv117__class_type_infoE"]
|
||||
static CLASS_TYPE_INFO_VTABLE: [usize; 3];
|
||||
}
|
||||
|
||||
// std::type_info for a rust_panic class
|
||||
#[lang = "eh_catch_typeinfo"]
|
||||
static EXCEPTION_TYPE_INFO: TypeInfo = TypeInfo {
|
||||
// Normally we would use .as_ptr().add(2) but this doesn't work in a const context.
|
||||
vtable: unsafe { &CLASS_TYPE_INFO_VTABLE[2] },
|
||||
// This intentionally doesn't use the normal name mangling scheme because
|
||||
// we don't want C++ to be able to produce or catch Rust panics.
|
||||
name: b"rust_panic\0".as_ptr(),
|
||||
};
|
||||
|
||||
pub fn payload() -> *mut u8 {
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
pub unsafe fn cleanup(ptr: *mut u8) -> Box<dyn Any + Send> {
|
||||
assert!(!ptr.is_null());
|
||||
let ex = ptr::read(ptr as *mut _);
|
||||
__cxa_free_exception(ptr as *mut _);
|
||||
let adjusted_ptr = __cxa_begin_catch(ptr as *mut libc::c_void);
|
||||
let ex = ptr::read(adjusted_ptr as *mut _);
|
||||
__cxa_end_catch();
|
||||
ex
|
||||
}
|
||||
|
||||
@ -32,11 +66,8 @@ pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
|
||||
if exception == ptr::null_mut() {
|
||||
return uw::_URC_FATAL_PHASE1_ERROR as u32;
|
||||
}
|
||||
let exception = exception as *mut Box<dyn Any + Send>;
|
||||
ptr::write(exception, data);
|
||||
__cxa_throw(exception as *mut _, ptr::null_mut(), ptr::null_mut());
|
||||
|
||||
unreachable!()
|
||||
ptr::write(exception as *mut _, data);
|
||||
__cxa_throw(exception as *mut _, &EXCEPTION_TYPE_INFO, ptr::null_mut());
|
||||
}
|
||||
|
||||
#[lang = "eh_personality"]
|
||||
@ -52,10 +83,11 @@ unsafe extern "C" fn rust_eh_personality(version: c_int,
|
||||
|
||||
extern "C" {
|
||||
fn __cxa_allocate_exception(thrown_size: libc::size_t) -> *mut libc::c_void;
|
||||
fn __cxa_free_exception(thrown_exception: *mut libc::c_void);
|
||||
fn __cxa_begin_catch(thrown_exception: *mut libc::c_void) -> *mut libc::c_void;
|
||||
fn __cxa_end_catch();
|
||||
fn __cxa_throw(thrown_exception: *mut libc::c_void,
|
||||
tinfo: *mut libc::c_void,
|
||||
dest: *mut libc::c_void);
|
||||
tinfo: *const TypeInfo,
|
||||
dest: *mut libc::c_void) -> !;
|
||||
fn __gxx_personality_v0(version: c_int,
|
||||
actions: uw::_Unwind_Action,
|
||||
exception_class: uw::_Unwind_Exception_Class,
|
||||
|
@ -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,
|
||||
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 @@ 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"))]
|
||||
@ -65,7 +61,6 @@ cfg_if::cfg_if! {
|
||||
}
|
||||
|
||||
mod dwarf;
|
||||
mod windows;
|
||||
|
||||
// Entry point for catching an exception, implemented using the `try` intrinsic
|
||||
// in the compiler.
|
||||
|
@ -51,9 +51,7 @@ use alloc::boxed::Box;
|
||||
use core::any::Any;
|
||||
use core::mem;
|
||||
use core::raw;
|
||||
|
||||
use crate::windows as c;
|
||||
use libc::{c_int, c_uint};
|
||||
use libc::{c_int, c_uint, c_void};
|
||||
|
||||
// First up, a whole bunch of type definitions. There's a few platform-specific
|
||||
// oddities here, and a lot that's just blatantly copied from LLVM. The purpose
|
||||
@ -76,18 +74,19 @@ use libc::{c_int, c_uint};
|
||||
// sort of operation. For example, if you compile this C++ code on MSVC and emit
|
||||
// the LLVM IR:
|
||||
//
|
||||
// #include <stdin.h>
|
||||
// #include <stdint.h>
|
||||
//
|
||||
// struct rust_panic {
|
||||
// uint64_t x[2];
|
||||
// }
|
||||
//
|
||||
// void foo() {
|
||||
// uint64_t a[2] = {0, 1};
|
||||
// rust_panic a = {0, 1};
|
||||
// throw a;
|
||||
// }
|
||||
//
|
||||
// That's essentially what we're trying to emulate. Most of the constant values
|
||||
// below were just copied from LLVM, I'm at least not 100% sure what's going on
|
||||
// everywhere. For example the `.PA_K\0` and `.PEA_K\0` strings below (stuck in
|
||||
// the names of a few of these) I'm not actually sure what they do, but it seems
|
||||
// to mirror what LLVM does!
|
||||
// below were just copied from LLVM,
|
||||
//
|
||||
// In any case, these structures are all constructed in a similar manner, and
|
||||
// it's just somewhat verbose for us.
|
||||
@ -98,10 +97,9 @@ use libc::{c_int, c_uint};
|
||||
#[macro_use]
|
||||
mod imp {
|
||||
pub type ptr_t = *mut u8;
|
||||
pub const OFFSET: i32 = 4;
|
||||
|
||||
#[cfg(bootstrap)]
|
||||
pub const NAME1: [u8; 7] = [b'.', b'P', b'A', b'_', b'K', 0, 0];
|
||||
pub const NAME2: [u8; 7] = [b'.', b'P', b'A', b'X', 0, 0, 0];
|
||||
|
||||
macro_rules! ptr {
|
||||
(0) => (core::ptr::null_mut());
|
||||
@ -113,10 +111,9 @@ mod imp {
|
||||
#[macro_use]
|
||||
mod imp {
|
||||
pub type ptr_t = u32;
|
||||
pub const OFFSET: i32 = 8;
|
||||
|
||||
#[cfg(bootstrap)]
|
||||
pub const NAME1: [u8; 7] = [b'.', b'P', b'E', b'A', b'_', b'K', 0];
|
||||
pub const NAME2: [u8; 7] = [b'.', b'P', b'E', b'A', b'X', 0, 0];
|
||||
|
||||
extern "C" {
|
||||
pub static __ImageBase: u8;
|
||||
@ -141,7 +138,7 @@ pub struct _ThrowInfo {
|
||||
#[repr(C)]
|
||||
pub struct _CatchableTypeArray {
|
||||
pub nCatchableTypes: c_int,
|
||||
pub arrayOfCatchableTypes: [imp::ptr_t; 2],
|
||||
pub arrayOfCatchableTypes: [imp::ptr_t; 1],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
@ -164,9 +161,19 @@ pub struct _PMD {
|
||||
pub struct _TypeDescriptor {
|
||||
pub pVFTable: *const u8,
|
||||
pub spare: *mut u8,
|
||||
#[cfg(bootstrap)]
|
||||
pub name: [u8; 7],
|
||||
#[cfg(not(bootstrap))]
|
||||
pub name: [u8; 11],
|
||||
}
|
||||
|
||||
// Note that we intentionally ignore name mangling rules here: we don't want C++
|
||||
// to be able to catch Rust panics by simply declaring a `struct rust_panic`.
|
||||
#[cfg(bootstrap)]
|
||||
use imp::NAME1 as TYPE_NAME;
|
||||
#[cfg(not(bootstrap))]
|
||||
const TYPE_NAME: [u8; 11] = *b"rust_panic\0";
|
||||
|
||||
static mut THROW_INFO: _ThrowInfo = _ThrowInfo {
|
||||
attributes: 0,
|
||||
pnfnUnwind: ptr!(0),
|
||||
@ -175,31 +182,22 @@ static mut THROW_INFO: _ThrowInfo = _ThrowInfo {
|
||||
};
|
||||
|
||||
static mut CATCHABLE_TYPE_ARRAY: _CatchableTypeArray = _CatchableTypeArray {
|
||||
nCatchableTypes: 2,
|
||||
arrayOfCatchableTypes: [ptr!(0), ptr!(0)],
|
||||
nCatchableTypes: 1,
|
||||
arrayOfCatchableTypes: [ptr!(0)],
|
||||
};
|
||||
|
||||
static mut CATCHABLE_TYPE1: _CatchableType = _CatchableType {
|
||||
properties: 1,
|
||||
static mut CATCHABLE_TYPE: _CatchableType = _CatchableType {
|
||||
properties: 0,
|
||||
pType: ptr!(0),
|
||||
thisDisplacement: _PMD {
|
||||
mdisp: 0,
|
||||
pdisp: -1,
|
||||
vdisp: 0,
|
||||
},
|
||||
sizeOrOffset: imp::OFFSET,
|
||||
copy_function: ptr!(0),
|
||||
};
|
||||
|
||||
static mut CATCHABLE_TYPE2: _CatchableType = _CatchableType {
|
||||
properties: 1,
|
||||
pType: ptr!(0),
|
||||
thisDisplacement: _PMD {
|
||||
mdisp: 0,
|
||||
pdisp: -1,
|
||||
vdisp: 0,
|
||||
},
|
||||
sizeOrOffset: imp::OFFSET,
|
||||
#[cfg(bootstrap)]
|
||||
sizeOrOffset: mem::size_of::<*mut u64>() as c_int,
|
||||
#[cfg(not(bootstrap))]
|
||||
sizeOrOffset: mem::size_of::<[u64; 2]>() as c_int,
|
||||
copy_function: ptr!(0),
|
||||
};
|
||||
|
||||
@ -215,22 +213,17 @@ extern "C" {
|
||||
static TYPE_INFO_VTABLE: *const u8;
|
||||
}
|
||||
|
||||
// We use #[lang = "msvc_try_filter"] here as this is the type descriptor which
|
||||
// We use #[lang = "eh_catch_typeinfo"] here as this is the type descriptor which
|
||||
// we'll use in LLVM's `catchpad` instruction which ends up also being passed as
|
||||
// an argument to the C++ personality function.
|
||||
//
|
||||
// Again, I'm not entirely sure what this is describing, it just seems to work.
|
||||
#[cfg_attr(not(test), lang = "msvc_try_filter")]
|
||||
static mut TYPE_DESCRIPTOR1: _TypeDescriptor = _TypeDescriptor {
|
||||
#[cfg_attr(bootstrap, lang = "msvc_try_filter")]
|
||||
#[cfg_attr(not(any(test, bootstrap)), lang = "eh_catch_typeinfo")]
|
||||
static mut TYPE_DESCRIPTOR: _TypeDescriptor = _TypeDescriptor {
|
||||
pVFTable: unsafe { &TYPE_INFO_VTABLE } as *const _ as *const _,
|
||||
spare: core::ptr::null_mut(),
|
||||
name: imp::NAME1,
|
||||
};
|
||||
|
||||
static mut TYPE_DESCRIPTOR2: _TypeDescriptor = _TypeDescriptor {
|
||||
pVFTable: unsafe { &TYPE_INFO_VTABLE } as *const _ as *const _,
|
||||
spare: core::ptr::null_mut(),
|
||||
name: imp::NAME2,
|
||||
name: TYPE_NAME,
|
||||
};
|
||||
|
||||
pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
|
||||
@ -246,6 +239,11 @@ pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
|
||||
let ptrs = mem::transmute::<_, raw::TraitObject>(data);
|
||||
let mut ptrs = [ptrs.data as u64, ptrs.vtable as u64];
|
||||
let mut ptrs_ptr = ptrs.as_mut_ptr();
|
||||
let throw_ptr = if cfg!(bootstrap) {
|
||||
&mut ptrs_ptr as *mut _ as *mut _
|
||||
} else {
|
||||
ptrs_ptr as *mut _
|
||||
};
|
||||
|
||||
// This... may seems surprising, and justifiably so. On 32-bit MSVC the
|
||||
// pointers between these structure are just that, pointers. On 64-bit MSVC,
|
||||
@ -270,17 +268,17 @@ pub unsafe fn panic(data: Box<dyn Any + Send>) -> u32 {
|
||||
atomic_store(&mut THROW_INFO.pCatchableTypeArray as *mut _ as *mut u32,
|
||||
ptr!(&CATCHABLE_TYPE_ARRAY as *const _) as u32);
|
||||
atomic_store(&mut CATCHABLE_TYPE_ARRAY.arrayOfCatchableTypes[0] as *mut _ as *mut u32,
|
||||
ptr!(&CATCHABLE_TYPE1 as *const _) as u32);
|
||||
atomic_store(&mut CATCHABLE_TYPE_ARRAY.arrayOfCatchableTypes[1] as *mut _ as *mut u32,
|
||||
ptr!(&CATCHABLE_TYPE2 as *const _) as u32);
|
||||
atomic_store(&mut CATCHABLE_TYPE1.pType as *mut _ as *mut u32,
|
||||
ptr!(&TYPE_DESCRIPTOR1 as *const _) as u32);
|
||||
atomic_store(&mut CATCHABLE_TYPE2.pType as *mut _ as *mut u32,
|
||||
ptr!(&TYPE_DESCRIPTOR2 as *const _) as u32);
|
||||
ptr!(&CATCHABLE_TYPE as *const _) as u32);
|
||||
atomic_store(&mut CATCHABLE_TYPE.pType as *mut _ as *mut u32,
|
||||
ptr!(&TYPE_DESCRIPTOR as *const _) as u32);
|
||||
|
||||
c::_CxxThrowException(&mut ptrs_ptr as *mut _ as *mut _,
|
||||
&mut THROW_INFO as *mut _ as *mut _);
|
||||
u32::max_value()
|
||||
extern "system" {
|
||||
#[unwind(allowed)]
|
||||
pub fn _CxxThrowException(pExceptionObject: *mut c_void, pThrowInfo: *mut u8) -> !;
|
||||
}
|
||||
|
||||
_CxxThrowException(throw_ptr,
|
||||
&mut THROW_INFO as *mut _ as *mut _);
|
||||
}
|
||||
|
||||
pub fn payload() -> [u64; 2] {
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
#![allow(nonstandard_style)]
|
||||
#![allow(dead_code)]
|
||||
#![cfg(windows)]
|
||||
|
||||
use libc::{c_long, c_ulong, c_void};
|
||||
|
||||
pub type DWORD = c_ulong;
|
||||
pub type LONG = c_long;
|
||||
pub type ULONG_PTR = usize;
|
||||
pub type LPVOID = *mut c_void;
|
||||
|
||||
pub const EXCEPTION_MAXIMUM_PARAMETERS: usize = 15;
|
||||
pub const EXCEPTION_NONCONTINUABLE: DWORD = 0x1; // Noncontinuable exception
|
||||
pub const EXCEPTION_UNWINDING: DWORD = 0x2; // Unwind is in progress
|
||||
pub const EXCEPTION_EXIT_UNWIND: DWORD = 0x4; // Exit unwind is in progress
|
||||
pub const EXCEPTION_TARGET_UNWIND: DWORD = 0x20; // Target unwind in progress
|
||||
pub const EXCEPTION_COLLIDED_UNWIND: DWORD = 0x40; // Collided exception handler call
|
||||
pub const EXCEPTION_UNWIND: DWORD = EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND |
|
||||
EXCEPTION_TARGET_UNWIND |
|
||||
EXCEPTION_COLLIDED_UNWIND;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct EXCEPTION_RECORD {
|
||||
pub ExceptionCode: DWORD,
|
||||
pub ExceptionFlags: DWORD,
|
||||
pub ExceptionRecord: *mut EXCEPTION_RECORD,
|
||||
pub ExceptionAddress: LPVOID,
|
||||
pub NumberParameters: DWORD,
|
||||
pub ExceptionInformation: [LPVOID; EXCEPTION_MAXIMUM_PARAMETERS],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct EXCEPTION_POINTERS {
|
||||
pub ExceptionRecord: *mut EXCEPTION_RECORD,
|
||||
pub ContextRecord: *mut CONTEXT,
|
||||
}
|
||||
|
||||
pub enum UNWIND_HISTORY_TABLE {}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct RUNTIME_FUNCTION {
|
||||
pub BeginAddress: DWORD,
|
||||
pub EndAddress: DWORD,
|
||||
pub UnwindData: DWORD,
|
||||
}
|
||||
|
||||
pub enum CONTEXT {}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct DISPATCHER_CONTEXT {
|
||||
pub ControlPc: LPVOID,
|
||||
pub ImageBase: LPVOID,
|
||||
pub FunctionEntry: *const RUNTIME_FUNCTION,
|
||||
pub EstablisherFrame: LPVOID,
|
||||
pub TargetIp: LPVOID,
|
||||
pub ContextRecord: *const CONTEXT,
|
||||
pub LanguageHandler: LPVOID,
|
||||
pub HandlerData: *const u8,
|
||||
pub HistoryTable: *const UNWIND_HISTORY_TABLE,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub enum EXCEPTION_DISPOSITION {
|
||||
ExceptionContinueExecution,
|
||||
ExceptionContinueSearch,
|
||||
ExceptionNestedException,
|
||||
ExceptionCollidedUnwind,
|
||||
}
|
||||
pub use self::EXCEPTION_DISPOSITION::*;
|
||||
|
||||
extern "system" {
|
||||
#[unwind(allowed)]
|
||||
pub fn RaiseException(dwExceptionCode: DWORD,
|
||||
dwExceptionFlags: DWORD,
|
||||
nNumberOfArguments: DWORD,
|
||||
lpArguments: *const ULONG_PTR);
|
||||
#[unwind(allowed)]
|
||||
pub fn RtlUnwindEx(TargetFrame: LPVOID,
|
||||
TargetIp: LPVOID,
|
||||
ExceptionRecord: *const EXCEPTION_RECORD,
|
||||
ReturnValue: LPVOID,
|
||||
OriginalContext: *const CONTEXT,
|
||||
HistoryTable: *const UNWIND_HISTORY_TABLE);
|
||||
#[unwind(allowed)]
|
||||
pub fn _CxxThrowException(pExceptionObject: *mut c_void, pThrowInfo: *mut u8);
|
||||
}
|
@ -385,7 +385,7 @@ language_item_table! {
|
||||
|
||||
EhPersonalityLangItem, "eh_personality", eh_personality, Target::Fn;
|
||||
EhUnwindResumeLangItem, "eh_unwind_resume", eh_unwind_resume, Target::Fn;
|
||||
MSVCTryFilterLangItem, "msvc_try_filter", msvc_try_filter, Target::Static;
|
||||
EhCatchTypeinfoLangItem, "eh_catch_typeinfo", eh_catch_typeinfo, Target::Static;
|
||||
|
||||
OwnedBoxLangItem, "owned_box", owned_box, Target::Struct;
|
||||
|
||||
|
@ -849,7 +849,7 @@ fn codegen_msvc_try(
|
||||
// We're generating an IR snippet that looks like:
|
||||
//
|
||||
// declare i32 @rust_try(%func, %data, %ptr) {
|
||||
// %slot = alloca i64*
|
||||
// %slot = alloca [2 x i64]
|
||||
// invoke %func(%data) to label %normal unwind label %catchswitch
|
||||
//
|
||||
// normal:
|
||||
@ -873,21 +873,25 @@ fn codegen_msvc_try(
|
||||
//
|
||||
// #include <stdint.h>
|
||||
//
|
||||
// struct rust_panic {
|
||||
// uint64_t x[2];
|
||||
// }
|
||||
//
|
||||
// int bar(void (*foo)(void), uint64_t *ret) {
|
||||
// try {
|
||||
// foo();
|
||||
// return 0;
|
||||
// } catch(uint64_t a[2]) {
|
||||
// ret[0] = a[0];
|
||||
// ret[1] = a[1];
|
||||
// } catch(rust_panic a) {
|
||||
// ret[0] = a.x[0];
|
||||
// ret[1] = a.x[1];
|
||||
// return 1;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// More information can be found in libstd's seh.rs implementation.
|
||||
let i64p = bx.type_ptr_to(bx.type_i64());
|
||||
let ptr_align = bx.tcx().data_layout.pointer_align.abi;
|
||||
let slot = bx.alloca(i64p, ptr_align);
|
||||
let i64_2 = bx.type_array(bx.type_i64(), 2);
|
||||
let i64_align = bx.tcx().data_layout.i64_align.abi;
|
||||
let slot = bx.alloca(i64_2, i64_align);
|
||||
bx.invoke(func, &[data], normal.llbb(), catchswitch.llbb(), None);
|
||||
|
||||
normal.ret(bx.const_i32(0));
|
||||
@ -895,22 +899,15 @@ fn codegen_msvc_try(
|
||||
let cs = catchswitch.catch_switch(None, None, 1);
|
||||
catchswitch.add_handler(cs, catchpad.llbb());
|
||||
|
||||
let tydesc = match bx.tcx().lang_items().msvc_try_filter() {
|
||||
let tydesc = match bx.tcx().lang_items().eh_catch_typeinfo() {
|
||||
Some(did) => bx.get_static(did),
|
||||
None => bug!("msvc_try_filter not defined"),
|
||||
None => bug!("eh_catch_typeinfo not defined, but needed for SEH unwinding"),
|
||||
};
|
||||
let funclet = catchpad.catch_pad(cs, &[tydesc, bx.const_i32(0), slot]);
|
||||
let addr = catchpad.load(slot, ptr_align);
|
||||
|
||||
let i64_align = bx.tcx().data_layout.i64_align.abi;
|
||||
let arg1 = catchpad.load(addr, i64_align);
|
||||
let val1 = bx.const_i32(1);
|
||||
let gep1 = catchpad.inbounds_gep(addr, &[val1]);
|
||||
let arg2 = catchpad.load(gep1, i64_align);
|
||||
let local_ptr = catchpad.bitcast(local_ptr, i64p);
|
||||
let gep2 = catchpad.inbounds_gep(local_ptr, &[val1]);
|
||||
catchpad.store(arg1, local_ptr, i64_align);
|
||||
catchpad.store(arg2, gep2, i64_align);
|
||||
let payload = catchpad.load(slot, i64_align);
|
||||
let local_ptr = catchpad.bitcast(local_ptr, bx.type_ptr_to(i64_2));
|
||||
catchpad.store(payload, local_ptr, i64_align);
|
||||
catchpad.catch_ret(&funclet, caught.llbb());
|
||||
|
||||
caught.ret(bx.const_i32(1));
|
||||
@ -978,7 +975,14 @@ fn codegen_gnu_try(
|
||||
// rust_try ignores the selector.
|
||||
let lpad_ty = bx.type_struct(&[bx.type_i8p(), bx.type_i32()], false);
|
||||
let vals = catch.landing_pad(lpad_ty, bx.eh_personality(), 1);
|
||||
catch.add_clause(vals, bx.const_null(bx.type_i8p()));
|
||||
let tydesc = match bx.tcx().lang_items().eh_catch_typeinfo() {
|
||||
Some(tydesc) => {
|
||||
let tydesc = bx.get_static(tydesc);
|
||||
bx.bitcast(tydesc, bx.type_i8p())
|
||||
}
|
||||
None => bx.const_null(bx.type_i8p()),
|
||||
};
|
||||
catch.add_clause(vals, tydesc);
|
||||
let ptr = catch.extract_value(vals, 0);
|
||||
let ptr_align = bx.tcx().data_layout.pointer_align.abi;
|
||||
let bitcast = catch.bitcast(local_ptr, bx.type_ptr_to(bx.type_i8p()));
|
||||
|
@ -244,3 +244,30 @@ if #[cfg(not(all(target_os = "ios", target_arch = "arm")))] {
|
||||
}
|
||||
}
|
||||
} // 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 $(EXTRARSCXXFLAGS)
|
||||
|
||||
$(TMPDIR)/libfoo.o: foo.cpp
|
||||
$(call COMPILE_OBJ_CXX,$@,$<)
|
60
src/test/run-make-fulldeps/foreign-exceptions/foo.cpp
Normal file
60
src/test/run-make-fulldeps/foreign-exceptions/foo.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void println(const char* s) {
|
||||
puts(s);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
struct exception {};
|
||||
struct rust_panic {};
|
||||
|
||||
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 (rust_panic 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;
|
||||
}
|
||||
}
|
||||
}
|
66
src/test/run-make-fulldeps/foreign-exceptions/foo.rs
Normal file
66
src/test/run-make-fulldeps/foreign-exceptions/foo.rs
Normal file
@ -0,0 +1,66 @@
|
||||
// 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.
|
||||
|
||||
// For linking libstdc++ on MinGW
|
||||
#![cfg_attr(all(windows, target_env = "gnu"), feature(static_nobundle))]
|
||||
|
||||
#![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();
|
||||
}
|
@ -6,7 +6,7 @@ all: foo
|
||||
$(call RUN,foo)
|
||||
|
||||
foo: foo.rs $(call NATIVE_STATICLIB,foo)
|
||||
$(RUSTC) $< -lfoo $(EXTRACXXFLAGS)
|
||||
$(RUSTC) $< -lfoo $(EXTRARSCXXFLAGS)
|
||||
|
||||
$(TMPDIR)/libfoo.o: foo.cpp
|
||||
$(call COMPILE_OBJ_CXX,$@,$<)
|
||||
|
@ -1,5 +1,8 @@
|
||||
// Tests that linking to C++ code with global destructors works.
|
||||
|
||||
// For linking libstdc++ on MinGW
|
||||
#![cfg_attr(all(windows, target_env = "gnu"), feature(static_nobundle))]
|
||||
|
||||
extern { fn get() -> u32; }
|
||||
|
||||
fn main() {
|
||||
|
@ -60,7 +60,7 @@ endif
|
||||
|
||||
ifdef IS_MSVC
|
||||
COMPILE_OBJ = $(CC) -c -Fo:`cygpath -w $(1)` $(2)
|
||||
COMPILE_OBJ_CXX = $(CXX) -c -Fo:`cygpath -w $(1)` $(2)
|
||||
COMPILE_OBJ_CXX = $(CXX) -EHs -c -Fo:`cygpath -w $(1)` $(2)
|
||||
NATIVE_STATICLIB_FILE = $(1).lib
|
||||
NATIVE_STATICLIB = $(TMPDIR)/$(call NATIVE_STATICLIB_FILE,$(1))
|
||||
OUT_EXE=-Fe:`cygpath -w $(TMPDIR)/$(call BIN,$(1))` \
|
||||
@ -80,10 +80,29 @@ ifdef IS_MSVC
|
||||
EXTRACFLAGS := ws2_32.lib userenv.lib advapi32.lib
|
||||
else
|
||||
EXTRACFLAGS := -lws2_32 -luserenv
|
||||
EXTRACXXFLAGS := -lstdc++
|
||||
# So this is a bit hacky: we can't use the DLL version of libstdc++ because
|
||||
# it pulls in the DLL version of libgcc, which means that we end up with 2
|
||||
# instances of the DW2 unwinding implementation. This is a problem on
|
||||
# i686-pc-windows-gnu because each module (DLL/EXE) needs to register its
|
||||
# unwind information with the unwinding implementation, and libstdc++'s
|
||||
# __cxa_throw won't see the unwinding info we registered with our statically
|
||||
# linked libgcc.
|
||||
#
|
||||
# Now, simply statically linking libstdc++ would fix this problem, except
|
||||
# that it is compiled with the expectation that pthreads is dynamically
|
||||
# linked as a DLL and will fail to link with a statically linked libpthread.
|
||||
#
|
||||
# So we end up with the following hack: we link use static-nobundle to only
|
||||
# link the parts of libstdc++ that we actually use, which doesn't include
|
||||
# the dependency on the pthreads DLL.
|
||||
EXTRARSCXXFLAGS := -l static-nobundle=stdc++
|
||||
endif
|
||||
else
|
||||
ifeq ($(UNAME),Darwin)
|
||||
EXTRACFLAGS := -lresolv
|
||||
EXTRACXXFLAGS := -lc++
|
||||
EXTRARSCXXFLAGS := -lc++
|
||||
else
|
||||
ifeq ($(UNAME),FreeBSD)
|
||||
EXTRACFLAGS := -lm -lpthread -lgcc_s
|
||||
@ -97,6 +116,7 @@ ifeq ($(UNAME),OpenBSD)
|
||||
else
|
||||
EXTRACFLAGS := -lm -lrt -ldl -lpthread
|
||||
EXTRACXXFLAGS := -lstdc++
|
||||
EXTRARSCXXFLAGS := -lstdc++
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
Loading…
Reference in New Issue
Block a user