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