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