//! Parsing of GCC-style Language-Specific Data Area (LSDA) //! For details see: //! http://refspecs.linuxfoundation.org/LSB_3.0.0/LSB-PDA/LSB-PDA/ehframechpt.html //! http://mentorembedded.github.io/cxx-abi/exceptions.pdf //! http://www.airs.com/blog/archives/460 //! http://www.airs.com/blog/archives/464 //! //! A reference implementation may be found in the GCC source tree //! (`/libgcc/unwind-c.c` as of this writing). #![allow(non_upper_case_globals)] #![allow(unused)] use crate::DwarfReader; use core::mem; use cslice::CSlice; pub const DW_EH_PE_omit: u8 = 0xFF; pub const DW_EH_PE_absptr: u8 = 0x00; pub const DW_EH_PE_uleb128: u8 = 0x01; pub const DW_EH_PE_udata2: u8 = 0x02; pub const DW_EH_PE_udata4: u8 = 0x03; pub const DW_EH_PE_udata8: u8 = 0x04; pub const DW_EH_PE_sleb128: u8 = 0x09; pub const DW_EH_PE_sdata2: u8 = 0x0A; pub const DW_EH_PE_sdata4: u8 = 0x0B; pub const DW_EH_PE_sdata8: u8 = 0x0C; pub const DW_EH_PE_pcrel: u8 = 0x10; pub const DW_EH_PE_textrel: u8 = 0x20; pub const DW_EH_PE_datarel: u8 = 0x30; pub const DW_EH_PE_funcrel: u8 = 0x40; pub const DW_EH_PE_aligned: u8 = 0x50; pub const DW_EH_PE_indirect: u8 = 0x80; #[derive(Copy, Clone)] pub struct EHContext<'a> { pub ip: usize, // Current instruction pointer pub func_start: usize, // Address of the current function pub get_text_start: &'a dyn Fn() -> usize, // Get address of the code section pub get_data_start: &'a dyn Fn() -> usize, // Get address of the data section } pub enum EHAction { None, Cleanup(usize), Catch(usize), Terminate, } pub const USING_SJLJ_EXCEPTIONS: bool = cfg!(all(target_os = "ios", target_arch = "arm")); fn size_of_encoded_value(encoding: u8) -> usize { if encoding == DW_EH_PE_omit { 0 } else { let encoding = encoding & 0x07; match encoding { DW_EH_PE_absptr => core::mem::size_of::<*const ()>(), DW_EH_PE_udata2 => 2, DW_EH_PE_udata4 => 4, DW_EH_PE_udata8 => 8, _ => unreachable!(), } } } unsafe fn get_ttype_entry( offset: usize, encoding: u8, ttype_base: usize, ttype: *const u8, ) -> Result<*const u8, ()> { let i = (offset * size_of_encoded_value(encoding)) as isize; read_encoded_pointer_with_base( &mut DwarfReader::new(ttype.offset(-i)), // the DW_EH_PE_pcrel is a hack. // It seems that the default encoding is absolute, but we have to take reallocation into // account. Unsure if we can fix this in the compiler setting or if this would be affected // by updating the compiler encoding | DW_EH_PE_pcrel, ttype_base, ) .map(|v| v as *const u8) } pub unsafe fn find_eh_action( lsda: *const u8, context: &EHContext<'_>, foreign_exception: bool, id: u32, ) -> Result { if lsda.is_null() { return Ok(EHAction::None); } let func_start = context.func_start; let mut reader = DwarfReader::new(lsda); let start_encoding = reader.read::(); // base address for landing pad offsets let lpad_base = if start_encoding != DW_EH_PE_omit { read_encoded_pointer(&mut reader, context, start_encoding)? } else { func_start }; let ttype_encoding = reader.read::(); // we do care about the type table let ttype_offset = if ttype_encoding != DW_EH_PE_omit { reader.read_uleb128() } else { 0 }; // for rust functions, it seems that there is no type table, so I just put whatever value here. // we should not return an error, otherwise we would abort unwinding and cannot unwind through // rust functions let ttype_base = get_base(ttype_encoding, context).unwrap_or(1); let ttype_table = reader.ptr.offset(ttype_offset as isize); let call_site_encoding = reader.read::(); let call_site_table_length = reader.read_uleb128(); let action_table = reader.ptr.offset(call_site_table_length as isize); 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 { // https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/libsupc%2B%2B/eh_personality.cc#L528 let lpad = lpad_base + cs_lpad; if cs_lpad == 0 { // no cleanups/handler return Ok(EHAction::None); } else if cs_action == 0 { return Ok(EHAction::Cleanup(lpad)); } else if foreign_exception { return Ok(EHAction::None); } else { let mut saw_cleanup = false; let mut action_record = action_table.offset(cs_action as isize - 1); loop { let mut reader = DwarfReader::new(action_record); let ar_filter = reader.read_sleb128(); action_record = reader.ptr; let ar_disp = reader.read_sleb128(); if ar_filter == 0 { saw_cleanup = true; } else if ar_filter > 0 { let catch_type = get_ttype_entry( ar_filter as usize, ttype_encoding, ttype_base, ttype_table, )?; let clause_ptr = *(catch_type as *const *const u32); if clause_ptr.is_null() { return Ok(EHAction::Catch(lpad)); } if *clause_ptr == id { return Ok(EHAction::Catch(lpad)); } } else if ar_filter < 0 { // FIXME: how to handle this? break; } if ar_disp == 0 { break; } action_record = action_record.offset((ar_disp as usize) as isize); } if saw_cleanup { return Ok(EHAction::Cleanup(lpad)); } else { return Ok(EHAction::None); } } } } // Ip is not present in the table. This should not happen... but it does: issue #35011. // So rather than returning EHAction::Terminate, we do this. Ok(EHAction::None) } else { // SjLj version: (not yet modified) // 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 Ok(EHAction::None), 0 => return Ok(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 Ok(interpret_cs_action(cs_action, lpad, foreign_exception)); } } } } fn interpret_cs_action(cs_action: u64, lpad: usize, foreign_exception: bool) -> EHAction { if cs_action == 0 { // If cs_action is 0 then this is a cleanup (Drop::drop). We run these // for both Rust panics and foreign exceptions. EHAction::Cleanup(lpad) } else if foreign_exception { // catch_unwind should not catch foreign exceptions, only Rust panics. // Instead just continue unwinding. EHAction::None } else { // Stop unwinding Rust panics at catch_unwind. EHAction::Catch(lpad) } } #[inline] fn round_up(unrounded: usize, align: usize) -> Result { if align.is_power_of_two() { Ok((unrounded + align - 1) & !(align - 1)) } else { Err(()) } } fn get_base(encoding: u8, context: &EHContext<'_>) -> Result { match encoding & 0x70 { DW_EH_PE_absptr | DW_EH_PE_pcrel | DW_EH_PE_aligned => Ok(0), DW_EH_PE_textrel => Ok((*context.get_text_start)()), DW_EH_PE_datarel => Ok((*context.get_data_start)()), DW_EH_PE_funcrel if context.func_start != 0 => Ok(context.func_start), _ => return Err(()), } } unsafe fn read_encoded_pointer( reader: &mut DwarfReader, context: &EHContext<'_>, encoding: u8, ) -> Result { read_encoded_pointer_with_base(reader, encoding, get_base(encoding, context)?) } unsafe fn read_encoded_pointer_with_base( reader: &mut DwarfReader, encoding: u8, base: usize, ) -> Result { if encoding == DW_EH_PE_omit { return Err(()); } let original_ptr = reader.ptr; // DW_EH_PE_aligned implies it's an absolute pointer value if encoding == DW_EH_PE_aligned { reader.ptr = round_up(reader.ptr as usize, mem::size_of::())? as *const u8; return Ok(reader.read::()); } let mut result = match encoding & 0x0F { DW_EH_PE_absptr => reader.read::(), DW_EH_PE_uleb128 => reader.read_uleb128() as usize, DW_EH_PE_udata2 => reader.read::() as usize, DW_EH_PE_udata4 => reader.read::() as usize, DW_EH_PE_udata8 => reader.read::() as usize, DW_EH_PE_sleb128 => reader.read_sleb128() as usize, DW_EH_PE_sdata2 => reader.read::() as usize, DW_EH_PE_sdata4 => reader.read::() as usize, DW_EH_PE_sdata8 => reader.read::() as usize, _ => return Err(()), }; result += if (encoding & 0x70) == DW_EH_PE_pcrel { original_ptr as usize } else { base }; if encoding & DW_EH_PE_indirect != 0 { result = *(result as *const usize); } Ok(result) }