Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/environ/src/trap_encoding.rs
1691 views
1
use core::fmt;
2
use object::{Bytes, LittleEndian, U32Bytes};
3
4
/// Information about trap.
5
#[derive(Debug, PartialEq, Eq, Clone)]
6
pub struct TrapInformation {
7
/// The offset of the trapping instruction in native code.
8
///
9
/// This is relative to the beginning of the function.
10
pub code_offset: u32,
11
12
/// Code of the trap.
13
pub trap_code: Trap,
14
}
15
16
// The code can be accessed from the c-api, where the possible values are
17
// translated into enum values defined there:
18
//
19
// * `wasm_trap_code` in c-api/src/trap.rs, and
20
// * `wasmtime_trap_code_enum` in c-api/include/wasmtime/trap.h.
21
//
22
// These need to be kept in sync.
23
#[non_exhaustive]
24
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
25
#[expect(missing_docs, reason = "self-describing variants")]
26
pub enum Trap {
27
/// The current stack space was exhausted.
28
StackOverflow,
29
30
/// An out-of-bounds memory access.
31
MemoryOutOfBounds,
32
33
/// A wasm atomic operation was presented with a not-naturally-aligned linear-memory address.
34
HeapMisaligned,
35
36
/// An out-of-bounds access to a table.
37
TableOutOfBounds,
38
39
/// Indirect call to a null table entry.
40
IndirectCallToNull,
41
42
/// Signature mismatch on indirect call.
43
BadSignature,
44
45
/// An integer arithmetic operation caused an overflow.
46
IntegerOverflow,
47
48
/// An integer division by zero.
49
IntegerDivisionByZero,
50
51
/// Failed float-to-int conversion.
52
BadConversionToInteger,
53
54
/// Code that was supposed to have been unreachable was reached.
55
UnreachableCodeReached,
56
57
/// Execution has potentially run too long and may be interrupted.
58
Interrupt,
59
60
/// When the `component-model` feature is enabled this trap represents a
61
/// function that was `canon lift`'d, then `canon lower`'d, then called.
62
/// This combination of creation of a function in the component model
63
/// generates a function that always traps and, when called, produces this
64
/// flavor of trap.
65
AlwaysTrapAdapter,
66
67
/// When wasm code is configured to consume fuel and it runs out of fuel
68
/// then this trap will be raised.
69
OutOfFuel,
70
71
/// Used to indicate that a trap was raised by atomic wait operations on non shared memory.
72
AtomicWaitNonSharedMemory,
73
74
/// Call to a null reference.
75
NullReference,
76
77
/// Attempt to access beyond the bounds of an array.
78
ArrayOutOfBounds,
79
80
/// Attempted an allocation that was too large to succeed.
81
AllocationTooLarge,
82
83
/// Attempted to cast a reference to a type that it is not an instance of.
84
CastFailure,
85
86
/// When the `component-model` feature is enabled this trap represents a
87
/// scenario where one component tried to call another component but it
88
/// would have violated the reentrance rules of the component model,
89
/// triggering a trap instead.
90
CannotEnterComponent,
91
92
/// Async-lifted export failed to produce a result by calling `task.return`
93
/// before returning `STATUS_DONE` and/or after all host tasks completed.
94
NoAsyncResult,
95
96
/// We are suspending to a tag for which there is no active handler.
97
UnhandledTag,
98
99
/// Attempt to resume a continuation twice.
100
ContinuationAlreadyConsumed,
101
102
/// A Pulley opcode was executed at runtime when the opcode was disabled at
103
/// compile time.
104
DisabledOpcode,
105
106
/// Async event loop deadlocked; i.e. it cannot make further progress given
107
/// that all host tasks have completed and any/all host-owned stream/future
108
/// handles have been dropped.
109
AsyncDeadlock,
110
// if adding a variant here be sure to update the `check!` macro below
111
}
112
113
impl Trap {
114
/// Converts a byte back into a `Trap` if its in-bounds
115
pub fn from_u8(byte: u8) -> Option<Trap> {
116
// FIXME: this could use some sort of derive-like thing to avoid having to
117
// deduplicate the names here.
118
//
119
// This simply converts from the a `u8`, to the `Trap` enum.
120
macro_rules! check {
121
($($name:ident)*) => ($(if byte == Trap::$name as u8 {
122
return Some(Trap::$name);
123
})*);
124
}
125
126
check! {
127
StackOverflow
128
MemoryOutOfBounds
129
HeapMisaligned
130
TableOutOfBounds
131
IndirectCallToNull
132
BadSignature
133
IntegerOverflow
134
IntegerDivisionByZero
135
BadConversionToInteger
136
UnreachableCodeReached
137
Interrupt
138
AlwaysTrapAdapter
139
OutOfFuel
140
AtomicWaitNonSharedMemory
141
NullReference
142
ArrayOutOfBounds
143
AllocationTooLarge
144
CastFailure
145
CannotEnterComponent
146
NoAsyncResult
147
UnhandledTag
148
ContinuationAlreadyConsumed
149
DisabledOpcode
150
AsyncDeadlock
151
}
152
153
None
154
}
155
}
156
157
impl fmt::Display for Trap {
158
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159
use Trap::*;
160
161
let desc = match self {
162
StackOverflow => "call stack exhausted",
163
MemoryOutOfBounds => "out of bounds memory access",
164
HeapMisaligned => "unaligned atomic",
165
TableOutOfBounds => "undefined element: out of bounds table access",
166
IndirectCallToNull => "uninitialized element",
167
BadSignature => "indirect call type mismatch",
168
IntegerOverflow => "integer overflow",
169
IntegerDivisionByZero => "integer divide by zero",
170
BadConversionToInteger => "invalid conversion to integer",
171
UnreachableCodeReached => "wasm `unreachable` instruction executed",
172
Interrupt => "interrupt",
173
AlwaysTrapAdapter => "degenerate component adapter called",
174
OutOfFuel => "all fuel consumed by WebAssembly",
175
AtomicWaitNonSharedMemory => "atomic wait on non-shared memory",
176
NullReference => "null reference",
177
ArrayOutOfBounds => "out of bounds array access",
178
AllocationTooLarge => "allocation size too large",
179
CastFailure => "cast failure",
180
CannotEnterComponent => "cannot enter component instance",
181
NoAsyncResult => "async-lifted export failed to produce a result",
182
UnhandledTag => "unhandled tag",
183
ContinuationAlreadyConsumed => "continuation already consumed",
184
DisabledOpcode => "pulley opcode disabled at compile time was executed",
185
AsyncDeadlock => "deadlock detected: event loop cannot make further progress",
186
};
187
write!(f, "wasm trap: {desc}")
188
}
189
}
190
191
impl core::error::Error for Trap {}
192
193
/// Decodes the provided trap information section and attempts to find the trap
194
/// code corresponding to the `offset` specified.
195
///
196
/// The `section` provided is expected to have been built by
197
/// `TrapEncodingBuilder` above. Additionally the `offset` should be a relative
198
/// offset within the text section of the compilation image.
199
pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> {
200
let (offsets, traps) = parse(section)?;
201
202
// The `offsets` table is sorted in the trap section so perform a binary
203
// search of the contents of this section to find whether `offset` is an
204
// entry in the section. Note that this is a precise search because trap pcs
205
// should always be precise as well as our metadata about them, which means
206
// we expect an exact match to correspond to a trap opcode.
207
//
208
// Once an index is found within the `offsets` array then that same index is
209
// used to lookup from the `traps` list of bytes to get the trap code byte
210
// corresponding to this offset.
211
let offset = u32::try_from(offset).ok()?;
212
let index = offsets
213
.binary_search_by_key(&offset, |val| val.get(LittleEndian))
214
.ok()?;
215
debug_assert!(index < traps.len());
216
let byte = *traps.get(index)?;
217
218
let trap = Trap::from_u8(byte);
219
debug_assert!(trap.is_some(), "missing mapping for {byte}");
220
trap
221
}
222
223
fn parse(section: &[u8]) -> Option<(&[U32Bytes<LittleEndian>], &[u8])> {
224
let mut section = Bytes(section);
225
// NB: this matches the encoding written by `append_to` above.
226
let count = section.read::<U32Bytes<LittleEndian>>().ok()?;
227
let count = usize::try_from(count.get(LittleEndian)).ok()?;
228
let (offsets, traps) =
229
object::slice_from_bytes::<U32Bytes<LittleEndian>>(section.0, count).ok()?;
230
debug_assert_eq!(traps.len(), count);
231
Some((offsets, traps))
232
}
233
234
/// Returns an iterator over all of the traps encoded in `section`, which should
235
/// have been produced by `TrapEncodingBuilder`.
236
pub fn iterate_traps(section: &[u8]) -> Option<impl Iterator<Item = (u32, Trap)> + '_> {
237
let (offsets, traps) = parse(section)?;
238
Some(
239
offsets
240
.iter()
241
.zip(traps)
242
.map(|(offset, trap)| (offset.get(LittleEndian), Trap::from_u8(*trap).unwrap())),
243
)
244
}
245
246