Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement rust_eh_personality in Rust, remove rust_eh_personality_catch. #34832

Merged
merged 1 commit into from
Jul 23, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 72 additions & 35 deletions src/libpanic_unwind/dwarf/eh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<usize> {
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;
Expand All @@ -77,32 +86,62 @@ pub unsafe fn find_landing_pad(lsda: *const u8, context: &EHContext) -> Option<u
let call_site_encoding = reader.read::<u8>();
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]
Expand Down Expand Up @@ -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!(),
};

Expand Down
165 changes: 72 additions & 93 deletions src/libpanic_unwind/gcc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 <arch>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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should RDX here be 2? I'm a little confused by LLVM's definitions here I think, I see:

def EAX : X86Reg<"eax", 0, [AX]>, DwarfRegNum<[-2, 0, 0]>;
def EDX : X86Reg<"edx", 2, [DX]>, DwarfRegNum<[-2, 2, 2]>;
def RAX : X86Reg<"rax", 0, [EAX]>, DwarfRegNum<[0, -2, -2]>;
def RDX : X86Reg<"rdx", 2, [EDX]>, DwarfRegNum<[1, -2, -2]>;

Which one of these numbers translates to the indexes here? The 0/2 for x86 match up with above, but the 0/1 I can't quite find for x86_64...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RDX is defined here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was just a little confused, the index for x86 seemed to come from the left hand side or the second/third number in DwarfRegNum but for x86_64 it came from the first number in DwarfRegNum.

I wonder if this may be the cause of the test failures?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yah, the docs are not very clear, but it seems that the first value is for 64 bit mode and the second - for 32. In any case, -2 means "invalid", so there really isn't any choice between the numbers.


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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh holy cow! I was figuring we'd just detect a little but but still piggy back on the system personality routines for most of the heavy lifting, but hey if we've got this looks good to me!

Was all of this based off a gcc or some other implementation? If so, could this have some more comments and/or links to where all this logic is coming from?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

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")))]
Expand Down
1 change: 1 addition & 0 deletions src/libpanic_unwind/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 (),
Expand Down
19 changes: 13 additions & 6 deletions src/libpanic_unwind/seh64_gnu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -81,6 +81,7 @@ pub unsafe fn cleanup(ptr: *mut u8) -> Box<Any + Send> {
// 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,
Expand Down Expand Up @@ -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<usize> {
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(),
}
}
Loading