Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/unwinder/src/exception_table.rs
3068 views
1
//! Compact representation of exception handlers associated with
2
//! callsites, for use when searching a Cranelift stack for a handler.
3
//!
4
//! This module implements (i) conversion from the metadata provided
5
//! alongside Cranelift's compilation result (as provided by
6
//! [`cranelift_codegen::MachBufferFinalized::call_sites`]) to its
7
//! format, and (ii) use of its format to find a handler efficiently.
8
//!
9
//! The format has been designed so that it can be mapped in from disk
10
//! and used without post-processing; this enables efficient
11
//! module-loading in runtimes such as Wasmtime.
12
13
use object::{Bytes, LittleEndian, U32Bytes};
14
15
#[cfg(feature = "cranelift")]
16
use alloc::vec;
17
use alloc::vec::Vec;
18
#[cfg(feature = "cranelift")]
19
use cranelift_codegen::{
20
ExceptionContextLoc, FinalizedMachCallSite, FinalizedMachExceptionHandler, binemit::CodeOffset,
21
};
22
use wasmtime_environ::prelude::*;
23
24
/// Collector struct for exception handlers per call site.
25
///
26
/// # Format
27
///
28
/// We keep six different arrays (`Vec`s) that we build as we visit
29
/// callsites, in ascending offset (address relative to beginning of
30
/// code segment) order: callsite offsets, frame offsets,
31
/// tag/destination ranges, tags, tag context SP offset, destination
32
/// offsets.
33
///
34
/// The callsite offsets, frame offsets, and tag/destination ranges
35
/// logically form a sorted lookup array, allowing us to find
36
/// information for any single callsite. The frame offset specifies
37
/// distance down to the SP value at the callsite (in bytes), relative
38
/// to the FP of that frame. The range denotes a range of indices in
39
/// the tag/context and destination offset arrays. Ranges are stored
40
/// with the (exclusive) *end* index only; the start index is implicit
41
/// as the previous end, or zero if first element.
42
///
43
/// The slices of tag, context, and handlers arrays named by `ranges`
44
/// for each callsite specify a series of handler items for that
45
/// callsite. The tag and context together allow a
46
/// dynamic-tag-instance match in the unwinder: the context specifies
47
/// an offset from SP at the callsite that contains a machine word
48
/// (e.g. with vmctx) that, together with the static tag index, can be
49
/// used to perform a dynamic match. A context of `-1` indicates no
50
/// dynamic context, and a tag of `-1` indicates a catch-all
51
/// handler. If a handler item matches, control should be transferred
52
/// to the code offset given in the last array, `handlers`.
53
///
54
/// # Example
55
///
56
/// An example of this data format:
57
///
58
/// ```plain
59
/// callsites: [0x10, 0x50, 0xf0] // callsites (return addrs) at offsets 0x10, 0x50, 0xf0
60
/// ranges: [2, 4, 5] // corresponding ranges for each callsite
61
/// frame_offsets: [0, 0x10, 0] // corresponding SP-to-FP offsets for each callsite
62
/// tags: [1, 5, 1, -1, -1] // tags for each handler at each callsite
63
/// contexts: [-1, -1, 0x10, 0x20, 0x30] // SP-offset for context for each tag
64
/// handlers: [0x40, 0x42, 0x6f, 0x71, 0xf5] // handler destinations at each callsite
65
/// ```
66
///
67
/// Expanding this out:
68
///
69
/// ```plain
70
/// callsites: [0x10, 0x50, 0xf0], # PCs relative to some start of return-points.
71
/// frame_offsets: [0, 0x10, 0], # SP-to-FP offsets at each callsite.
72
/// ranges: [
73
/// 2, # callsite 0x10 has tags/handlers indices 0..2
74
/// 4, # callsite 0x50 has tags/handlers indices 2..4
75
/// 5, # callsite 0xf0 has tags/handlers indices 4..5
76
/// ],
77
/// tags: [
78
/// # tags for callsite 0x10:
79
/// 1,
80
/// 5,
81
/// # tags for callsite 0x50:
82
/// 1,
83
/// -1, # "catch-all"
84
/// # tags for callsite 0xf0:
85
/// -1, # "catch-all"
86
/// ]
87
/// contexts: [
88
/// # SP-offsets for context for each tag at callsite 0x10:
89
/// -1,
90
/// -1,
91
/// # for callsite 0x50:
92
/// 0x10,
93
/// 0x20,
94
/// # for callsite 0xf0:
95
/// 0x30,
96
/// ]
97
/// handlers: [
98
/// # handlers for callsite 0x10:
99
/// 0x40, # relative PC to handle tag 1 (above)
100
/// 0x42, # relative PC to handle tag 5
101
/// # handlers for callsite 0x50:
102
/// 0x6f, # relative PC to handle tag 1
103
/// 0x71, # relative PC to handle all other tags
104
/// # handlers for callsite 0xf0:
105
/// 0xf5, # relative PC to handle all other tags
106
/// ]
107
/// ```
108
#[cfg(feature = "cranelift")]
109
#[derive(Clone, Debug, Default)]
110
pub struct ExceptionTableBuilder {
111
pub callsites: Vec<U32Bytes<LittleEndian>>,
112
pub frame_offsets: Vec<U32Bytes<LittleEndian>>,
113
pub ranges: Vec<U32Bytes<LittleEndian>>,
114
pub tags: Vec<U32Bytes<LittleEndian>>,
115
pub contexts: Vec<U32Bytes<LittleEndian>>,
116
pub handlers: Vec<U32Bytes<LittleEndian>>,
117
last_start_offset: CodeOffset,
118
}
119
120
#[cfg(feature = "cranelift")]
121
impl ExceptionTableBuilder {
122
/// Add a function at a given offset from the start of the
123
/// compiled code section, recording information about its call
124
/// sites.
125
///
126
/// Functions must be added in ascending offset order.
127
pub fn add_func<'a>(
128
&mut self,
129
start_offset: CodeOffset,
130
call_sites: impl Iterator<Item = FinalizedMachCallSite<'a>>,
131
) -> Result<()> {
132
// Ensure that we see functions in offset order.
133
assert!(start_offset >= self.last_start_offset);
134
self.last_start_offset = start_offset;
135
136
// Visit each callsite in turn, translating offsets from
137
// function-local to section-local.
138
let mut handlers = vec![];
139
for call_site in call_sites {
140
let ret_addr = call_site.ret_addr.checked_add(start_offset).unwrap();
141
handlers.extend(call_site.exception_handlers.iter().cloned());
142
143
let start_idx = u32::try_from(self.tags.len()).unwrap();
144
let mut context = u32::MAX;
145
for handler in call_site.exception_handlers {
146
match handler {
147
FinalizedMachExceptionHandler::Tag(tag, offset) => {
148
self.tags.push(U32Bytes::new(LittleEndian, tag.as_u32()));
149
self.contexts.push(U32Bytes::new(LittleEndian, context));
150
self.handlers.push(U32Bytes::new(
151
LittleEndian,
152
offset.checked_add(start_offset).unwrap(),
153
));
154
}
155
FinalizedMachExceptionHandler::Default(offset) => {
156
self.tags.push(U32Bytes::new(LittleEndian, u32::MAX));
157
self.contexts.push(U32Bytes::new(LittleEndian, context));
158
self.handlers.push(U32Bytes::new(
159
LittleEndian,
160
offset.checked_add(start_offset).unwrap(),
161
));
162
}
163
FinalizedMachExceptionHandler::Context(ExceptionContextLoc::SPOffset(
164
offset,
165
)) => {
166
context = *offset;
167
}
168
FinalizedMachExceptionHandler::Context(ExceptionContextLoc::GPR(_)) => {
169
panic!(
170
"Wasmtime exception unwind info only supports dynamic contexts on the stack"
171
);
172
}
173
}
174
}
175
let end_idx = u32::try_from(self.tags.len()).unwrap();
176
177
// Omit empty callsites for compactness.
178
if end_idx > start_idx {
179
self.ranges.push(U32Bytes::new(LittleEndian, end_idx));
180
self.frame_offsets.push(U32Bytes::new(
181
LittleEndian,
182
call_site.frame_offset.unwrap_or(u32::MAX),
183
));
184
self.callsites.push(U32Bytes::new(LittleEndian, ret_addr));
185
}
186
}
187
188
Ok(())
189
}
190
191
/// Serialize the exception-handler data section, taking a closure
192
/// to consume slices.
193
pub fn serialize<F: FnMut(&[u8])>(&self, mut f: F) {
194
// Serialize the length of `callsites` / `ranges`.
195
let callsite_count = u32::try_from(self.callsites.len()).unwrap();
196
f(&callsite_count.to_le_bytes());
197
// Serialize the length of `tags` / `handlers`.
198
let handler_count = u32::try_from(self.handlers.len()).unwrap();
199
f(&handler_count.to_le_bytes());
200
201
// Serialize `callsites`, `ranges`, `tags`, and `handlers` in
202
// that order.
203
f(object::bytes_of_slice(&self.callsites));
204
f(object::bytes_of_slice(&self.frame_offsets));
205
f(object::bytes_of_slice(&self.ranges));
206
f(object::bytes_of_slice(&self.tags));
207
f(object::bytes_of_slice(&self.contexts));
208
f(object::bytes_of_slice(&self.handlers));
209
}
210
211
/// Serialize the exception-handler data section to a vector of
212
/// bytes.
213
pub fn to_vec(&self) -> Vec<u8> {
214
let mut bytes = vec![];
215
self.serialize(|slice| bytes.extend(slice.iter().cloned()));
216
bytes
217
}
218
}
219
220
/// ExceptionTable deserialized from a serialized slice.
221
///
222
/// This struct retains borrows of the various serialized parts of the
223
/// exception table data as produced by
224
/// [`ExceptionTableBuilder::serialize`].
225
#[derive(Clone, Debug)]
226
pub struct ExceptionTable<'a> {
227
callsites: &'a [U32Bytes<LittleEndian>],
228
ranges: &'a [U32Bytes<LittleEndian>],
229
frame_offsets: &'a [U32Bytes<LittleEndian>],
230
tags: &'a [U32Bytes<LittleEndian>],
231
contexts: &'a [U32Bytes<LittleEndian>],
232
handlers: &'a [U32Bytes<LittleEndian>],
233
}
234
235
/// Wasmtime exception table item, after parsing.
236
///
237
/// Note that this is separately defined from the equivalent type in
238
/// Cranelift, `cranelift_codegen::FinalizedMachExceptionHandler`,
239
/// because we need this in runtime-only builds when Cranelift is not
240
/// included.
241
#[derive(Clone, Debug, PartialEq, Eq)]
242
pub struct ExceptionHandler {
243
/// A tag (arbitrary `u32` identifier from CLIF) or `None` for catch-all.
244
pub tag: Option<u32>,
245
/// Dynamic context, if provided, with which to interpret the
246
/// tag. Context is available at the given offset from SP in this
247
/// frame.
248
pub context_sp_offset: Option<u32>,
249
/// Handler code offset.
250
pub handler_offset: u32,
251
}
252
253
impl<'a> ExceptionTable<'a> {
254
/// Parse exception tables from a byte-slice as produced by
255
/// [`ExceptionTableBuilder::serialize`].
256
pub fn parse(data: &'a [u8]) -> Result<ExceptionTable<'a>> {
257
let mut data = Bytes(data);
258
let callsite_count = data
259
.read::<U32Bytes<LittleEndian>>()
260
.map_err(|_| format_err!("Unable to read callsite count prefix"))?;
261
let callsite_count = usize::try_from(callsite_count.get(LittleEndian))?;
262
let handler_count = data
263
.read::<U32Bytes<LittleEndian>>()
264
.map_err(|_| format_err!("Unable to read handler count prefix"))?;
265
let handler_count = usize::try_from(handler_count.get(LittleEndian))?;
266
let (callsites, data) =
267
object::slice_from_bytes::<U32Bytes<LittleEndian>>(data.0, callsite_count)
268
.map_err(|_| format_err!("Unable to read callsites slice"))?;
269
let (frame_offsets, data) =
270
object::slice_from_bytes::<U32Bytes<LittleEndian>>(data, callsite_count)
271
.map_err(|_| format_err!("Unable to read frame_offsets slice"))?;
272
let (ranges, data) =
273
object::slice_from_bytes::<U32Bytes<LittleEndian>>(data, callsite_count)
274
.map_err(|_| format_err!("Unable to read ranges slice"))?;
275
let (tags, data) = object::slice_from_bytes::<U32Bytes<LittleEndian>>(data, handler_count)
276
.map_err(|_| format_err!("Unable to read tags slice"))?;
277
let (contexts, data) =
278
object::slice_from_bytes::<U32Bytes<LittleEndian>>(data, handler_count)
279
.map_err(|_| format_err!("Unable to read contexts slice"))?;
280
let (handlers, data) =
281
object::slice_from_bytes::<U32Bytes<LittleEndian>>(data, handler_count)
282
.map_err(|_| format_err!("Unable to read handlers slice"))?;
283
284
if !data.is_empty() {
285
bail!("Unexpected data at end of serialized exception table");
286
}
287
288
Ok(ExceptionTable {
289
callsites,
290
frame_offsets,
291
ranges,
292
tags,
293
contexts,
294
handlers,
295
})
296
}
297
298
/// Look up the set of handlers, if any, for a given return
299
/// address (as an offset into the code section).
300
///
301
/// The handler for `None` (the catch-all/default handler), if
302
/// any, will always come last.
303
///
304
/// Note: we use raw `u32` types for code offsets here to avoid
305
/// dependencies on `cranelift-codegen` when this crate is built
306
/// without compiler backend support (runtime-only config).
307
///
308
/// Returns a tuple of `(frame offset, handler iterator)`. The
309
/// frame offset, if `Some`, specifies the distance from SP to FP
310
/// at this callsite.
311
pub fn lookup_pc(&self, pc: u32) -> (Option<u32>, impl Iterator<Item = ExceptionHandler> + '_) {
312
let callsite_idx = self
313
.callsites
314
.binary_search_by_key(&pc, |callsite| callsite.get(LittleEndian))
315
.ok();
316
let frame_offset = callsite_idx
317
.map(|idx| self.frame_offsets[idx])
318
.and_then(|offset| option_from_u32(offset.get(LittleEndian)));
319
320
(
321
frame_offset,
322
callsite_idx
323
.into_iter()
324
.flat_map(|callsite_idx| self.handlers_for_callsite(callsite_idx)),
325
)
326
}
327
328
/// Look up the frame offset and handler destination if any, for a
329
/// given return address (as an offset into the code section) and
330
/// exception tag.
331
///
332
/// Note: we use raw `u32` types for code offsets and tags here to
333
/// avoid dependencies on `cranelift-codegen` when this crate is
334
/// built without compiler backend support (runtime-only config).
335
pub fn lookup_pc_tag(&self, pc: u32, tag: u32) -> Option<(u32, u32)> {
336
// First, look up the callsite in the sorted callsites list.
337
let callsite_idx = self
338
.callsites
339
.binary_search_by_key(&pc, |callsite| callsite.get(LittleEndian))
340
.ok()?;
341
let frame_offset =
342
option_from_u32(self.frame_offsets[callsite_idx].get(LittleEndian)).unwrap_or(0);
343
344
let (tags, _, handlers) = self.tags_contexts_handlers_for_callsite(callsite_idx);
345
346
// Is there any handler with an exact tag match?
347
if let Ok(handler_idx) = tags.binary_search_by_key(&tag, |tag| tag.get(LittleEndian)) {
348
return Some((frame_offset, handlers[handler_idx].get(LittleEndian)));
349
}
350
351
// If not, is there a fallback handler? Note that we serialize
352
// it with the tag `u32::MAX`, so it is always last in sorted
353
// order.
354
if tags.last().map(|v| v.get(LittleEndian)) == Some(u32::MAX) {
355
return Some((frame_offset, handlers.last().unwrap().get(LittleEndian)));
356
}
357
358
None
359
}
360
361
fn tags_contexts_handlers_for_callsite(
362
&self,
363
idx: usize,
364
) -> (
365
&[U32Bytes<LittleEndian>],
366
&[U32Bytes<LittleEndian>],
367
&[U32Bytes<LittleEndian>],
368
) {
369
let end_idx = self.ranges[idx].get(LittleEndian);
370
let start_idx = if idx > 0 {
371
self.ranges[idx - 1].get(LittleEndian)
372
} else {
373
0
374
};
375
376
// Take the subslices of `tags`, `contexts`, and `handlers`
377
// corresponding to this callsite.
378
let start_idx = usize::try_from(start_idx).unwrap();
379
let end_idx = usize::try_from(end_idx).unwrap();
380
let tags = &self.tags[start_idx..end_idx];
381
let contexts = &self.contexts[start_idx..end_idx];
382
let handlers = &self.handlers[start_idx..end_idx];
383
(tags, contexts, handlers)
384
}
385
386
fn handlers_for_callsite(&self, idx: usize) -> impl Iterator<Item = ExceptionHandler> {
387
let (tags, contexts, handlers) = self.tags_contexts_handlers_for_callsite(idx);
388
tags.iter()
389
.zip(contexts.iter())
390
.zip(handlers.iter())
391
.map(|((tag, context), handler)| {
392
let tag = option_from_u32(tag.get(LittleEndian));
393
let context = option_from_u32(context.get(LittleEndian));
394
let handler = handler.get(LittleEndian);
395
ExceptionHandler {
396
tag,
397
context_sp_offset: context,
398
handler_offset: handler,
399
}
400
})
401
}
402
403
/// Provide an iterator over callsites, and for each callsite, the
404
/// frame offset and arrays of handlers.
405
pub fn into_iter(self) -> impl Iterator<Item = (u32, Option<u32>, Vec<ExceptionHandler>)> + 'a {
406
self.callsites
407
.iter()
408
.map(|pc| pc.get(LittleEndian))
409
.enumerate()
410
.map(move |(i, pc)| {
411
(
412
pc,
413
option_from_u32(self.frame_offsets[i].get(LittleEndian)),
414
self.handlers_for_callsite(i).collect(),
415
)
416
})
417
}
418
}
419
420
fn option_from_u32(value: u32) -> Option<u32> {
421
if value == u32::MAX { None } else { Some(value) }
422
}
423
424
#[cfg(all(test, feature = "cranelift"))]
425
mod test {
426
use super::*;
427
use cranelift_codegen::entity::EntityRef;
428
use cranelift_codegen::ir::ExceptionTag;
429
430
#[test]
431
fn serialize_exception_table() {
432
let callsites = [
433
FinalizedMachCallSite {
434
ret_addr: 0x10,
435
frame_offset: None,
436
exception_handlers: &[
437
FinalizedMachExceptionHandler::Tag(ExceptionTag::new(1), 0x20),
438
FinalizedMachExceptionHandler::Tag(ExceptionTag::new(2), 0x30),
439
FinalizedMachExceptionHandler::Default(0x40),
440
],
441
},
442
FinalizedMachCallSite {
443
ret_addr: 0x48,
444
frame_offset: None,
445
exception_handlers: &[],
446
},
447
FinalizedMachCallSite {
448
ret_addr: 0x50,
449
frame_offset: Some(0x20),
450
exception_handlers: &[FinalizedMachExceptionHandler::Default(0x60)],
451
},
452
];
453
454
let mut builder = ExceptionTableBuilder::default();
455
builder.add_func(0x100, callsites.into_iter()).unwrap();
456
let mut bytes = vec![];
457
builder.serialize(|slice| bytes.extend(slice.iter().cloned()));
458
459
let deserialized = ExceptionTable::parse(&bytes).unwrap();
460
461
let (frame_offset, iter) = deserialized.lookup_pc(0x148);
462
assert_eq!(frame_offset, None);
463
assert_eq!(iter.collect::<Vec<ExceptionHandler>>(), vec![]);
464
465
let (frame_offset, iter) = deserialized.lookup_pc(0x110);
466
assert_eq!(frame_offset, None);
467
assert_eq!(
468
iter.collect::<Vec<ExceptionHandler>>(),
469
vec![
470
ExceptionHandler {
471
tag: Some(1),
472
context_sp_offset: None,
473
handler_offset: 0x120
474
},
475
ExceptionHandler {
476
tag: Some(2),
477
context_sp_offset: None,
478
handler_offset: 0x130
479
},
480
ExceptionHandler {
481
tag: None,
482
context_sp_offset: None,
483
handler_offset: 0x140
484
},
485
]
486
);
487
488
let (frame_offset, iter) = deserialized.lookup_pc(0x150);
489
assert_eq!(frame_offset, Some(0x20));
490
assert_eq!(
491
iter.collect::<Vec<ExceptionHandler>>(),
492
vec![ExceptionHandler {
493
tag: None,
494
context_sp_offset: None,
495
handler_offset: 0x160
496
}]
497
);
498
}
499
}
500
501