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 /// A Pulley opcode was executed at runtime when the opcode was disabled at 97 /// compile time. 98 DisabledOpcode, 99 // if adding a variant here be sure to update the `check!` macro below 100 } 101 102 impl Trap { 103 /// Converts a byte back into a `Trap` if its in-bounds 104 pub fn from_u8(byte: u8) -> Option<Trap> { 105 // FIXME: this could use some sort of derive-like thing to avoid having to 106 // deduplicate the names here. 107 // 108 // This simply converts from the a `u8`, to the `Trap` enum. 109 macro_rules! check { 110 ($($name:ident)*) => ($(if byte == Trap::$name as u8 { 111 return Some(Trap::$name); 112 })*); 113 } 114 115 check! { 116 StackOverflow 117 MemoryOutOfBounds 118 HeapMisaligned 119 TableOutOfBounds 120 IndirectCallToNull 121 BadSignature 122 IntegerOverflow 123 IntegerDivisionByZero 124 BadConversionToInteger 125 UnreachableCodeReached 126 Interrupt 127 AlwaysTrapAdapter 128 OutOfFuel 129 AtomicWaitNonSharedMemory 130 NullReference 131 ArrayOutOfBounds 132 AllocationTooLarge 133 CastFailure 134 CannotEnterComponent 135 NoAsyncResult 136 DisabledOpcode 137 } 138 139 None 140 } 141 } 142 143 impl fmt::Display for Trap { 144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 145 use Trap::*; 146 147 let desc = match self { 148 StackOverflow => "call stack exhausted", 149 MemoryOutOfBounds => "out of bounds memory access", 150 HeapMisaligned => "unaligned atomic", 151 TableOutOfBounds => "undefined element: out of bounds table access", 152 IndirectCallToNull => "uninitialized element", 153 BadSignature => "indirect call type mismatch", 154 IntegerOverflow => "integer overflow", 155 IntegerDivisionByZero => "integer divide by zero", 156 BadConversionToInteger => "invalid conversion to integer", 157 UnreachableCodeReached => "wasm `unreachable` instruction executed", 158 Interrupt => "interrupt", 159 AlwaysTrapAdapter => "degenerate component adapter called", 160 OutOfFuel => "all fuel consumed by WebAssembly", 161 AtomicWaitNonSharedMemory => "atomic wait on non-shared memory", 162 NullReference => "null reference", 163 ArrayOutOfBounds => "out of bounds array access", 164 AllocationTooLarge => "allocation size too large", 165 CastFailure => "cast failure", 166 CannotEnterComponent => "cannot enter component instance", 167 NoAsyncResult => "async-lifted export failed to produce a result", 168 DisabledOpcode => "pulley opcode disabled at compile time was executed", 169 }; 170 write!(f, "wasm trap: {desc}") 171 } 172 } 173 174 impl core::error::Error for Trap {} 175 176 /// Decodes the provided trap information section and attempts to find the trap 177 /// code corresponding to the `offset` specified. 178 /// 179 /// The `section` provided is expected to have been built by 180 /// `TrapEncodingBuilder` above. Additionally the `offset` should be a relative 181 /// offset within the text section of the compilation image. 182 pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> { 183 let (offsets, traps) = parse(section)?; 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 byte = *traps.get(index)?; 200 201 let trap = Trap::from_u8(byte); 202 debug_assert!(trap.is_some(), "missing mapping for {byte}"); 203 trap 204 } 205 206 fn parse(section: &[u8]) -> Option<(&[U32Bytes<LittleEndian>], &[u8])> { 207 let mut section = Bytes(section); 208 // NB: this matches the encoding written by `append_to` above. 209 let count = section.read::<U32Bytes<LittleEndian>>().ok()?; 210 let count = usize::try_from(count.get(LittleEndian)).ok()?; 211 let (offsets, traps) = 212 object::slice_from_bytes::<U32Bytes<LittleEndian>>(section.0, count).ok()?; 213 debug_assert_eq!(traps.len(), count); 214 Some((offsets, traps)) 215 } 216 217 /// Returns an iterator over all of the traps encoded in `section`, which should 218 /// have been produced by `TrapEncodingBuilder`. 219 pub fn iterate_traps(section: &[u8]) -> Option<impl Iterator<Item = (u32, Trap)> + '_> { 220 let (offsets, traps) = parse(section)?; 221 Some( 222 offsets 223 .iter() 224 .zip(traps) 225 .map(|(offset, trap)| (offset.get(LittleEndian), Trap::from_u8(*trap).unwrap())), 226 ) 227 } 228