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