diff --git a/crates/environ/src/address_map.rs b/crates/environ/src/address_map.rs index 7a219543f1..c55250113b 100644 --- a/crates/environ/src/address_map.rs +++ b/crates/environ/src/address_map.rs @@ -116,6 +116,23 @@ impl AddressMapSection { } } +/// Parse an `ELF_WASMTIME_ADDRMAP` section, returning the slice of code offsets +/// and the slice of associated file positions for each offset. +fn parse_address_map( + section: &[u8], +) -> Option<(&[U32Bytes], &[U32Bytes])> { + let mut section = Bytes(section); + // NB: this matches the encoding written by `append_to` above. + let count = section.read::>().ok()?; + let count = usize::try_from(count.get(LittleEndian)).ok()?; + let (offsets, section) = + object::slice_from_bytes::>(section.0, count).ok()?; + let (positions, section) = + object::slice_from_bytes::>(section, count).ok()?; + debug_assert!(section.is_empty()); + Some((offsets, positions)) +} + /// Lookup an `offset` within an encoded address map section, returning the /// original `FilePos` that corresponds to the offset, if found. /// @@ -127,15 +144,7 @@ impl AddressMapSection { /// section of the pc that is being looked up. If `offset` is out of range or /// doesn't correspond to anything in this file then `None` is returned. pub fn lookup_file_pos(section: &[u8], offset: usize) -> Option { - let mut section = Bytes(section); - // NB: this matches the encoding written by `append_to` above. - let count = section.read::>().ok()?; - let count = usize::try_from(count.get(LittleEndian)).ok()?; - let (offsets, section) = - object::slice_from_bytes::>(section.0, count).ok()?; - let (positions, section) = - object::slice_from_bytes::>(section, count).ok()?; - debug_assert!(section.is_empty()); + let (offsets, positions) = parse_address_map(section)?; // First perform a binary search on the `offsets` array. This is a sorted // array of offsets within the text section, which is conveniently what our @@ -160,3 +169,24 @@ pub fn lookup_file_pos(section: &[u8], offset: usize) -> Option { let pos = positions.get(index)?; Some(FilePos(pos.get(LittleEndian))) } + +/// Iterate over the address map contained in the given address map section. +/// +/// This function takes a `section` as its first argument which must have been +/// created with `AddressMapSection` above. This is intended to be the raw +/// `ELF_WASMTIME_ADDRMAP` section from the compilation artifact. +/// +/// The yielded offsets are relative to the start of the text section for this +/// map's code object. +pub fn iterate_address_map<'a>( + section: &'a [u8], +) -> Option + 'a> { + let (offsets, positions) = parse_address_map(section)?; + + Some( + offsets + .iter() + .map(|o| o.get(LittleEndian)) + .zip(positions.iter().map(|pos| FilePos(pos.get(LittleEndian)))), + ) +} diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index d528321956..1da66f547a 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -1042,6 +1042,73 @@ impl Module { self.inner.memory_images()?; Ok(()) } + + /// Get the map from `.text` section offsets to Wasm binary offsets for this + /// module. + /// + /// Each entry is a (`.text` section offset, Wasm binary offset) pair. + /// + /// Entries are yielded in order of `.text` section offset. + /// + /// Some entries are missing a Wasm binary offset. This is for code that is + /// not associated with any single location in the Wasm binary, or for when + /// source information was optimized away. + /// + /// Not every module has an address map, since address map generation can be + /// turned off on `Config`. + /// + /// There is not an entry for every `.text` section offset. Every offset + /// after an entry's offset, but before the next entry's offset, is + /// considered to map to the same Wasm binary offset as the original + /// entry. For example, the address map will not contain the following + /// sequnce of entries: + /// + /// ```ignore + /// [ + /// // ... + /// (10, Some(42)), + /// (11, Some(42)), + /// (12, Some(42)), + /// (13, Some(43)), + /// // ... + /// ] + /// ``` + /// + /// Instead, it will drop the entries for offsets `11` and `12` since they + /// are the same as the entry for offset `10`: + /// + /// ```ignore + /// [ + /// // ... + /// (10, Some(42)), + /// (13, Some(43)), + /// // ... + /// ] + /// ``` + pub fn address_map<'a>(&'a self) -> Option)> + 'a> { + Some( + wasmtime_environ::iterate_address_map( + self.code_object().code_memory().address_map_data(), + )? + .map(|(offset, file_pos)| (offset as usize, file_pos.file_offset())), + ) + } + + /// Get this module's code object's `.text` section, containing its compiled + /// executable code. + pub fn text(&self) -> &[u8] { + self.code_object().code_memory().text() + } + + /// Get the locations of functions in this module's `.text` section. + /// + /// Each function's locartion is a (`.text` section offset, length) pair. + pub fn function_locations<'a>(&'a self) -> impl ExactSizeIterator + 'a { + self.compiled_module().finished_functions().map(|(f, _)| { + let loc = self.compiled_module().func_loc(f); + (loc.start as usize, loc.length as usize) + }) + } } impl ModuleInner {