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 super::DwarfReader; |
16 | use core::mem; |
17 | use core::ptr; |
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 | pub const USING_SJLJ_EXCEPTIONS: bool = cfg!(all(target_os = "ios" , target_arch = "arm" )); |
58 | |
59 | pub unsafe fn find_eh_action(lsda: *const u8, context: &EHContext<'_>) -> Result<EHAction, ()> { |
60 | if lsda.is_null() { |
61 | return Ok(EHAction::None); |
62 | } |
63 | |
64 | let func_start = context.func_start; |
65 | let mut reader = DwarfReader::new(lsda); |
66 | |
67 | let start_encoding = reader.read::<u8>(); |
68 | // base address for landing pad offsets |
69 | let lpad_base = if start_encoding != DW_EH_PE_omit { |
70 | read_encoded_pointer(&mut reader, context, start_encoding)? |
71 | } else { |
72 | func_start |
73 | }; |
74 | |
75 | let ttype_encoding = reader.read::<u8>(); |
76 | if ttype_encoding != DW_EH_PE_omit { |
77 | // Rust doesn't analyze exception types, so we don't care about the type table |
78 | reader.read_uleb128(); |
79 | } |
80 | |
81 | let call_site_encoding = reader.read::<u8>(); |
82 | let call_site_table_length = reader.read_uleb128(); |
83 | let action_table = reader.ptr.add(call_site_table_length as usize); |
84 | let ip = context.ip; |
85 | |
86 | if !USING_SJLJ_EXCEPTIONS { |
87 | // read the callsite table |
88 | while reader.ptr < action_table { |
89 | // these are offsets rather than pointers; |
90 | let cs_start = read_encoded_offset(&mut reader, call_site_encoding)?; |
91 | let cs_len = read_encoded_offset(&mut reader, call_site_encoding)?; |
92 | let cs_lpad = read_encoded_offset(&mut reader, call_site_encoding)?; |
93 | let cs_action_entry = reader.read_uleb128(); |
94 | // Callsite table is sorted by cs_start, so if we've passed the ip, we |
95 | // may stop searching. |
96 | if ip < func_start.wrapping_add(cs_start) { |
97 | break; |
98 | } |
99 | if ip < func_start.wrapping_add(cs_start + cs_len) { |
100 | if cs_lpad == 0 { |
101 | return Ok(EHAction::None); |
102 | } else { |
103 | let lpad = lpad_base.wrapping_add(cs_lpad); |
104 | return Ok(interpret_cs_action(action_table, cs_action_entry, lpad)); |
105 | } |
106 | } |
107 | } |
108 | // Ip is not present in the table. This indicates a nounwind call. |
109 | Ok(EHAction::Terminate) |
110 | } else { |
111 | // SjLj version: |
112 | // The "IP" is an index into the call-site table, with two exceptions: |
113 | // -1 means 'no-action', and 0 means 'terminate'. |
114 | match ip.addr() as isize { |
115 | -1 => return Ok(EHAction::None), |
116 | 0 => return Ok(EHAction::Terminate), |
117 | _ => (), |
118 | } |
119 | let mut idx = ip.addr(); |
120 | loop { |
121 | let cs_lpad = reader.read_uleb128(); |
122 | let cs_action_entry = reader.read_uleb128(); |
123 | idx -= 1; |
124 | if idx == 0 { |
125 | // Can never have null landing pad for sjlj -- that would have |
126 | // been indicated by a -1 call site index. |
127 | // FIXME(strict provenance) |
128 | let lpad = ptr::with_exposed_provenance((cs_lpad + 1) as usize); |
129 | return Ok(interpret_cs_action(action_table, cs_action_entry, lpad)); |
130 | } |
131 | } |
132 | } |
133 | } |
134 | |
135 | unsafe fn interpret_cs_action( |
136 | action_table: *const u8, |
137 | cs_action_entry: u64, |
138 | lpad: LPad, |
139 | ) -> EHAction { |
140 | if cs_action_entry == 0 { |
141 | // If cs_action_entry is 0 then this is a cleanup (Drop::drop). We run these |
142 | // for both Rust panics and foreign exceptions. |
143 | EHAction::Cleanup(lpad) |
144 | } else { |
145 | // If lpad != 0 and cs_action_entry != 0, we have to check ttype_index. |
146 | // If ttype_index == 0 under the condition, we take cleanup action. |
147 | let action_record: *const u8 = action_table.offset(count:cs_action_entry as isize - 1); |
148 | let mut action_reader: DwarfReader = DwarfReader::new(ptr:action_record); |
149 | let ttype_index: i64 = action_reader.read_sleb128(); |
150 | if ttype_index == 0 { |
151 | EHAction::Cleanup(lpad) |
152 | } else if ttype_index > 0 { |
153 | // Stop unwinding Rust panics at catch_unwind. |
154 | EHAction::Catch(lpad) |
155 | } else { |
156 | EHAction::Filter(lpad) |
157 | } |
158 | } |
159 | } |
160 | |
161 | #[inline ] |
162 | fn round_up(unrounded: usize, align: usize) -> Result<usize, ()> { |
163 | if align.is_power_of_two() { Ok((unrounded + align - 1) & !(align - 1)) } else { Err(()) } |
164 | } |
165 | |
166 | /// Read a offset (`usize`) from `reader` whose encoding is described by `encoding`. |
167 | /// |
168 | /// `encoding` must be a [DWARF Exception Header Encoding as described by the LSB spec][LSB-dwarf-ext]. |
169 | /// In addition the upper ("application") part must be zero. |
170 | /// |
171 | /// # Errors |
172 | /// Returns `Err` if `encoding` |
173 | /// * is not a valid DWARF Exception Header Encoding, |
174 | /// * is `DW_EH_PE_omit`, or |
175 | /// * has a non-zero application part. |
176 | /// |
177 | /// [LSB-dwarf-ext]: https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/dwarfext.html |
178 | unsafe fn read_encoded_offset(reader: &mut DwarfReader, encoding: u8) -> Result<usize, ()> { |
179 | if encoding == DW_EH_PE_omit || encoding & 0xF0 != 0 { |
180 | return Err(()); |
181 | } |
182 | let result: usize = match encoding & 0x0F { |
183 | // despite the name, LLVM also uses absptr for offsets instead of pointers |
184 | DW_EH_PE_absptr => reader.read::<usize>(), |
185 | DW_EH_PE_uleb128 => reader.read_uleb128() as usize, |
186 | DW_EH_PE_udata2 => reader.read::<u16>() as usize, |
187 | DW_EH_PE_udata4 => reader.read::<u32>() as usize, |
188 | DW_EH_PE_udata8 => reader.read::<u64>() as usize, |
189 | DW_EH_PE_sleb128 => reader.read_sleb128() as usize, |
190 | DW_EH_PE_sdata2 => reader.read::<i16>() as usize, |
191 | DW_EH_PE_sdata4 => reader.read::<i32>() as usize, |
192 | DW_EH_PE_sdata8 => reader.read::<i64>() as usize, |
193 | _ => return Err(()), |
194 | }; |
195 | Ok(result) |
196 | } |
197 | |
198 | /// Read a pointer from `reader` whose encoding is described by `encoding`. |
199 | /// |
200 | /// `encoding` must be a [DWARF Exception Header Encoding as described by the LSB spec][LSB-dwarf-ext]. |
201 | /// |
202 | /// # Errors |
203 | /// Returns `Err` if `encoding` |
204 | /// * is not a valid DWARF Exception Header Encoding, |
205 | /// * is `DW_EH_PE_omit`, or |
206 | /// * combines `DW_EH_PE_absptr` or `DW_EH_PE_aligned` application part with an integer encoding |
207 | /// (not `DW_EH_PE_absptr`) in the value format part. |
208 | /// |
209 | /// [LSB-dwarf-ext]: https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/dwarfext.html |
210 | unsafe fn read_encoded_pointer( |
211 | reader: &mut DwarfReader, |
212 | context: &EHContext<'_>, |
213 | encoding: u8, |
214 | ) -> Result<*const u8, ()> { |
215 | if encoding == DW_EH_PE_omit { |
216 | return Err(()); |
217 | } |
218 | |
219 | let base_ptr = match encoding & 0x70 { |
220 | DW_EH_PE_absptr => core::ptr::null(), |
221 | // relative to address of the encoded value, despite the name |
222 | DW_EH_PE_pcrel => reader.ptr, |
223 | DW_EH_PE_funcrel => { |
224 | if context.func_start.is_null() { |
225 | return Err(()); |
226 | } |
227 | context.func_start |
228 | } |
229 | DW_EH_PE_textrel => (*context.get_text_start)(), |
230 | DW_EH_PE_datarel => (*context.get_data_start)(), |
231 | // aligned means the value is aligned to the size of a pointer |
232 | DW_EH_PE_aligned => { |
233 | reader.ptr = |
234 | reader.ptr.with_addr(round_up(reader.ptr.addr(), mem::size_of::<*const u8>())?); |
235 | core::ptr::null() |
236 | } |
237 | _ => return Err(()), |
238 | }; |
239 | |
240 | let mut ptr = if base_ptr.is_null() { |
241 | // any value encoding other than absptr would be nonsensical here; |
242 | // there would be no source of pointer provenance |
243 | if encoding & 0x0F != DW_EH_PE_absptr { |
244 | return Err(()); |
245 | } |
246 | reader.read::<*const u8>() |
247 | } else { |
248 | let offset = read_encoded_offset(reader, encoding & 0x0F)?; |
249 | base_ptr.wrapping_add(offset) |
250 | }; |
251 | |
252 | if encoding & DW_EH_PE_indirect != 0 { |
253 | ptr = *(ptr.cast::<*const u8>()); |
254 | } |
255 | |
256 | Ok(ptr) |
257 | } |
258 | |