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