diff --git a/Cargo.toml b/Cargo.toml index 972af76..6709f7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ fde-phdr = ["libc"] fde-registry = ["alloc"] dwarf-expr = [] hide-trace = [] +personality = [] personality-dummy = [] system-alloc = [] default = ["dwarf-expr", "hide-trace", "fde-phdr", "fde-registry"] diff --git a/src/abi.rs b/src/abi.rs index 9d39ce0..00ad302 100644 --- a/src/abi.rs +++ b/src/abi.rs @@ -75,6 +75,7 @@ pub struct UnwindException { pub exception_cleanup: Option, private_1: Option, private_2: usize, + private_unused: [usize; Arch::UNWIND_PRIVATE_DATA_SIZE - 2], } pub type UnwindTraceFn = @@ -128,9 +129,7 @@ pub extern "C" fn _Unwind_SetIP(unwind_ctx: &mut UnwindContext<'_>, value: usize } #[no_mangle] -pub extern "C" fn _Unwind_GetLanguageSpecificData( - unwind_ctx: &UnwindContext<'_>, -) -> *mut c_void { +pub extern "C" fn _Unwind_GetLanguageSpecificData(unwind_ctx: &UnwindContext<'_>) -> *mut c_void { unwind_ctx .frame .map(|f| f.lsda() as *mut c_void) diff --git a/src/arch/x86_64.rs b/src/arch/x86_64.rs index 9711cb3..58dd9c4 100644 --- a/src/arch/x86_64.rs +++ b/src/arch/x86_64.rs @@ -13,9 +13,13 @@ pub struct Context { pub struct Arch; +#[allow(unused)] impl Arch { pub const SP: Register = X86_64::RSP; pub const RA: Register = X86_64::RA; + + pub const UNWIND_DATA_REG: (Register, Register) = (X86_64::RAX, X86_64::RDX); + pub const UNWIND_PRIVATE_DATA_SIZE: usize = 6; } impl fmt::Debug for Context { diff --git a/src/lib.rs b/src/lib.rs index 4a19ddd..e8b774d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #![feature(c_unwind)] #![feature(naked_functions)] #![feature(asm)] +#![cfg_attr(any(feature = "personality", feature = "personality-dummy"), feature(lang_items))] #![warn(rust_2018_idioms)] #![warn(unsafe_op_in_unsafe_fn)] #![no_std] @@ -14,6 +15,8 @@ mod find_fde; mod frame; mod util; +#[cfg(feature = "personality")] +mod personality; #[cfg(feature = "personality-dummy")] mod personality_dummy; diff --git a/src/personality.rs b/src/personality.rs new file mode 100644 index 0000000..2dda30a --- /dev/null +++ b/src/personality.rs @@ -0,0 +1,181 @@ +// References: +// https://github.com/rust-lang/rust/blob/c4be230b4a30eb74e3a3908455731ebc2f731d3d/library/panic_unwind/src/gcc.rs +// https://github.com/rust-lang/rust/blob/c4be230b4a30eb74e3a3908455731ebc2f731d3d/library/panic_unwind/src/dwarf/eh.rs +// https://docs.rs/gimli/0.25.0/src/gimli/read/cfi.rs.html + +use core::mem; +use gimli::{constants, NativeEndian}; +use gimli::{EndianSlice, Error, Pointer, Reader}; + +use crate::abi::*; +use crate::arch::*; +use crate::util::*; + +#[derive(Debug)] +enum EHAction { + None, + Cleanup(usize), + Catch(usize), +} + +fn parse_pointer_encoding(input: &mut StaticSlice) -> gimli::Result { + let eh_pe = input.read_u8()?; + let eh_pe = constants::DwEhPe(eh_pe); + + if eh_pe.is_valid_encoding() { + Ok(eh_pe) + } else { + Err(gimli::Error::UnknownPointerEncoding) + } +} + +fn parse_encoded_pointer( + encoding: constants::DwEhPe, + unwind_ctx: &UnwindContext<'_>, + input: &mut StaticSlice, +) -> gimli::Result { + if encoding == constants::DW_EH_PE_omit { + return Err(Error::CannotParseOmitPointerEncoding); + } + + let base = match encoding.application() { + constants::DW_EH_PE_absptr => 0, + constants::DW_EH_PE_pcrel => input.slice().as_ptr() as u64, + constants::DW_EH_PE_textrel => _Unwind_GetTextRelBase(unwind_ctx) as u64, + constants::DW_EH_PE_datarel => _Unwind_GetDataRelBase(unwind_ctx) as u64, + constants::DW_EH_PE_funcrel => _Unwind_GetRegionStart(unwind_ctx) as u64, + constants::DW_EH_PE_aligned => return Err(Error::UnsupportedPointerEncoding), + _ => unreachable!(), + }; + + let offset = match encoding.format() { + constants::DW_EH_PE_absptr => input.read_address(mem::size_of::() as _), + constants::DW_EH_PE_uleb128 => input.read_uleb128(), + constants::DW_EH_PE_udata2 => input.read_u16().map(u64::from), + constants::DW_EH_PE_udata4 => input.read_u32().map(u64::from), + constants::DW_EH_PE_udata8 => input.read_u64(), + constants::DW_EH_PE_sleb128 => input.read_sleb128().map(|a| a as u64), + constants::DW_EH_PE_sdata2 => input.read_i16().map(|a| a as u64), + constants::DW_EH_PE_sdata4 => input.read_i32().map(|a| a as u64), + constants::DW_EH_PE_sdata8 => input.read_i64().map(|a| a as u64), + _ => unreachable!(), + }?; + + let address = base.wrapping_add(offset); + Ok(if encoding.is_indirect() { + Pointer::Indirect(address) + } else { + Pointer::Direct(address) + }) +} + +fn find_eh_action( + reader: &mut StaticSlice, + unwind_ctx: &UnwindContext<'_>, +) -> gimli::Result { + let func_start = _Unwind_GetRegionStart(unwind_ctx); + let mut ip_before_instr = 0; + let ip = _Unwind_GetIPInfo(unwind_ctx, &mut ip_before_instr); + let ip = if ip_before_instr != 0 { ip } else { ip - 1 }; + + let start_encoding = parse_pointer_encoding(reader)?; + let lpad_base = if !start_encoding.is_absent() { + unsafe { deref_pointer(parse_encoded_pointer(start_encoding, unwind_ctx, reader)?) } + } else { + func_start + }; + + let ttype_encoding = parse_pointer_encoding(reader)?; + if !ttype_encoding.is_absent() { + reader.read_uleb128()?; + } + + let call_site_encoding = parse_pointer_encoding(reader)?; + let call_site_table_length = reader.read_uleb128()?; + reader.truncate(call_site_table_length as _)?; + + while !reader.is_empty() { + let cs_start = unsafe { + deref_pointer(parse_encoded_pointer( + call_site_encoding, + unwind_ctx, + reader, + )?) + }; + let cs_len = unsafe { + deref_pointer(parse_encoded_pointer( + call_site_encoding, + unwind_ctx, + reader, + )?) + }; + let cs_lpad = unsafe { + deref_pointer(parse_encoded_pointer( + call_site_encoding, + unwind_ctx, + reader, + )?) + }; + let cs_action = reader.read_uleb128()?; + if ip < func_start + cs_start { + break; + } + if ip < func_start + cs_start + cs_len { + if cs_lpad == 0 { + return Ok(EHAction::None); + } else { + let lpad = lpad_base + cs_lpad; + return Ok(match cs_action { + 0 => EHAction::Cleanup(lpad), + _ => EHAction::Catch(lpad), + }); + } + } + } + Ok(EHAction::None) +} + +#[lang = "eh_personality"] +fn rust_eh_personality( + version: c_int, + actions: UnwindAction, + _exception_class: u64, + exception: &mut UnwindException, + unwind_ctx: &mut UnwindContext<'_>, +) -> UnwindReasonCode { + if version != 1 { + return UnwindReasonCode::FATAL_PHASE1_ERROR; + } + + let lsda = _Unwind_GetLanguageSpecificData(unwind_ctx); + if lsda.is_null() { + return UnwindReasonCode::CONTINUE_UNWIND; + } + + let mut lsda = EndianSlice::new(unsafe { get_unlimited_slice(lsda as _) }, NativeEndian); + let eh_action = match find_eh_action(&mut lsda, unwind_ctx) { + Ok(v) => v, + Err(_) => return UnwindReasonCode::FATAL_PHASE1_ERROR, + }; + + if actions.contains(UnwindAction::SEARCH_PHASE) { + match eh_action { + EHAction::None | EHAction::Cleanup(_) => UnwindReasonCode::CONTINUE_UNWIND, + EHAction::Catch(_) => UnwindReasonCode::HANDLER_FOUND, + } + } else { + match eh_action { + EHAction::None => UnwindReasonCode::CONTINUE_UNWIND, + EHAction::Cleanup(lpad) | EHAction::Catch(lpad) => { + _Unwind_SetGR( + unwind_ctx, + Arch::UNWIND_DATA_REG.0 .0 as _, + exception as *mut _ as usize, + ); + _Unwind_SetGR(unwind_ctx, Arch::UNWIND_DATA_REG.1 .0 as _, 0); + _Unwind_SetIP(unwind_ctx, lpad); + UnwindReasonCode::INSTALL_CONTEXT + } + } + } +}