wasmtime_environ/address_map.rs
1//! Data structures to provide transformation of the source
2
3use object::{Bytes, LittleEndian, U32Bytes};
4use serde_derive::{Deserialize, Serialize};
5
6/// Single source location to generated address mapping.
7#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
8pub struct InstructionAddressMap {
9 /// Where in the source wasm binary this instruction comes from, specified
10 /// in an offset of bytes from the front of the file.
11 pub srcloc: FilePos,
12
13 /// Offset from the start of the function's compiled code to where this
14 /// instruction is located, or the region where it starts.
15 pub code_offset: u32,
16}
17
18/// A position within an original source file,
19///
20/// This structure is used as a newtype wrapper around a 32-bit integer which
21/// represents an offset within a file where a wasm instruction or function is
22/// to be originally found.
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
24pub struct FilePos(u32);
25
26impl FilePos {
27 /// Create a new file position with the given offset.
28 pub fn new(pos: u32) -> FilePos {
29 assert!(pos != u32::MAX);
30 FilePos(pos)
31 }
32
33 /// Get the null file position.
34 pub fn none() -> FilePos {
35 FilePos(u32::MAX)
36 }
37
38 /// Is this the null file position?
39 #[inline]
40 pub fn is_none(&self) -> bool {
41 *self == FilePos::none()
42 }
43
44 /// Returns the offset that this offset was created with.
45 ///
46 /// Note that positions created with `FilePos::none` and the `Default`
47 /// implementation will return `None` here, whereas positions created with
48 /// `FilePos::new` will return `Some`.
49 pub fn file_offset(self) -> Option<u32> {
50 if self.0 == u32::MAX {
51 None
52 } else {
53 Some(self.0)
54 }
55 }
56}
57
58impl Default for FilePos {
59 fn default() -> FilePos {
60 FilePos::none()
61 }
62}
63
64/// Parse an `ELF_WASMTIME_ADDRMAP` section, returning the slice of code offsets
65/// and the slice of associated file positions for each offset.
66fn parse_address_map(
67 section: &[u8],
68) -> Option<(&[U32Bytes<LittleEndian>], &[U32Bytes<LittleEndian>])> {
69 let mut section = Bytes(section);
70 // NB: this matches the encoding written by `append_to` in the
71 // `compile::address_map` module.
72 let count = section.read::<U32Bytes<LittleEndian>>().ok()?;
73 let count = usize::try_from(count.get(LittleEndian)).ok()?;
74 let (offsets, section) =
75 object::slice_from_bytes::<U32Bytes<LittleEndian>>(section.0, count).ok()?;
76 let (positions, section) =
77 object::slice_from_bytes::<U32Bytes<LittleEndian>>(section, count).ok()?;
78 debug_assert!(section.is_empty());
79 Some((offsets, positions))
80}
81
82/// Lookup an `offset` within an encoded address map section, returning the
83/// original `FilePos` that corresponds to the offset, if found.
84///
85/// This function takes a `section` as its first argument which must have been
86/// created with `AddressMapSection` above. This is intended to be the raw
87/// `ELF_WASMTIME_ADDRMAP` section from the compilation artifact.
88///
89/// The `offset` provided is a relative offset from the start of the text
90/// section of the pc that is being looked up. If `offset` is out of range or
91/// doesn't correspond to anything in this file then `None` is returned.
92pub fn lookup_file_pos(section: &[u8], offset: usize) -> Option<FilePos> {
93 let (offsets, positions) = parse_address_map(section)?;
94
95 // First perform a binary search on the `offsets` array. This is a sorted
96 // array of offsets within the text section, which is conveniently what our
97 // `offset` also is. Note that we are somewhat unlikely to find a precise
98 // match on the element in the array, so we're largely interested in which
99 // "bucket" the `offset` falls into.
100 let offset = u32::try_from(offset).ok()?;
101 let index = match offsets.binary_search_by_key(&offset, |v| v.get(LittleEndian)) {
102 // Exact hit!
103 Ok(i) => i,
104
105 // This *would* be at the first slot in the array, so no
106 // instructions cover `pc`.
107 Err(0) => return None,
108
109 // This would be at the `nth` slot, so we're at the `n-1`th slot.
110 Err(n) => n - 1,
111 };
112
113 // Using the `index` we found of which bucket `offset` corresponds to we can
114 // lookup the actual `FilePos` value in the `positions` array.
115 let pos = positions.get(index)?;
116 Some(FilePos(pos.get(LittleEndian)))
117}
118
119/// Iterate over the address map contained in the given address map section.
120///
121/// This function takes a `section` as its first argument which must have been
122/// created with `AddressMapSection` above. This is intended to be the raw
123/// `ELF_WASMTIME_ADDRMAP` section from the compilation artifact.
124///
125/// The yielded offsets are relative to the start of the text section for this
126/// map's code object.
127pub fn iterate_address_map<'a>(
128 section: &'a [u8],
129) -> Option<impl Iterator<Item = (u32, FilePos)> + 'a> {
130 let (offsets, positions) = parse_address_map(section)?;
131
132 Some(
133 offsets
134 .iter()
135 .map(|o| o.get(LittleEndian))
136 .zip(positions.iter().map(|pos| FilePos(pos.get(LittleEndian)))),
137 )
138}