// 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 } } } }