wasmtime_environ/
trap_encoding.rs

1use crate::obj::ELF_WASMTIME_TRAPS;
2use object::write::{Object, StandardSegment};
3use object::{Bytes, LittleEndian, SectionKind, U32Bytes};
4use std::convert::TryFrom;
5use std::fmt;
6use std::ops::Range;
7
8/// A helper structure to build the custom-encoded section of a wasmtime
9/// compilation image which encodes trap information.
10///
11/// This structure is incrementally fed the results of compiling individual
12/// functions and handles all the encoding internally, allowing usage of
13/// `lookup_trap_code` below with the resulting section.
14#[derive(Default)]
15pub struct TrapEncodingBuilder {
16    offsets: Vec<U32Bytes<LittleEndian>>,
17    traps: Vec<u8>,
18    last_offset: u32,
19}
20
21/// Information about trap.
22#[derive(Debug, PartialEq, Eq, Clone)]
23pub struct TrapInformation {
24    /// The offset of the trapping instruction in native code.
25    ///
26    /// This is relative to the beginning of the function.
27    pub code_offset: u32,
28
29    /// Code of the trap.
30    pub trap_code: Trap,
31}
32
33// The code can be accessed from the c-api, where the possible values are
34// translated into enum values defined there:
35//
36// * `wasm_trap_code` in c-api/src/trap.rs, and
37// * `wasmtime_trap_code_enum` in c-api/include/wasmtime/trap.h.
38//
39// These need to be kept in sync.
40#[non_exhaustive]
41#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
42#[allow(missing_docs)]
43pub enum Trap {
44    /// The current stack space was exhausted.
45    StackOverflow,
46
47    /// An out-of-bounds memory access.
48    MemoryOutOfBounds,
49
50    /// A wasm atomic operation was presented with a not-naturally-aligned linear-memory address.
51    HeapMisaligned,
52
53    /// An out-of-bounds access to a table.
54    TableOutOfBounds,
55
56    /// Indirect call to a null table entry.
57    IndirectCallToNull,
58
59    /// Signature mismatch on indirect call.
60    BadSignature,
61
62    /// An integer arithmetic operation caused an overflow.
63    IntegerOverflow,
64
65    /// An integer division by zero.
66    IntegerDivisionByZero,
67
68    /// Failed float-to-int conversion.
69    BadConversionToInteger,
70
71    /// Code that was supposed to have been unreachable was reached.
72    UnreachableCodeReached,
73
74    /// Execution has potentially run too long and may be interrupted.
75    Interrupt,
76
77    /// When the `component-model` feature is enabled this trap represents a
78    /// function that was `canon lift`'d, then `canon lower`'d, then called.
79    /// This combination of creation of a function in the component model
80    /// generates a function that always traps and, when called, produces this
81    /// flavor of trap.
82    AlwaysTrapAdapter,
83
84    /// When wasm code is configured to consume fuel and it runs out of fuel
85    /// then this trap will be raised.
86    OutOfFuel,
87
88    /// Used to indicate that a trap was raised by atomic wait operations on non shared memory.
89    AtomicWaitNonSharedMemory,
90    // if adding a variant here be sure to update the `check!` macro below
91}
92
93impl fmt::Display for Trap {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        use Trap::*;
96
97        let desc = match self {
98            StackOverflow => "call stack exhausted",
99            MemoryOutOfBounds => "out of bounds memory access",
100            HeapMisaligned => "unaligned atomic",
101            TableOutOfBounds => "undefined element: out of bounds table access",
102            IndirectCallToNull => "uninitialized element",
103            BadSignature => "indirect call type mismatch",
104            IntegerOverflow => "integer overflow",
105            IntegerDivisionByZero => "integer divide by zero",
106            BadConversionToInteger => "invalid conversion to integer",
107            UnreachableCodeReached => "wasm `unreachable` instruction executed",
108            Interrupt => "interrupt",
109            AlwaysTrapAdapter => "degenerate component adapter called",
110            OutOfFuel => "all fuel consumed by WebAssembly",
111            AtomicWaitNonSharedMemory => "atomic wait on non-shared memory",
112        };
113        write!(f, "wasm trap: {desc}")
114    }
115}
116
117impl std::error::Error for Trap {}
118
119impl TrapEncodingBuilder {
120    /// Appends trap information about a function into this section.
121    ///
122    /// This function is called to describe traps for the `func` range
123    /// specified. The `func` offsets are specified relative to the text section
124    /// itself, and the `traps` offsets are specified relative to the start of
125    /// `func`.
126    ///
127    /// This is required to be called in-order for increasing ranges of `func`
128    /// to ensure the final array is properly sorted. Additionally `traps` must
129    /// be sorted.
130    pub fn push(&mut self, func: Range<u64>, traps: &[TrapInformation]) {
131        // NB: for now this only supports <=4GB text sections in object files.
132        // Alternative schemes will need to be created for >32-bit offsets to
133        // avoid making this section overly large.
134        let func_start = u32::try_from(func.start).unwrap();
135        let func_end = u32::try_from(func.end).unwrap();
136
137        // Sanity-check to ensure that functions are pushed in-order, otherwise
138        // the `offsets` array won't be sorted which is our goal.
139        assert!(func_start >= self.last_offset);
140
141        self.offsets.reserve(traps.len());
142        self.traps.reserve(traps.len());
143        for info in traps {
144            let pos = func_start + info.code_offset;
145            assert!(pos >= self.last_offset);
146            self.offsets.push(U32Bytes::new(LittleEndian, pos));
147            self.traps.push(info.trap_code as u8);
148            self.last_offset = pos;
149        }
150
151        self.last_offset = func_end;
152    }
153
154    /// Encodes this section into the object provided.
155    pub fn append_to(self, obj: &mut Object) {
156        let section = obj.add_section(
157            obj.segment_name(StandardSegment::Data).to_vec(),
158            ELF_WASMTIME_TRAPS.as_bytes().to_vec(),
159            SectionKind::ReadOnlyData,
160        );
161
162        // NB: this matches the encoding expected by `lookup` below.
163        let amt = u32::try_from(self.traps.len()).unwrap();
164        obj.append_section_data(section, &amt.to_le_bytes(), 1);
165        obj.append_section_data(section, object::bytes_of_slice(&self.offsets), 1);
166        obj.append_section_data(section, &self.traps, 1);
167    }
168}
169
170/// Decodes the provided trap information section and attempts to find the trap
171/// code corresponding to the `offset` specified.
172///
173/// The `section` provided is expected to have been built by
174/// `TrapEncodingBuilder` above. Additionally the `offset` should be a relative
175/// offset within the text section of the compilation image.
176pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> {
177    let mut section = Bytes(section);
178    // NB: this matches the encoding written by `append_to` above.
179    let count = section.read::<U32Bytes<LittleEndian>>().ok()?;
180    let count = usize::try_from(count.get(LittleEndian)).ok()?;
181    let (offsets, traps) =
182        object::slice_from_bytes::<U32Bytes<LittleEndian>>(section.0, count).ok()?;
183    debug_assert_eq!(traps.len(), count);
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 trap = *traps.get(index)?;
200
201    // FIXME: this could use some sort of derive-like thing to avoid having to
202    // deduplicate the names here.
203    //
204    // This simply converts from the `trap`, a `u8`, to the `Trap` enum.
205    macro_rules! check {
206        ($($name:ident)*) => ($(if trap == Trap::$name as u8 {
207            return Some(Trap::$name);
208        })*);
209    }
210
211    check! {
212        StackOverflow
213        MemoryOutOfBounds
214        HeapMisaligned
215        TableOutOfBounds
216        IndirectCallToNull
217        BadSignature
218        IntegerOverflow
219        IntegerDivisionByZero
220        BadConversionToInteger
221        UnreachableCodeReached
222        Interrupt
223        AlwaysTrapAdapter
224        OutOfFuel
225        AtomicWaitNonSharedMemory
226    }
227
228    if cfg!(debug_assertions) {
229        panic!("missing mapping for {}", trap);
230    } else {
231        None
232    }
233}