blob: ee1129809b81f870a25c5174223fb79910bed155 [file] [log] [blame] [edit]
// 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<constants::DwEhPe> {
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(eh_pe))
}
}
fn parse_encoded_pointer(
encoding: constants::DwEhPe,
unwind_ctx: &UnwindContext<'_>,
input: &mut StaticSlice,
) -> gimli::Result<Pointer> {
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::<usize>() 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<EHAction> {
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"]
unsafe 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 usize,
);
_Unwind_SetGR(unwind_ctx, Arch::UNWIND_DATA_REG.1 .0 as _, 0);
_Unwind_SetIP(unwind_ctx, lpad);
UnwindReasonCode::INSTALL_CONTEXT
}
}
}
}