1 use object::write::{Object, StandardSegment}; 2 use object::{Bytes, LittleEndian, SectionKind, U32Bytes}; 3 use std::convert::TryFrom; 4 use std::ops::Range; 5 6 /// A helper structure to build the custom-encoded section of a wasmtime 7 /// compilation image which encodes trap information. 8 /// 9 /// This structure is incrementally fed the results of compiling individual 10 /// functions and handles all the encoding internally, allowing usage of 11 /// `lookup_trap_code` below with the resulting section. 12 #[derive(Default)] 13 pub struct TrapEncodingBuilder { 14 offsets: Vec<U32Bytes<LittleEndian>>, 15 traps: Vec<u8>, 16 last_offset: u32, 17 } 18 19 /// A custom binary-encoded section of wasmtime compilation artifacts which 20 /// encodes the ability to map an offset in the text section to the trap code 21 /// that it corresponds to. 22 /// 23 /// This section is used at runtime to determine what flavor fo trap happened to 24 /// ensure that embedders and debuggers know the reason for the wasm trap. The 25 /// encoding of this section is custom to Wasmtime and managed with helpers in 26 /// the `object` crate: 27 /// 28 /// * First the section has a 32-bit little endian integer indicating how many 29 /// trap entries are in the section. 30 /// * Next is an array, of the same length as read before, of 32-bit 31 /// little-endian integers. These integers are offsets into the text section 32 /// of the compilation image. 33 /// * Finally is the same count number of bytes. Each of these bytes corresponds 34 /// to a trap code. 35 /// 36 /// This section is decoded by `lookup_trap_code` below which will read the 37 /// section count, slice some bytes to get the various arrays, and then perform 38 /// a binary search on the offsets array to find the an index corresponding to 39 /// the pc being looked up. If found the same index in the trap array (the array 40 /// of bytes) is the trap code for that offset. 41 /// 42 /// Note that at this time this section has an alignment of 1. Additionally due 43 /// to the 32-bit encodings for offsets this doesn't support images >=4gb. 44 pub const ELF_WASMTIME_TRAPS: &str = ".wasmtime.traps"; 45 46 /// Information about trap. 47 #[derive(Debug, PartialEq, Eq, Clone)] 48 pub struct TrapInformation { 49 /// The offset of the trapping instruction in native code. 50 /// 51 /// This is relative to the beginning of the function. 52 pub code_offset: u32, 53 54 /// Code of the trap. 55 pub trap_code: TrapCode, 56 } 57 58 /// A trap code describing the reason for a trap. 59 /// 60 /// All trap instructions have an explicit trap code. 61 #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] 62 #[repr(u8)] 63 pub enum TrapCode { 64 /// The current stack space was exhausted. 65 StackOverflow, 66 67 /// A `heap_addr` instruction detected an out-of-bounds error. 68 /// 69 /// Note that not all out-of-bounds heap accesses are reported this way; 70 /// some are detected by a segmentation fault on the heap unmapped or 71 /// offset-guard pages. 72 HeapOutOfBounds, 73 74 /// A wasm atomic operation was presented with a not-naturally-aligned linear-memory address. 75 HeapMisaligned, 76 77 /// A `table_addr` instruction detected an out-of-bounds error. 78 TableOutOfBounds, 79 80 /// Indirect call to a null table entry. 81 IndirectCallToNull, 82 83 /// Signature mismatch on indirect call. 84 BadSignature, 85 86 /// An integer arithmetic operation caused an overflow. 87 IntegerOverflow, 88 89 /// An integer division by zero. 90 IntegerDivisionByZero, 91 92 /// Failed float-to-int conversion. 93 BadConversionToInteger, 94 95 /// Code that was supposed to have been unreachable was reached. 96 UnreachableCodeReached, 97 98 /// Execution has potentially run too long and may be interrupted. 99 /// This trap is resumable. 100 Interrupt, 101 102 /// Used for the component model when functions are lifted/lowered in a way 103 /// that generates a function that always traps. 104 AlwaysTrapAdapter, 105 // if adding a variant here be sure to update the `check!` macro below 106 } 107 108 impl TrapEncodingBuilder { 109 /// Appends trap information about a function into this section. 110 /// 111 /// This function is called to describe traps for the `func` range 112 /// specified. The `func` offsets are specified relative to the text section 113 /// itself, and the `traps` offsets are specified relative to the start of 114 /// `func`. 115 /// 116 /// This is required to be called in-order for increasing ranges of `func` 117 /// to ensure the final array is properly sorted. Additionally `traps` must 118 /// be sorted. 119 pub fn push(&mut self, func: Range<u64>, traps: &[TrapInformation]) { 120 // NB: for now this only supports <=4GB text sections in object files. 121 // Alternative schemes will need to be created for >32-bit offsets to 122 // avoid making this section overly large. 123 let func_start = u32::try_from(func.start).unwrap(); 124 let func_end = u32::try_from(func.end).unwrap(); 125 126 // Sanity-check to ensure that functions are pushed in-order, otherwise 127 // the `offsets` array won't be sorted which is our goal. 128 assert!(func_start >= self.last_offset); 129 130 self.offsets.reserve(traps.len()); 131 self.traps.reserve(traps.len()); 132 for info in traps { 133 let pos = func_start + info.code_offset; 134 assert!(pos >= self.last_offset); 135 self.offsets.push(U32Bytes::new(LittleEndian, pos)); 136 self.traps.push(info.trap_code as u8); 137 self.last_offset = pos; 138 } 139 140 self.last_offset = func_end; 141 } 142 143 /// Encodes this section into the object provided. 144 pub fn append_to(self, obj: &mut Object) { 145 let section = obj.add_section( 146 obj.segment_name(StandardSegment::Data).to_vec(), 147 ELF_WASMTIME_TRAPS.as_bytes().to_vec(), 148 SectionKind::ReadOnlyData, 149 ); 150 151 // NB: this matches the encoding expected by `lookup` below. 152 let amt = u32::try_from(self.traps.len()).unwrap(); 153 obj.append_section_data(section, &amt.to_le_bytes(), 1); 154 obj.append_section_data(section, object::bytes_of_slice(&self.offsets), 1); 155 obj.append_section_data(section, &self.traps, 1); 156 } 157 } 158 159 /// Decodes the provided trap information section and attempts to find the trap 160 /// code corresponding to the `offset` specified. 161 /// 162 /// The `section` provided is expected to have been built by 163 /// `TrapEncodingBuilder` above. Additionally the `offset` should be a relative 164 /// offset within the text section of the compilation image. 165 pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<TrapCode> { 166 let mut section = Bytes(section); 167 // NB: this matches the encoding written by `append_to` above. 168 let count = section.read::<U32Bytes<LittleEndian>>().ok()?; 169 let count = usize::try_from(count.get(LittleEndian)).ok()?; 170 let (offsets, traps) = 171 object::slice_from_bytes::<U32Bytes<LittleEndian>>(section.0, count).ok()?; 172 debug_assert_eq!(traps.len(), count); 173 174 // The `offsets` table is sorted in the trap section so perform a binary 175 // search of the contents of this section to find whether `offset` is an 176 // entry in the section. Note that this is a precise search because trap pcs 177 // should always be precise as well as our metadata about them, which means 178 // we expect an exact match to correspond to a trap opcode. 179 // 180 // Once an index is found within the `offsets` array then that same index is 181 // used to lookup from the `traps` list of bytes to get the trap code byte 182 // corresponding to this offset. 183 let offset = u32::try_from(offset).ok()?; 184 let index = offsets 185 .binary_search_by_key(&offset, |val| val.get(LittleEndian)) 186 .ok()?; 187 debug_assert!(index < traps.len()); 188 let trap = *traps.get(index)?; 189 190 // FIXME: this could use some sort of derive-like thing to avoid having to 191 // deduplicate the names here. 192 // 193 // This simply converts from the `trap`, a `u8`, to the `TrapCode` enum. 194 macro_rules! check { 195 ($($name:ident)*) => ($(if trap == TrapCode::$name as u8 { 196 return Some(TrapCode::$name); 197 })*); 198 } 199 200 check! { 201 StackOverflow 202 HeapOutOfBounds 203 HeapMisaligned 204 TableOutOfBounds 205 IndirectCallToNull 206 BadSignature 207 IntegerOverflow 208 IntegerDivisionByZero 209 BadConversionToInteger 210 UnreachableCodeReached 211 Interrupt 212 AlwaysTrapAdapter 213 } 214 215 if cfg!(debug_assertions) { 216 panic!("missing mapping for {}", trap); 217 } else { 218 None 219 } 220 } 221