| 1 | //! Parsing of GCC-style Language-Specific Data Area (LSDA) |
| 2 | //! For details see: |
| 3 | //! * <https://refspecs.linuxfoundation.org/LSB_3.0.0/LSB-PDA/LSB-PDA/ehframechpt.html> |
| 4 | //! * <https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/dwarfext.html> |
| 5 | //! * <https://itanium-cxx-abi.github.io/cxx-abi/exceptions.pdf> |
| 6 | //! * <https://www.airs.com/blog/archives/460> |
| 7 | //! * <https://www.airs.com/blog/archives/464> |
| 8 | //! |
| 9 | //! A reference implementation may be found in the GCC source tree |
| 10 | //! (`<root>/libgcc/unwind-c.c` as of this writing). |
| 11 | |
| 12 | #![allow (non_upper_case_globals)] |
| 13 | #![allow (unused)] |
| 14 | |
| 15 | use core::ptr; |
| 16 | |
| 17 | use super::DwarfReader; |
| 18 | |
| 19 | pub const DW_EH_PE_omit: u8 = 0xFF; |
| 20 | pub const DW_EH_PE_absptr: u8 = 0x00; |
| 21 | |
| 22 | pub const DW_EH_PE_uleb128: u8 = 0x01; |
| 23 | pub const DW_EH_PE_udata2: u8 = 0x02; |
| 24 | pub const DW_EH_PE_udata4: u8 = 0x03; |
| 25 | pub const DW_EH_PE_udata8: u8 = 0x04; |
| 26 | pub const DW_EH_PE_sleb128: u8 = 0x09; |
| 27 | pub const DW_EH_PE_sdata2: u8 = 0x0A; |
| 28 | pub const DW_EH_PE_sdata4: u8 = 0x0B; |
| 29 | pub const DW_EH_PE_sdata8: u8 = 0x0C; |
| 30 | |
| 31 | pub const DW_EH_PE_pcrel: u8 = 0x10; |
| 32 | pub const DW_EH_PE_textrel: u8 = 0x20; |
| 33 | pub const DW_EH_PE_datarel: u8 = 0x30; |
| 34 | pub const DW_EH_PE_funcrel: u8 = 0x40; |
| 35 | pub const DW_EH_PE_aligned: u8 = 0x50; |
| 36 | |
| 37 | pub const DW_EH_PE_indirect: u8 = 0x80; |
| 38 | |
| 39 | #[derive (Copy, Clone)] |
| 40 | pub struct EHContext<'a> { |
| 41 | pub ip: *const u8, // Current instruction pointer |
| 42 | pub func_start: *const u8, // Pointer to the current function |
| 43 | pub get_text_start: &'a dyn Fn() -> *const u8, // Get pointer to the code section |
| 44 | pub get_data_start: &'a dyn Fn() -> *const u8, // Get pointer to the data section |
| 45 | } |
| 46 | |
| 47 | /// Landing pad. |
| 48 | type LPad = *const u8; |
| 49 | pub enum EHAction { |
| 50 | None, |
| 51 | Cleanup(LPad), |
| 52 | Catch(LPad), |
| 53 | Filter(LPad), |
| 54 | Terminate, |
| 55 | } |
| 56 | |
| 57 | /// 32-bit ARM Darwin platforms uses SjLj exceptions. |
| 58 | /// |
| 59 | /// The exception is watchOS armv7k (specifically that subarchitecture), which |
| 60 | /// instead uses DWARF Call Frame Information (CFI) unwinding. |
| 61 | /// |
| 62 | /// <https://github.com/llvm/llvm-project/blob/llvmorg-18.1.4/clang/lib/Driver/ToolChains/Darwin.cpp#L3107-L3119> |
| 63 | pub const USING_SJLJ_EXCEPTIONS: bool = |
| 64 | cfg!(all(target_vendor = "apple" , not(target_os = "watchos" ), target_arch = "arm" )); |
| 65 | |
| 66 | pub unsafe fn find_eh_action(lsda: *const u8, context: &EHContext<'_>) -> Result<EHAction, ()> { |
| 67 | if lsda.is_null() { |
| 68 | return Ok(EHAction::None); |
| 69 | } |
| 70 | |
| 71 | let func_start = context.func_start; |
| 72 | let mut reader = DwarfReader::new(lsda); |
| 73 | let lpad_base = unsafe { |
| 74 | let start_encoding = reader.read::<u8>(); |
| 75 | // base address for landing pad offsets |
| 76 | if start_encoding != DW_EH_PE_omit { |
| 77 | read_encoded_pointer(&mut reader, context, start_encoding)? |
| 78 | } else { |
| 79 | func_start |
| 80 | } |
| 81 | }; |
| 82 | let call_site_encoding = unsafe { |
| 83 | let ttype_encoding = reader.read::<u8>(); |
| 84 | if ttype_encoding != DW_EH_PE_omit { |
| 85 | // Rust doesn't analyze exception types, so we don't care about the type table |
| 86 | reader.read_uleb128(); |
| 87 | } |
| 88 | |
| 89 | reader.read::<u8>() |
| 90 | }; |
| 91 | let action_table = unsafe { |
| 92 | let call_site_table_length = reader.read_uleb128(); |
| 93 | reader.ptr.add(call_site_table_length as usize) |
| 94 | }; |
| 95 | let ip = context.ip; |
| 96 | |
| 97 | if !USING_SJLJ_EXCEPTIONS { |
| 98 | // read the callsite table |
| 99 | while reader.ptr < action_table { |
| 100 | unsafe { |
| 101 | // these are offsets rather than pointers; |
| 102 | let cs_start = read_encoded_offset(&mut reader, call_site_encoding)?; |
| 103 | let cs_len = read_encoded_offset(&mut reader, call_site_encoding)?; |
| 104 | let cs_lpad = read_encoded_offset(&mut reader, call_site_encoding)?; |
| 105 | let cs_action_entry = reader.read_uleb128(); |
| 106 | // Callsite table is sorted by cs_start, so if we've passed the ip, we |
| 107 | // may stop searching. |
| 108 | if ip < func_start.wrapping_add(cs_start) { |
| 109 | break; |
| 110 | } |
| 111 | if ip < func_start.wrapping_add(cs_start + cs_len) { |
| 112 | if cs_lpad == 0 { |
| 113 | return Ok(EHAction::None); |
| 114 | } else { |
| 115 | let lpad = lpad_base.wrapping_add(cs_lpad); |
| 116 | return Ok(interpret_cs_action(action_table, cs_action_entry, lpad)); |
| 117 | } |
| 118 | } |
| 119 | } |
| 120 | } |
| 121 | // Ip is not present in the table. This indicates a nounwind call. |
| 122 | Ok(EHAction::Terminate) |
| 123 | } else { |
| 124 | // SjLj version: |
| 125 | // The "IP" is an index into the call-site table, with two exceptions: |
| 126 | // -1 means 'no-action', and 0 means 'terminate'. |
| 127 | match ip.addr() as isize { |
| 128 | -1 => return Ok(EHAction::None), |
| 129 | 0 => return Ok(EHAction::Terminate), |
| 130 | _ => (), |
| 131 | } |
| 132 | let mut idx = ip.addr(); |
| 133 | loop { |
| 134 | let cs_lpad = unsafe { reader.read_uleb128() }; |
| 135 | let cs_action_entry = unsafe { reader.read_uleb128() }; |
| 136 | idx -= 1; |
| 137 | if idx == 0 { |
| 138 | // Can never have null landing pad for sjlj -- that would have |
| 139 | // been indicated by a -1 call site index. |
| 140 | // FIXME(strict provenance) |
| 141 | let lpad = ptr::with_exposed_provenance((cs_lpad + 1) as usize); |
| 142 | return Ok(unsafe { interpret_cs_action(action_table, cs_action_entry, lpad) }); |
| 143 | } |
| 144 | } |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | unsafe fn interpret_cs_action( |
| 149 | action_table: *const u8, |
| 150 | cs_action_entry: u64, |
| 151 | lpad: LPad, |
| 152 | ) -> EHAction { |
| 153 | if cs_action_entry == 0 { |
| 154 | // If cs_action_entry is 0 then this is a cleanup (Drop::drop). We run these |
| 155 | // for both Rust panics and foreign exceptions. |
| 156 | EHAction::Cleanup(lpad) |
| 157 | } else { |
| 158 | // If lpad != 0 and cs_action_entry != 0, we have to check ttype_index. |
| 159 | // If ttype_index == 0 under the condition, we take cleanup action. |
| 160 | let action_record: *const u8 = unsafe { action_table.offset(count:cs_action_entry as isize - 1) }; |
| 161 | let mut action_reader: DwarfReader = DwarfReader::new(ptr:action_record); |
| 162 | let ttype_index: i64 = unsafe { action_reader.read_sleb128() }; |
| 163 | if ttype_index == 0 { |
| 164 | EHAction::Cleanup(lpad) |
| 165 | } else if ttype_index > 0 { |
| 166 | // Stop unwinding Rust panics at catch_unwind. |
| 167 | EHAction::Catch(lpad) |
| 168 | } else { |
| 169 | EHAction::Filter(lpad) |
| 170 | } |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | #[inline ] |
| 175 | fn round_up(unrounded: usize, align: usize) -> Result<usize, ()> { |
| 176 | if align.is_power_of_two() { Ok((unrounded + align - 1) & !(align - 1)) } else { Err(()) } |
| 177 | } |
| 178 | |
| 179 | /// Reads an offset (`usize`) from `reader` whose encoding is described by `encoding`. |
| 180 | /// |
| 181 | /// `encoding` must be a [DWARF Exception Header Encoding as described by the LSB spec][LSB-dwarf-ext]. |
| 182 | /// In addition the upper ("application") part must be zero. |
| 183 | /// |
| 184 | /// # Errors |
| 185 | /// Returns `Err` if `encoding` |
| 186 | /// * is not a valid DWARF Exception Header Encoding, |
| 187 | /// * is `DW_EH_PE_omit`, or |
| 188 | /// * has a non-zero application part. |
| 189 | /// |
| 190 | /// [LSB-dwarf-ext]: https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/dwarfext.html |
| 191 | unsafe fn read_encoded_offset(reader: &mut DwarfReader, encoding: u8) -> Result<usize, ()> { |
| 192 | if encoding == DW_EH_PE_omit || encoding & 0xF0 != 0 { |
| 193 | return Err(()); |
| 194 | } |
| 195 | let result: usize = unsafe { |
| 196 | match encoding & 0x0F { |
| 197 | // despite the name, LLVM also uses absptr for offsets instead of pointers |
| 198 | DW_EH_PE_absptr => reader.read::<usize>(), |
| 199 | DW_EH_PE_uleb128 => reader.read_uleb128() as usize, |
| 200 | DW_EH_PE_udata2 => reader.read::<u16>() as usize, |
| 201 | DW_EH_PE_udata4 => reader.read::<u32>() as usize, |
| 202 | DW_EH_PE_udata8 => reader.read::<u64>() as usize, |
| 203 | DW_EH_PE_sleb128 => reader.read_sleb128() as usize, |
| 204 | DW_EH_PE_sdata2 => reader.read::<i16>() as usize, |
| 205 | DW_EH_PE_sdata4 => reader.read::<i32>() as usize, |
| 206 | DW_EH_PE_sdata8 => reader.read::<i64>() as usize, |
| 207 | _ => return Err(()), |
| 208 | } |
| 209 | }; |
| 210 | Ok(result) |
| 211 | } |
| 212 | |
| 213 | /// Reads a pointer from `reader` whose encoding is described by `encoding`. |
| 214 | /// |
| 215 | /// `encoding` must be a [DWARF Exception Header Encoding as described by the LSB spec][LSB-dwarf-ext]. |
| 216 | /// |
| 217 | /// # Errors |
| 218 | /// Returns `Err` if `encoding` |
| 219 | /// * is not a valid DWARF Exception Header Encoding, |
| 220 | /// * is `DW_EH_PE_omit`, or |
| 221 | /// * combines `DW_EH_PE_absptr` or `DW_EH_PE_aligned` application part with an integer encoding |
| 222 | /// (not `DW_EH_PE_absptr`) in the value format part. |
| 223 | /// |
| 224 | /// [LSB-dwarf-ext]: https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/dwarfext.html |
| 225 | unsafe fn read_encoded_pointer( |
| 226 | reader: &mut DwarfReader, |
| 227 | context: &EHContext<'_>, |
| 228 | encoding: u8, |
| 229 | ) -> Result<*const u8, ()> { |
| 230 | if encoding == DW_EH_PE_omit { |
| 231 | return Err(()); |
| 232 | } |
| 233 | |
| 234 | let base_ptr = match encoding & 0x70 { |
| 235 | DW_EH_PE_absptr => core::ptr::null(), |
| 236 | // relative to address of the encoded value, despite the name |
| 237 | DW_EH_PE_pcrel => reader.ptr, |
| 238 | DW_EH_PE_funcrel => { |
| 239 | if context.func_start.is_null() { |
| 240 | return Err(()); |
| 241 | } |
| 242 | context.func_start |
| 243 | } |
| 244 | DW_EH_PE_textrel => (*context.get_text_start)(), |
| 245 | DW_EH_PE_datarel => (*context.get_data_start)(), |
| 246 | // aligned means the value is aligned to the size of a pointer |
| 247 | DW_EH_PE_aligned => { |
| 248 | reader.ptr = reader.ptr.with_addr(round_up(reader.ptr.addr(), size_of::<*const u8>())?); |
| 249 | core::ptr::null() |
| 250 | } |
| 251 | _ => return Err(()), |
| 252 | }; |
| 253 | |
| 254 | let mut ptr = if base_ptr.is_null() { |
| 255 | // any value encoding other than absptr would be nonsensical here; |
| 256 | // there would be no source of pointer provenance |
| 257 | if encoding & 0x0F != DW_EH_PE_absptr { |
| 258 | return Err(()); |
| 259 | } |
| 260 | unsafe { reader.read::<*const u8>() } |
| 261 | } else { |
| 262 | let offset = unsafe { read_encoded_offset(reader, encoding & 0x0F)? }; |
| 263 | base_ptr.wrapping_add(offset) |
| 264 | }; |
| 265 | |
| 266 | if encoding & DW_EH_PE_indirect != 0 { |
| 267 | ptr = unsafe { *(ptr.cast::<*const u8>()) }; |
| 268 | } |
| 269 | |
| 270 | Ok(ptr) |
| 271 | } |
| 272 | |