wasmtime_environ/trap_encoding.rs
1use crate::obj::ELF_WASMTIME_TRAPS;
2use object::write::{Object, StandardSegment};
3use object::{Bytes, LittleEndian, SectionKind, U32Bytes};
4use std::convert::TryFrom;
5use std::fmt;
6use std::ops::Range;
7
8/// A helper structure to build the custom-encoded section of a wasmtime
9/// compilation image which encodes trap information.
10///
11/// This structure is incrementally fed the results of compiling individual
12/// functions and handles all the encoding internally, allowing usage of
13/// `lookup_trap_code` below with the resulting section.
14#[derive(Default)]
15pub struct TrapEncodingBuilder {
16 offsets: Vec<U32Bytes<LittleEndian>>,
17 traps: Vec<u8>,
18 last_offset: u32,
19}
20
21/// Information about trap.
22#[derive(Debug, PartialEq, Eq, Clone)]
23pub struct TrapInformation {
24 /// The offset of the trapping instruction in native code.
25 ///
26 /// This is relative to the beginning of the function.
27 pub code_offset: u32,
28
29 /// Code of the trap.
30 pub trap_code: Trap,
31}
32
33// The code can be accessed from the c-api, where the possible values are
34// translated into enum values defined there:
35//
36// * `wasm_trap_code` in c-api/src/trap.rs, and
37// * `wasmtime_trap_code_enum` in c-api/include/wasmtime/trap.h.
38//
39// These need to be kept in sync.
40#[non_exhaustive]
41#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
42#[allow(missing_docs)]
43pub enum Trap {
44 /// The current stack space was exhausted.
45 StackOverflow,
46
47 /// An out-of-bounds memory access.
48 MemoryOutOfBounds,
49
50 /// A wasm atomic operation was presented with a not-naturally-aligned linear-memory address.
51 HeapMisaligned,
52
53 /// An out-of-bounds access to a table.
54 TableOutOfBounds,
55
56 /// Indirect call to a null table entry.
57 IndirectCallToNull,
58
59 /// Signature mismatch on indirect call.
60 BadSignature,
61
62 /// An integer arithmetic operation caused an overflow.
63 IntegerOverflow,
64
65 /// An integer division by zero.
66 IntegerDivisionByZero,
67
68 /// Failed float-to-int conversion.
69 BadConversionToInteger,
70
71 /// Code that was supposed to have been unreachable was reached.
72 UnreachableCodeReached,
73
74 /// Execution has potentially run too long and may be interrupted.
75 Interrupt,
76
77 /// When the `component-model` feature is enabled this trap represents a
78 /// function that was `canon lift`'d, then `canon lower`'d, then called.
79 /// This combination of creation of a function in the component model
80 /// generates a function that always traps and, when called, produces this
81 /// flavor of trap.
82 AlwaysTrapAdapter,
83
84 /// When wasm code is configured to consume fuel and it runs out of fuel
85 /// then this trap will be raised.
86 OutOfFuel,
87
88 /// Used to indicate that a trap was raised by atomic wait operations on non shared memory.
89 AtomicWaitNonSharedMemory,
90 // if adding a variant here be sure to update the `check!` macro below
91}
92
93impl fmt::Display for Trap {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 use Trap::*;
96
97 let desc = match self {
98 StackOverflow => "call stack exhausted",
99 MemoryOutOfBounds => "out of bounds memory access",
100 HeapMisaligned => "unaligned atomic",
101 TableOutOfBounds => "undefined element: out of bounds table access",
102 IndirectCallToNull => "uninitialized element",
103 BadSignature => "indirect call type mismatch",
104 IntegerOverflow => "integer overflow",
105 IntegerDivisionByZero => "integer divide by zero",
106 BadConversionToInteger => "invalid conversion to integer",
107 UnreachableCodeReached => "wasm `unreachable` instruction executed",
108 Interrupt => "interrupt",
109 AlwaysTrapAdapter => "degenerate component adapter called",
110 OutOfFuel => "all fuel consumed by WebAssembly",
111 AtomicWaitNonSharedMemory => "atomic wait on non-shared memory",
112 };
113 write!(f, "wasm trap: {desc}")
114 }
115}
116
117impl std::error::Error for Trap {}
118
119impl TrapEncodingBuilder {
120 /// Appends trap information about a function into this section.
121 ///
122 /// This function is called to describe traps for the `func` range
123 /// specified. The `func` offsets are specified relative to the text section
124 /// itself, and the `traps` offsets are specified relative to the start of
125 /// `func`.
126 ///
127 /// This is required to be called in-order for increasing ranges of `func`
128 /// to ensure the final array is properly sorted. Additionally `traps` must
129 /// be sorted.
130 pub fn push(&mut self, func: Range<u64>, traps: &[TrapInformation]) {
131 // NB: for now this only supports <=4GB text sections in object files.
132 // Alternative schemes will need to be created for >32-bit offsets to
133 // avoid making this section overly large.
134 let func_start = u32::try_from(func.start).unwrap();
135 let func_end = u32::try_from(func.end).unwrap();
136
137 // Sanity-check to ensure that functions are pushed in-order, otherwise
138 // the `offsets` array won't be sorted which is our goal.
139 assert!(func_start >= self.last_offset);
140
141 self.offsets.reserve(traps.len());
142 self.traps.reserve(traps.len());
143 for info in traps {
144 let pos = func_start + info.code_offset;
145 assert!(pos >= self.last_offset);
146 self.offsets.push(U32Bytes::new(LittleEndian, pos));
147 self.traps.push(info.trap_code as u8);
148 self.last_offset = pos;
149 }
150
151 self.last_offset = func_end;
152 }
153
154 /// Encodes this section into the object provided.
155 pub fn append_to(self, obj: &mut Object) {
156 let section = obj.add_section(
157 obj.segment_name(StandardSegment::Data).to_vec(),
158 ELF_WASMTIME_TRAPS.as_bytes().to_vec(),
159 SectionKind::ReadOnlyData,
160 );
161
162 // NB: this matches the encoding expected by `lookup` below.
163 let amt = u32::try_from(self.traps.len()).unwrap();
164 obj.append_section_data(section, &amt.to_le_bytes(), 1);
165 obj.append_section_data(section, object::bytes_of_slice(&self.offsets), 1);
166 obj.append_section_data(section, &self.traps, 1);
167 }
168}
169
170/// Decodes the provided trap information section and attempts to find the trap
171/// code corresponding to the `offset` specified.
172///
173/// The `section` provided is expected to have been built by
174/// `TrapEncodingBuilder` above. Additionally the `offset` should be a relative
175/// offset within the text section of the compilation image.
176pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> {
177 let mut section = Bytes(section);
178 // NB: this matches the encoding written by `append_to` above.
179 let count = section.read::<U32Bytes<LittleEndian>>().ok()?;
180 let count = usize::try_from(count.get(LittleEndian)).ok()?;
181 let (offsets, traps) =
182 object::slice_from_bytes::<U32Bytes<LittleEndian>>(section.0, count).ok()?;
183 debug_assert_eq!(traps.len(), count);
184
185 // The `offsets` table is sorted in the trap section so perform a binary
186 // search of the contents of this section to find whether `offset` is an
187 // entry in the section. Note that this is a precise search because trap pcs
188 // should always be precise as well as our metadata about them, which means
189 // we expect an exact match to correspond to a trap opcode.
190 //
191 // Once an index is found within the `offsets` array then that same index is
192 // used to lookup from the `traps` list of bytes to get the trap code byte
193 // corresponding to this offset.
194 let offset = u32::try_from(offset).ok()?;
195 let index = offsets
196 .binary_search_by_key(&offset, |val| val.get(LittleEndian))
197 .ok()?;
198 debug_assert!(index < traps.len());
199 let trap = *traps.get(index)?;
200
201 // FIXME: this could use some sort of derive-like thing to avoid having to
202 // deduplicate the names here.
203 //
204 // This simply converts from the `trap`, a `u8`, to the `Trap` enum.
205 macro_rules! check {
206 ($($name:ident)*) => ($(if trap == Trap::$name as u8 {
207 return Some(Trap::$name);
208 })*);
209 }
210
211 check! {
212 StackOverflow
213 MemoryOutOfBounds
214 HeapMisaligned
215 TableOutOfBounds
216 IndirectCallToNull
217 BadSignature
218 IntegerOverflow
219 IntegerDivisionByZero
220 BadConversionToInteger
221 UnreachableCodeReached
222 Interrupt
223 AlwaysTrapAdapter
224 OutOfFuel
225 AtomicWaitNonSharedMemory
226 }
227
228 if cfg!(debug_assertions) {
229 panic!("missing mapping for {}", trap);
230 } else {
231 None
232 }
233}