wasmtime_internal_unwinder/
exception_table.rs1use object::{Bytes, LittleEndian, U32Bytes};
14
15#[cfg(feature = "cranelift")]
16use alloc::vec;
17use alloc::vec::Vec;
18#[cfg(feature = "cranelift")]
19use cranelift_codegen::{
20 ExceptionContextLoc, FinalizedMachCallSite, FinalizedMachExceptionHandler, binemit::CodeOffset,
21};
22
23#[cfg(feature = "cranelift")]
108#[derive(Clone, Debug, Default)]
109pub struct ExceptionTableBuilder {
110 pub callsites: Vec<U32Bytes<LittleEndian>>,
111 pub frame_offsets: Vec<U32Bytes<LittleEndian>>,
112 pub ranges: Vec<U32Bytes<LittleEndian>>,
113 pub tags: Vec<U32Bytes<LittleEndian>>,
114 pub contexts: Vec<U32Bytes<LittleEndian>>,
115 pub handlers: Vec<U32Bytes<LittleEndian>>,
116 last_start_offset: CodeOffset,
117}
118
119#[cfg(feature = "cranelift")]
120impl ExceptionTableBuilder {
121 pub fn add_func<'a>(
127 &mut self,
128 start_offset: CodeOffset,
129 call_sites: impl Iterator<Item = FinalizedMachCallSite<'a>>,
130 ) -> anyhow::Result<()> {
131 assert!(start_offset >= self.last_start_offset);
133 self.last_start_offset = start_offset;
134
135 let mut handlers = vec![];
138 for call_site in call_sites {
139 let ret_addr = call_site.ret_addr.checked_add(start_offset).unwrap();
140 handlers.extend(call_site.exception_handlers.iter().cloned());
141
142 let start_idx = u32::try_from(self.tags.len()).unwrap();
143 let mut context = u32::MAX;
144 for handler in call_site.exception_handlers {
145 match handler {
146 FinalizedMachExceptionHandler::Tag(tag, offset) => {
147 self.tags.push(U32Bytes::new(LittleEndian, tag.as_u32()));
148 self.contexts.push(U32Bytes::new(LittleEndian, context));
149 self.handlers.push(U32Bytes::new(
150 LittleEndian,
151 offset.checked_add(start_offset).unwrap(),
152 ));
153 }
154 FinalizedMachExceptionHandler::Default(offset) => {
155 self.tags.push(U32Bytes::new(LittleEndian, u32::MAX));
156 self.contexts.push(U32Bytes::new(LittleEndian, context));
157 self.handlers.push(U32Bytes::new(
158 LittleEndian,
159 offset.checked_add(start_offset).unwrap(),
160 ));
161 }
162 FinalizedMachExceptionHandler::Context(ExceptionContextLoc::SPOffset(
163 offset,
164 )) => {
165 context = *offset;
166 }
167 FinalizedMachExceptionHandler::Context(ExceptionContextLoc::GPR(_)) => {
168 panic!(
169 "Wasmtime exception unwind info only supports dynamic contexts on the stack"
170 );
171 }
172 }
173 }
174 let end_idx = u32::try_from(self.tags.len()).unwrap();
175
176 if end_idx > start_idx {
178 self.ranges.push(U32Bytes::new(LittleEndian, end_idx));
179 self.frame_offsets.push(U32Bytes::new(
180 LittleEndian,
181 call_site.frame_offset.unwrap_or(u32::MAX),
182 ));
183 self.callsites.push(U32Bytes::new(LittleEndian, ret_addr));
184 }
185 }
186
187 Ok(())
188 }
189
190 pub fn serialize<F: FnMut(&[u8])>(&self, mut f: F) {
193 let callsite_count = u32::try_from(self.callsites.len()).unwrap();
195 f(&callsite_count.to_le_bytes());
196 let handler_count = u32::try_from(self.handlers.len()).unwrap();
198 f(&handler_count.to_le_bytes());
199
200 f(object::bytes_of_slice(&self.callsites));
203 f(object::bytes_of_slice(&self.frame_offsets));
204 f(object::bytes_of_slice(&self.ranges));
205 f(object::bytes_of_slice(&self.tags));
206 f(object::bytes_of_slice(&self.contexts));
207 f(object::bytes_of_slice(&self.handlers));
208 }
209
210 pub fn to_vec(&self) -> Vec<u8> {
213 let mut bytes = vec![];
214 self.serialize(|slice| bytes.extend(slice.iter().cloned()));
215 bytes
216 }
217}
218
219#[derive(Clone, Debug)]
225pub struct ExceptionTable<'a> {
226 callsites: &'a [U32Bytes<LittleEndian>],
227 ranges: &'a [U32Bytes<LittleEndian>],
228 frame_offsets: &'a [U32Bytes<LittleEndian>],
229 tags: &'a [U32Bytes<LittleEndian>],
230 contexts: &'a [U32Bytes<LittleEndian>],
231 handlers: &'a [U32Bytes<LittleEndian>],
232}
233
234#[derive(Clone, Debug, PartialEq, Eq)]
241pub struct ExceptionHandler {
242 pub tag: Option<u32>,
244 pub context_sp_offset: Option<u32>,
248 pub handler_offset: u32,
250}
251
252impl<'a> ExceptionTable<'a> {
253 pub fn parse(data: &'a [u8]) -> anyhow::Result<ExceptionTable<'a>> {
256 let mut data = Bytes(data);
257 let callsite_count = data
258 .read::<U32Bytes<LittleEndian>>()
259 .map_err(|_| anyhow::anyhow!("Unable to read callsite count prefix"))?;
260 let callsite_count = usize::try_from(callsite_count.get(LittleEndian))?;
261 let handler_count = data
262 .read::<U32Bytes<LittleEndian>>()
263 .map_err(|_| anyhow::anyhow!("Unable to read handler count prefix"))?;
264 let handler_count = usize::try_from(handler_count.get(LittleEndian))?;
265 let (callsites, data) =
266 object::slice_from_bytes::<U32Bytes<LittleEndian>>(data.0, callsite_count)
267 .map_err(|_| anyhow::anyhow!("Unable to read callsites slice"))?;
268 let (frame_offsets, data) =
269 object::slice_from_bytes::<U32Bytes<LittleEndian>>(data, callsite_count)
270 .map_err(|_| anyhow::anyhow!("Unable to read frame_offsets slice"))?;
271 let (ranges, data) =
272 object::slice_from_bytes::<U32Bytes<LittleEndian>>(data, callsite_count)
273 .map_err(|_| anyhow::anyhow!("Unable to read ranges slice"))?;
274 let (tags, data) = object::slice_from_bytes::<U32Bytes<LittleEndian>>(data, handler_count)
275 .map_err(|_| anyhow::anyhow!("Unable to read tags slice"))?;
276 let (contexts, data) =
277 object::slice_from_bytes::<U32Bytes<LittleEndian>>(data, handler_count)
278 .map_err(|_| anyhow::anyhow!("Unable to read contexts slice"))?;
279 let (handlers, data) =
280 object::slice_from_bytes::<U32Bytes<LittleEndian>>(data, handler_count)
281 .map_err(|_| anyhow::anyhow!("Unable to read handlers slice"))?;
282
283 if !data.is_empty() {
284 anyhow::bail!("Unexpected data at end of serialized exception table");
285 }
286
287 Ok(ExceptionTable {
288 callsites,
289 frame_offsets,
290 ranges,
291 tags,
292 contexts,
293 handlers,
294 })
295 }
296
297 pub fn lookup_pc(&self, pc: u32) -> (Option<u32>, impl Iterator<Item = ExceptionHandler> + '_) {
311 let callsite_idx = self
312 .callsites
313 .binary_search_by_key(&pc, |callsite| callsite.get(LittleEndian))
314 .ok();
315 let frame_offset = callsite_idx
316 .map(|idx| self.frame_offsets[idx])
317 .and_then(|offset| option_from_u32(offset.get(LittleEndian)));
318
319 (
320 frame_offset,
321 callsite_idx
322 .into_iter()
323 .flat_map(|callsite_idx| self.handlers_for_callsite(callsite_idx)),
324 )
325 }
326
327 pub fn lookup_pc_tag(&self, pc: u32, tag: u32) -> Option<(u32, u32)> {
335 let callsite_idx = self
337 .callsites
338 .binary_search_by_key(&pc, |callsite| callsite.get(LittleEndian))
339 .ok()?;
340 let frame_offset =
341 option_from_u32(self.frame_offsets[callsite_idx].get(LittleEndian)).unwrap_or(0);
342
343 let (tags, _, handlers) = self.tags_contexts_handlers_for_callsite(callsite_idx);
344
345 if let Ok(handler_idx) = tags.binary_search_by_key(&tag, |tag| tag.get(LittleEndian)) {
347 return Some((frame_offset, handlers[handler_idx].get(LittleEndian)));
348 }
349
350 if tags.last().map(|v| v.get(LittleEndian)) == Some(u32::MAX) {
354 return Some((frame_offset, handlers.last().unwrap().get(LittleEndian)));
355 }
356
357 None
358 }
359
360 fn tags_contexts_handlers_for_callsite(
361 &self,
362 idx: usize,
363 ) -> (
364 &[U32Bytes<LittleEndian>],
365 &[U32Bytes<LittleEndian>],
366 &[U32Bytes<LittleEndian>],
367 ) {
368 let end_idx = self.ranges[idx].get(LittleEndian);
369 let start_idx = if idx > 0 {
370 self.ranges[idx - 1].get(LittleEndian)
371 } else {
372 0
373 };
374
375 let start_idx = usize::try_from(start_idx).unwrap();
378 let end_idx = usize::try_from(end_idx).unwrap();
379 let tags = &self.tags[start_idx..end_idx];
380 let contexts = &self.contexts[start_idx..end_idx];
381 let handlers = &self.handlers[start_idx..end_idx];
382 (tags, contexts, handlers)
383 }
384
385 fn handlers_for_callsite(&self, idx: usize) -> impl Iterator<Item = ExceptionHandler> {
386 let (tags, contexts, handlers) = self.tags_contexts_handlers_for_callsite(idx);
387 tags.iter()
388 .zip(contexts.iter())
389 .zip(handlers.iter())
390 .map(|((tag, context), handler)| {
391 let tag = option_from_u32(tag.get(LittleEndian));
392 let context = option_from_u32(context.get(LittleEndian));
393 let handler = handler.get(LittleEndian);
394 ExceptionHandler {
395 tag,
396 context_sp_offset: context,
397 handler_offset: handler,
398 }
399 })
400 }
401
402 pub fn into_iter(self) -> impl Iterator<Item = (u32, Option<u32>, Vec<ExceptionHandler>)> + 'a {
405 self.callsites
406 .iter()
407 .map(|pc| pc.get(LittleEndian))
408 .enumerate()
409 .map(move |(i, pc)| {
410 (
411 pc,
412 option_from_u32(self.frame_offsets[i].get(LittleEndian)),
413 self.handlers_for_callsite(i).collect(),
414 )
415 })
416 }
417}
418
419fn option_from_u32(value: u32) -> Option<u32> {
420 if value == u32::MAX { None } else { Some(value) }
421}
422
423#[cfg(all(test, feature = "cranelift"))]
424mod test {
425 use super::*;
426 use cranelift_codegen::entity::EntityRef;
427 use cranelift_codegen::ir::ExceptionTag;
428
429 #[test]
430 fn serialize_exception_table() {
431 let callsites = [
432 FinalizedMachCallSite {
433 ret_addr: 0x10,
434 frame_offset: None,
435 exception_handlers: &[
436 FinalizedMachExceptionHandler::Tag(ExceptionTag::new(1), 0x20),
437 FinalizedMachExceptionHandler::Tag(ExceptionTag::new(2), 0x30),
438 FinalizedMachExceptionHandler::Default(0x40),
439 ],
440 },
441 FinalizedMachCallSite {
442 ret_addr: 0x48,
443 frame_offset: None,
444 exception_handlers: &[],
445 },
446 FinalizedMachCallSite {
447 ret_addr: 0x50,
448 frame_offset: Some(0x20),
449 exception_handlers: &[FinalizedMachExceptionHandler::Default(0x60)],
450 },
451 ];
452
453 let mut builder = ExceptionTableBuilder::default();
454 builder.add_func(0x100, callsites.into_iter()).unwrap();
455 let mut bytes = vec![];
456 builder.serialize(|slice| bytes.extend(slice.iter().cloned()));
457
458 let deserialized = ExceptionTable::parse(&bytes).unwrap();
459
460 let (frame_offset, iter) = deserialized.lookup_pc(0x148);
461 assert_eq!(frame_offset, None);
462 assert_eq!(iter.collect::<Vec<ExceptionHandler>>(), vec![]);
463
464 let (frame_offset, iter) = deserialized.lookup_pc(0x110);
465 assert_eq!(frame_offset, None);
466 assert_eq!(
467 iter.collect::<Vec<ExceptionHandler>>(),
468 vec![
469 ExceptionHandler {
470 tag: Some(1),
471 context_sp_offset: None,
472 handler_offset: 0x120
473 },
474 ExceptionHandler {
475 tag: Some(2),
476 context_sp_offset: None,
477 handler_offset: 0x130
478 },
479 ExceptionHandler {
480 tag: None,
481 context_sp_offset: None,
482 handler_offset: 0x140
483 },
484 ]
485 );
486
487 let (frame_offset, iter) = deserialized.lookup_pc(0x150);
488 assert_eq!(frame_offset, Some(0x20));
489 assert_eq!(
490 iter.collect::<Vec<ExceptionHandler>>(),
491 vec![ExceptionHandler {
492 tag: None,
493 context_sp_offset: None,
494 handler_offset: 0x160
495 }]
496 );
497 }
498}