From 051c2d14fb1e73866376668088971605d38f0c65 Mon Sep 17 00:00:00 2001 From: Vadim Chugunov Date: Fri, 22 Jul 2016 14:57:54 -0700 Subject: [PATCH] Implement rust_eh_personality in Rust, remove rust_eh_personality_catch. Well, not quite: ARM EHABI platforms still use the old scheme -- for now. --- src/libpanic_unwind/dwarf/eh.rs | 107 +++++++++++++------- src/libpanic_unwind/gcc.rs | 165 ++++++++++++++----------------- src/libpanic_unwind/lib.rs | 1 + src/libpanic_unwind/seh64_gnu.rs | 19 ++-- src/librustc_trans/intrinsic.rs | 14 ++- src/libunwind/libunwind.rs | 8 ++ 6 files changed, 176 insertions(+), 138 deletions(-) diff --git a/src/libpanic_unwind/dwarf/eh.rs b/src/libpanic_unwind/dwarf/eh.rs index 0ad6a74d1013c..32fdf5c204801 100644 --- a/src/libpanic_unwind/dwarf/eh.rs +++ b/src/libpanic_unwind/dwarf/eh.rs @@ -45,16 +45,25 @@ pub const DW_EH_PE_aligned: u8 = 0x50; pub const DW_EH_PE_indirect: u8 = 0x80; #[derive(Copy, Clone)] -pub struct EHContext { +pub struct EHContext<'a> { pub ip: usize, // Current instruction pointer pub func_start: usize, // Address of the current function - pub text_start: usize, // Address of the code section - pub data_start: usize, // Address of the data section + pub get_text_start: &'a Fn() -> usize, // Get address of the code section + pub get_data_start: &'a Fn() -> usize, // Get address of the data section } -pub unsafe fn find_landing_pad(lsda: *const u8, context: &EHContext) -> Option { +pub enum EHAction { + None, + Cleanup(usize), + Catch(usize), + Terminate, +} + +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) -> EHAction { if lsda.is_null() { - return None; + return EHAction::None; } let func_start = context.func_start; @@ -77,32 +86,62 @@ pub unsafe fn find_landing_pad(lsda: *const u8, context: &EHContext) -> Option(); let call_site_table_length = reader.read_uleb128(); let action_table = reader.ptr.offset(call_site_table_length as isize); - // Return addresses point 1 byte past the call instruction, which could - // be in the next IP range. - let ip = context.ip - 1; - - while reader.ptr < action_table { - let cs_start = read_encoded_pointer(&mut reader, context, call_site_encoding); - let cs_len = read_encoded_pointer(&mut reader, context, call_site_encoding); - let cs_lpad = read_encoded_pointer(&mut reader, context, call_site_encoding); - let cs_action = reader.read_uleb128(); - // Callsite table is sorted by cs_start, so if we've passed the ip, we - // may stop searching. - if ip < func_start + cs_start { - break; + let ip = context.ip; + + if !USING_SJLJ_EXCEPTIONS { + while reader.ptr < action_table { + let cs_start = read_encoded_pointer(&mut reader, context, call_site_encoding); + let cs_len = read_encoded_pointer(&mut reader, context, call_site_encoding); + let cs_lpad = read_encoded_pointer(&mut reader, context, call_site_encoding); + let cs_action = reader.read_uleb128(); + // Callsite table is sorted by cs_start, so if we've passed the ip, we + // may stop searching. + if ip < func_start + cs_start { + break; + } + if ip < func_start + cs_start + cs_len { + if cs_lpad == 0 { + return EHAction::None; + } else { + let lpad = lpad_base + cs_lpad; + return interpret_cs_action(cs_action, lpad); + } + } } - if ip < func_start + cs_start + cs_len { - if cs_lpad != 0 { - return Some(lpad_base + cs_lpad); - } else { - return None; + // If ip is not present in the table, call terminate. This is for + // a destructor inside a cleanup, or a library routine the compiler + // was not expecting to throw + EHAction::Terminate + } else { + // SjLj version: + // The "IP" is an index into the call-site table, with two exceptions: + // -1 means 'no-action', and 0 means 'terminate'. + match ip as isize { + -1 => return EHAction::None, + 0 => return EHAction::Terminate, + _ => (), + } + let mut idx = ip; + loop { + let cs_lpad = reader.read_uleb128(); + let cs_action = reader.read_uleb128(); + idx -= 1; + if idx == 0 { + // 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 interpret_cs_action(cs_action, lpad); } } } - // IP range not found: gcc's C++ personality calls terminate() here, - // however the rest of the languages treat this the same as cs_lpad == 0. - // We follow this suit. - None +} + +fn interpret_cs_action(cs_action: u64, lpad: usize) -> EHAction { + if cs_action == 0 { + EHAction::Cleanup(lpad) + } else { + EHAction::Catch(lpad) + } } #[inline] @@ -140,18 +179,16 @@ unsafe fn read_encoded_pointer(reader: &mut DwarfReader, DW_EH_PE_absptr => 0, // relative to address of the encoded value, despite the name DW_EH_PE_pcrel => reader.ptr as usize, - DW_EH_PE_textrel => { - assert!(context.text_start != 0); - context.text_start - } - DW_EH_PE_datarel => { - assert!(context.data_start != 0); - context.data_start - } DW_EH_PE_funcrel => { assert!(context.func_start != 0); context.func_start } + DW_EH_PE_textrel => { + (*context.get_text_start)() + } + DW_EH_PE_datarel => { + (*context.get_data_start)() + } _ => panic!(), }; diff --git a/src/libpanic_unwind/gcc.rs b/src/libpanic_unwind/gcc.rs index 3c46072e17e1a..cdf772ad3b825 100644 --- a/src/libpanic_unwind/gcc.rs +++ b/src/libpanic_unwind/gcc.rs @@ -106,117 +106,96 @@ fn rust_exception_class() -> uw::_Unwind_Exception_Class { 0x4d4f5a_00_52555354 } -// We could implement our personality routine in Rust, however exception -// info decoding is tedious. More importantly, personality routines have to -// handle various platform quirks, which are not fun to maintain. For this -// reason, we attempt to reuse personality routine of the C language: -// __gcc_personality_v0. -// -// Since C does not support exception catching, __gcc_personality_v0 simply -// always returns _URC_CONTINUE_UNWIND in search phase, and always returns -// _URC_INSTALL_CONTEXT (i.e. "invoke cleanup code") in cleanup phase. -// -// This is pretty close to Rust's exception handling approach, except that Rust -// does have a single "catch-all" handler at the bottom of each thread's stack. -// So we have two versions of the personality routine: -// - rust_eh_personality, used by all cleanup landing pads, which never catches, -// so the behavior of __gcc_personality_v0 is perfectly adequate there, and -// - rust_eh_personality_catch, used only by rust_try(), which always catches. -// -// See also: rustc_trans::trans::intrinsic::trans_gnu_try - -#[cfg(all(not(target_arch = "arm"), - not(all(windows, target_arch = "x86_64"))))] +// All targets, except ARM which uses a slightly different ABI (however, iOS goes here as it uses +// SjLj unwinding). Also, 64-bit Windows implementation lives in seh64_gnu.rs +#[cfg(all(any(target_os = "ios", not(target_arch = "arm"))))] pub mod eabi { use unwind as uw; - use libc::c_int; + use libc::{c_int, uintptr_t}; + use dwarf::eh::{EHContext, EHAction, find_eh_action}; - extern "C" { - fn __gcc_personality_v0(version: c_int, - actions: uw::_Unwind_Action, - exception_class: uw::_Unwind_Exception_Class, - ue_header: *mut uw::_Unwind_Exception, - context: *mut uw::_Unwind_Context) - -> uw::_Unwind_Reason_Code; - } + // Register ids were lifted from LLVM's TargetLowering::getExceptionPointerRegister() + // and TargetLowering::getExceptionSelectorRegister() for each architecture, + // then mapped to DWARF register numbers via register definition tables + // (typically RegisterInfo.td, search for "DwarfRegNum"). + // See also http://llvm.org/docs/WritingAnLLVMBackend.html#defining-a-register. - #[lang = "eh_personality"] - #[no_mangle] - extern "C" fn rust_eh_personality(version: c_int, - actions: uw::_Unwind_Action, - exception_class: uw::_Unwind_Exception_Class, - ue_header: *mut uw::_Unwind_Exception, - context: *mut uw::_Unwind_Context) - -> uw::_Unwind_Reason_Code { - unsafe { __gcc_personality_v0(version, actions, exception_class, ue_header, context) } - } + #[cfg(target_arch = "x86")] + const UNWIND_DATA_REG: (i32, i32) = (0, 2); // EAX, EDX - #[lang = "eh_personality_catch"] - #[no_mangle] - pub extern "C" fn rust_eh_personality_catch(version: c_int, - actions: uw::_Unwind_Action, - exception_class: uw::_Unwind_Exception_Class, - ue_header: *mut uw::_Unwind_Exception, - context: *mut uw::_Unwind_Context) - -> uw::_Unwind_Reason_Code { + #[cfg(target_arch = "x86_64")] + const UNWIND_DATA_REG: (i32, i32) = (0, 1); // RAX, RDX - if (actions as c_int & uw::_UA_SEARCH_PHASE as c_int) != 0 { - // search phase - uw::_URC_HANDLER_FOUND // catch! - } else { - // cleanup phase - unsafe { __gcc_personality_v0(version, actions, exception_class, ue_header, context) } - } - } -} - -// iOS on armv7 is using SjLj exceptions and therefore requires to use -// a specialized personality routine: __gcc_personality_sj0 + #[cfg(any(target_arch = "arm", target_arch = "aarch64"))] + const UNWIND_DATA_REG: (i32, i32) = (0, 1); // R0, R1 / X0, X1 -#[cfg(all(target_os = "ios", target_arch = "arm"))] -pub mod eabi { - use unwind as uw; - use libc::c_int; + #[cfg(any(target_arch = "mips", target_arch = "mipsel"))] + const UNWIND_DATA_REG: (i32, i32) = (4, 5); // A0, A1 - extern "C" { - fn __gcc_personality_sj0(version: c_int, - actions: uw::_Unwind_Action, - exception_class: uw::_Unwind_Exception_Class, - ue_header: *mut uw::_Unwind_Exception, - context: *mut uw::_Unwind_Context) - -> uw::_Unwind_Reason_Code; - } + #[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))] + const UNWIND_DATA_REG: (i32, i32) = (3, 4); // R3, R4 / X3, X4 + // Based on GCC's C and C++ personality routines. For reference, see: + // 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 #[lang = "eh_personality"] #[no_mangle] - pub extern "C" fn rust_eh_personality(version: c_int, - actions: uw::_Unwind_Action, - exception_class: uw::_Unwind_Exception_Class, - ue_header: *mut uw::_Unwind_Exception, - context: *mut uw::_Unwind_Context) - -> uw::_Unwind_Reason_Code { - unsafe { __gcc_personality_sj0(version, actions, exception_class, ue_header, context) } + #[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 lsda = uw::_Unwind_GetLanguageSpecificData(context) as *const u8; + let mut ip_before_instr: c_int = 0; + let ip = uw::_Unwind_GetIPInfo(context, &mut ip_before_instr); + let eh_context = EHContext { + // The return address points 1 byte past the call instruction, + // which could be in the next IP range in LSDA range table. + ip: if ip_before_instr != 0 { ip } else { ip - 1 }, + func_start: uw::_Unwind_GetRegionStart(context), + get_text_start: &|| uw::_Unwind_GetTextRelBase(context), + get_data_start: &|| uw::_Unwind_GetDataRelBase(context), + }; + let eh_action = find_eh_action(lsda, &eh_context); + + if actions as i32 & uw::_UA_SEARCH_PHASE as i32 != 0 { + match eh_action { + EHAction::None | EHAction::Cleanup(_) => return uw::_URC_CONTINUE_UNWIND, + EHAction::Catch(_) => return uw::_URC_HANDLER_FOUND, + EHAction::Terminate => return uw::_URC_FATAL_PHASE1_ERROR, + } + } else { + match eh_action { + EHAction::None => return 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); + return uw::_URC_INSTALL_CONTEXT; + } + EHAction::Terminate => return uw::_URC_FATAL_PHASE2_ERROR, + } + } } + #[cfg(stage0)] #[lang = "eh_personality_catch"] #[no_mangle] - pub extern "C" fn rust_eh_personality_catch(version: c_int, - actions: uw::_Unwind_Action, - exception_class: uw::_Unwind_Exception_Class, - ue_header: *mut uw::_Unwind_Exception, - context: *mut uw::_Unwind_Context) - -> uw::_Unwind_Reason_Code { - if (actions as c_int & uw::_UA_SEARCH_PHASE as c_int) != 0 { - // search phase - uw::_URC_HANDLER_FOUND // catch! - } else { - // cleanup phase - unsafe { __gcc_personality_sj0(version, actions, exception_class, ue_header, context) } - } + pub unsafe extern "C" fn rust_eh_personality_catch(version: c_int, + actions: uw::_Unwind_Action, + exception_class: uw::_Unwind_Exception_Class, + ue_header: *mut uw::_Unwind_Exception, + context: *mut uw::_Unwind_Context) + -> uw::_Unwind_Reason_Code { + rust_eh_personality(version, actions, exception_class, ue_header, context) } } - // ARM EHABI uses a slightly different personality routine signature, // but otherwise works the same. #[cfg(all(target_arch = "arm", not(target_os = "ios")))] diff --git a/src/libpanic_unwind/lib.rs b/src/libpanic_unwind/lib.rs index b765ee6f81cf9..11dd9befe0a82 100644 --- a/src/libpanic_unwind/lib.rs +++ b/src/libpanic_unwind/lib.rs @@ -101,6 +101,7 @@ pub unsafe extern "C" fn __rust_maybe_catch_panic(f: fn(*mut u8), // Entry point for raising an exception, just delegates to the platform-specific // implementation. #[no_mangle] +#[unwind] pub unsafe extern "C" fn __rust_start_panic(data: usize, vtable: usize) -> u32 { imp::panic(mem::transmute(raw::TraitObject { data: data as *mut (), diff --git a/src/libpanic_unwind/seh64_gnu.rs b/src/libpanic_unwind/seh64_gnu.rs index 56801e8cb6bcf..7dc428871b387 100644 --- a/src/libpanic_unwind/seh64_gnu.rs +++ b/src/libpanic_unwind/seh64_gnu.rs @@ -19,7 +19,7 @@ use alloc::boxed::Box; use core::any::Any; use core::intrinsics; use core::ptr; -use dwarf::eh; +use dwarf::eh::{EHContext, EHAction, find_eh_action}; use windows as c; // Define our exception codes: @@ -81,6 +81,7 @@ pub unsafe fn cleanup(ptr: *mut u8) -> Box { // This is considered acceptable, because the behavior of throwing exceptions // through a C ABI boundary is undefined. +#[cfg(stage0)] #[lang = "eh_personality_catch"] #[cfg(not(test))] unsafe extern "C" fn rust_eh_personality_catch(exceptionRecord: *mut c::EXCEPTION_RECORD, @@ -132,11 +133,17 @@ unsafe extern "C" fn rust_eh_unwind_resume(panic_ctx: c::LPVOID) -> ! { } unsafe fn find_landing_pad(dc: &c::DISPATCHER_CONTEXT) -> Option { - let eh_ctx = eh::EHContext { - ip: dc.ControlPc as 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, - text_start: dc.ImageBase as usize, - data_start: 0, + get_text_start: &|| dc.ImageBase as usize, + get_data_start: &|| unimplemented!(), }; - eh::find_landing_pad(dc.HandlerData, &eh_ctx) + match find_eh_action(dc.HandlerData, &eh_ctx) { + EHAction::None => None, + EHAction::Cleanup(lpad) | EHAction::Catch(lpad) => Some(lpad), + EHAction::Terminate => intrinsics::abort(), + } } diff --git a/src/librustc_trans/intrinsic.rs b/src/librustc_trans/intrinsic.rs index a721361fce0e3..f033b278fe7f0 100644 --- a/src/librustc_trans/intrinsic.rs +++ b/src/librustc_trans/intrinsic.rs @@ -1193,11 +1193,17 @@ fn trans_gnu_try<'blk, 'tcx>(bcx: Block<'blk, 'tcx>, // managed by the standard library. attributes::emit_uwtable(bcx.fcx.llfn, true); - let catch_pers = match tcx.lang_items.eh_personality_catch() { - Some(did) => { - Callee::def(ccx, did, tcx.mk_substs(Substs::empty())).reify(ccx).val + let target = &bcx.sess().target.target; + let catch_pers = if target.arch == "arm" && target.target_os != "ios" { + // Only ARM still uses a separate catch personality (for now) + match tcx.lang_items.eh_personality_catch() { + Some(did) => { + Callee::def(ccx, did, tcx.mk_substs(Substs::empty())).reify(ccx).val + } + None => bug!("eh_personality_catch not defined"), } - None => bug!("eh_personality_catch not defined"), + } else { + bcx.fcx.eh_personality() }; let then = bcx.fcx.new_temp_block("then"); diff --git a/src/libunwind/libunwind.rs b/src/libunwind/libunwind.rs index aadfe202afe79..d9b6c9ed74dd2 100644 --- a/src/libunwind/libunwind.rs +++ b/src/libunwind/libunwind.rs @@ -58,6 +58,7 @@ pub enum _Unwind_Reason_Code { pub type _Unwind_Exception_Class = u64; pub type _Unwind_Word = libc::uintptr_t; +pub type _Unwind_Ptr = libc::uintptr_t; pub type _Unwind_Trace_Fn = extern "C" fn(ctx: *mut _Unwind_Context, arg: *mut libc::c_void) -> _Unwind_Reason_Code; @@ -156,6 +157,13 @@ extern "C" { ip_before_insn: *mut libc::c_int) -> libc::uintptr_t; + pub fn _Unwind_GetLanguageSpecificData(ctx: *mut _Unwind_Context) -> _Unwind_Ptr; + pub fn _Unwind_GetRegionStart(ctx: *mut _Unwind_Context) -> _Unwind_Ptr; + pub fn _Unwind_GetTextRelBase(ctx: *mut _Unwind_Context) -> _Unwind_Ptr; + pub fn _Unwind_GetDataRelBase(ctx: *mut _Unwind_Context) -> _Unwind_Ptr; + pub fn _Unwind_SetGR(ctx: *mut _Unwind_Context, reg_index: libc::c_int, value: _Unwind_Ptr); + pub fn _Unwind_SetIP(ctx: *mut _Unwind_Context, value: _Unwind_Ptr); + #[cfg(all(not(target_os = "android"), not(all(target_os = "linux", target_arch = "arm"))))] pub fn _Unwind_FindEnclosingFunction(pc: *mut libc::c_void) -> *mut libc::c_void;