1 use object::{Bytes, LittleEndian, U32Bytes}; 2 use std::fmt; 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 #[allow(missing_docs)] 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 get the bits of a null `i31ref`. 78 NullI31Ref, 79 80 /// When the `component-model` feature is enabled this trap represents a 81 /// scenario where one component tried to call another component but it 82 /// would have violated the reentrance rules of the component model, 83 /// triggering a trap instead. 84 CannotEnterComponent, 85 // if adding a variant here be sure to update the `check!` macro below 86 } 87 88 impl Trap { 89 /// Converts a byte back into a `Trap` if its in-bounds 90 pub fn from_u8(byte: u8) -> Option<Trap> { 91 // FIXME: this could use some sort of derive-like thing to avoid having to 92 // deduplicate the names here. 93 // 94 // This simply converts from the a `u8`, to the `Trap` enum. 95 macro_rules! check { 96 ($($name:ident)*) => ($(if byte == Trap::$name as u8 { 97 return Some(Trap::$name); 98 })*); 99 } 100 101 check! { 102 StackOverflow 103 MemoryOutOfBounds 104 HeapMisaligned 105 TableOutOfBounds 106 IndirectCallToNull 107 BadSignature 108 IntegerOverflow 109 IntegerDivisionByZero 110 BadConversionToInteger 111 UnreachableCodeReached 112 Interrupt 113 AlwaysTrapAdapter 114 OutOfFuel 115 AtomicWaitNonSharedMemory 116 NullReference 117 NullI31Ref 118 CannotEnterComponent 119 } 120 121 None 122 } 123 } 124 125 impl fmt::Display for Trap { 126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 127 use Trap::*; 128 129 let desc = match self { 130 StackOverflow => "call stack exhausted", 131 MemoryOutOfBounds => "out of bounds memory access", 132 HeapMisaligned => "unaligned atomic", 133 TableOutOfBounds => "undefined element: out of bounds table access", 134 IndirectCallToNull => "uninitialized element", 135 BadSignature => "indirect call type mismatch", 136 IntegerOverflow => "integer overflow", 137 IntegerDivisionByZero => "integer divide by zero", 138 BadConversionToInteger => "invalid conversion to integer", 139 UnreachableCodeReached => "wasm `unreachable` instruction executed", 140 Interrupt => "interrupt", 141 AlwaysTrapAdapter => "degenerate component adapter called", 142 OutOfFuel => "all fuel consumed by WebAssembly", 143 AtomicWaitNonSharedMemory => "atomic wait on non-shared memory", 144 NullReference => "null reference", 145 NullI31Ref => "null i31 reference", 146 CannotEnterComponent => "cannot enter component instance", 147 }; 148 write!(f, "wasm trap: {desc}") 149 } 150 } 151 152 impl std::error::Error for Trap {} 153 154 /// Decodes the provided trap information section and attempts to find the trap 155 /// code corresponding to the `offset` specified. 156 /// 157 /// The `section` provided is expected to have been built by 158 /// `TrapEncodingBuilder` above. Additionally the `offset` should be a relative 159 /// offset within the text section of the compilation image. 160 pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> { 161 let mut section = Bytes(section); 162 // NB: this matches the encoding written by `append_to` above. 163 let count = section.read::<U32Bytes<LittleEndian>>().ok()?; 164 let count = usize::try_from(count.get(LittleEndian)).ok()?; 165 let (offsets, traps) = 166 object::slice_from_bytes::<U32Bytes<LittleEndian>>(section.0, count).ok()?; 167 debug_assert_eq!(traps.len(), count); 168 169 // The `offsets` table is sorted in the trap section so perform a binary 170 // search of the contents of this section to find whether `offset` is an 171 // entry in the section. Note that this is a precise search because trap pcs 172 // should always be precise as well as our metadata about them, which means 173 // we expect an exact match to correspond to a trap opcode. 174 // 175 // Once an index is found within the `offsets` array then that same index is 176 // used to lookup from the `traps` list of bytes to get the trap code byte 177 // corresponding to this offset. 178 let offset = u32::try_from(offset).ok()?; 179 let index = offsets 180 .binary_search_by_key(&offset, |val| val.get(LittleEndian)) 181 .ok()?; 182 debug_assert!(index < traps.len()); 183 let byte = *traps.get(index)?; 184 185 let trap = Trap::from_u8(byte); 186 debug_assert!(trap.is_some(), "missing mapping for {}", byte); 187 trap 188 } 189