* Remove the `Paged` memory initialization variant This commit simplifies the `MemoryInitialization` enum by removing the `Paged` variant. The `Paged` variant was originally added for uffd, but that support has now been removed in #4040. This is no longer necessary but is still used as an intermediate step of becoming a `Static` variant of initialized memory (which copy-on-write uses). As a result this commit largely modifies the static initialization of memory steps and folds the two methods together. * Apply suggestions from code review Co-authored-by: Peter Huene <peter@huene.dev> Co-authored-by: Peter Huene <peter@huene.dev>
751 lines
29 KiB
Rust
751 lines
29 KiB
Rust
//! Define the `instantiate` function, which takes a byte array containing an
|
|
//! encoded wasm module and returns a live wasm instance. Also, define
|
|
//! `CompiledModule` to allow compiling and instantiating to be done as separate
|
|
//! steps.
|
|
|
|
use crate::code_memory::CodeMemory;
|
|
use crate::debug::create_gdbjit_image;
|
|
use crate::ProfilingAgent;
|
|
use anyhow::{anyhow, bail, Context, Error, Result};
|
|
use object::write::{Object, StandardSegment, WritableBuffer};
|
|
use object::{File, Object as _, ObjectSection, SectionKind};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::convert::TryFrom;
|
|
use std::ops::Range;
|
|
use std::str;
|
|
use std::sync::Arc;
|
|
use thiserror::Error;
|
|
use wasmtime_environ::{
|
|
CompileError, DefinedFuncIndex, FuncIndex, FunctionInfo, Module, ModuleTranslation, PrimaryMap,
|
|
SignatureIndex, StackMapInformation, Trampoline, Tunables, ELF_WASMTIME_ADDRMAP,
|
|
ELF_WASMTIME_TRAPS,
|
|
};
|
|
use wasmtime_runtime::{
|
|
CompiledModuleId, CompiledModuleIdAllocator, GdbJitImageRegistration, InstantiationError,
|
|
MmapVec, VMFunctionBody, VMTrampoline,
|
|
};
|
|
|
|
/// This is the name of the section in the final ELF image which contains
|
|
/// concatenated data segments from the original wasm module.
|
|
///
|
|
/// This section is simply a list of bytes and ranges into this section are
|
|
/// stored within a `Module` for each data segment. Memory initialization and
|
|
/// passive segment management all index data directly located in this section.
|
|
///
|
|
/// Note that this implementation does not afford any method of leveraging the
|
|
/// `data.drop` instruction to actually release the data back to the OS. The
|
|
/// data section is simply always present in the ELF image. If we wanted to
|
|
/// release the data it's probably best to figure out what the best
|
|
/// implementation is for it at the time given a particular set of constraints.
|
|
const ELF_WASM_DATA: &'static str = ".rodata.wasm";
|
|
|
|
/// This is the name of the section in the final ELF image which contains a
|
|
/// `bincode`-encoded `CompiledModuleInfo`.
|
|
///
|
|
/// This section is optionally decoded in `CompiledModule::from_artifacts`
|
|
/// depending on whether or not a `CompiledModuleInfo` is already available. In
|
|
/// cases like `Module::new` where compilation directly leads into consumption,
|
|
/// it's available. In cases like `Module::deserialize` this section must be
|
|
/// decoded to get all the relevant information.
|
|
const ELF_WASMTIME_INFO: &'static str = ".wasmtime.info";
|
|
|
|
/// This is the name of the section in the final ELF image which contains a
|
|
/// concatenated list of all function names.
|
|
///
|
|
/// This section is optionally included in the final artifact depending on
|
|
/// whether the wasm module has any name data at all (or in the future if we add
|
|
/// an option to not preserve name data). This section is a concatenated list of
|
|
/// strings where `CompiledModuleInfo::func_names` stores offsets/lengths into
|
|
/// this section.
|
|
///
|
|
/// Note that the goal of this section is to avoid having to decode names at
|
|
/// module-load time if we can. Names are typically only used for debugging or
|
|
/// things like backtraces so there's no need to eagerly load all of them. By
|
|
/// storing the data in a separate section the hope is that the data, which is
|
|
/// sometimes quite large (3MB seen for spidermonkey-compiled-to-wasm), can be
|
|
/// paged in lazily from an mmap and is never paged in if we never reference it.
|
|
const ELF_NAME_DATA: &'static str = ".name.wasm";
|
|
|
|
/// An error condition while setting up a wasm instance, be it validation,
|
|
/// compilation, or instantiation.
|
|
#[derive(Error, Debug)]
|
|
pub enum SetupError {
|
|
/// The module did not pass validation.
|
|
#[error("Validation error: {0}")]
|
|
Validate(String),
|
|
|
|
/// A wasm translation error occurred.
|
|
#[error("WebAssembly failed to compile")]
|
|
Compile(#[from] CompileError),
|
|
|
|
/// Some runtime resource was unavailable or insufficient, or the start function
|
|
/// trapped.
|
|
#[error("Instantiation failed during setup")]
|
|
Instantiate(#[from] InstantiationError),
|
|
|
|
/// Debug information generation error occurred.
|
|
#[error("Debug information error")]
|
|
DebugInfo(#[from] anyhow::Error),
|
|
}
|
|
|
|
/// Secondary in-memory results of compilation.
|
|
///
|
|
/// This opaque structure can be optionally passed back to
|
|
/// `CompiledModule::from_artifacts` to avoid decoding extra information there.
|
|
#[derive(Serialize, Deserialize)]
|
|
pub struct CompiledModuleInfo {
|
|
/// Type information about the compiled WebAssembly module.
|
|
module: Module,
|
|
|
|
/// Metadata about each compiled function.
|
|
funcs: PrimaryMap<DefinedFuncIndex, FunctionInfo>,
|
|
|
|
/// Sorted list, by function index, of names we have for this module.
|
|
func_names: Vec<FunctionName>,
|
|
|
|
/// The trampolines compiled into the text section and their start/length
|
|
/// relative to the start of the text section.
|
|
trampolines: Vec<Trampoline>,
|
|
|
|
/// General compilation metadata.
|
|
meta: Metadata,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct FunctionName {
|
|
idx: FuncIndex,
|
|
offset: u32,
|
|
len: u32,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
struct Metadata {
|
|
/// Whether or not native debug information is available in `obj`
|
|
native_debug_info_present: bool,
|
|
|
|
/// Whether or not the original wasm module contained debug information that
|
|
/// we skipped and did not parse.
|
|
has_unparsed_debuginfo: bool,
|
|
|
|
/// Offset in the original wasm file to the code section.
|
|
code_section_offset: u64,
|
|
|
|
/// Whether or not custom wasm-specific dwarf sections were inserted into
|
|
/// the ELF image.
|
|
///
|
|
/// Note that even if this flag is `true` sections may be missing if they
|
|
/// weren't found in the original wasm module itself.
|
|
has_wasm_debuginfo: bool,
|
|
}
|
|
|
|
/// Finishes compilation of the `translation` specified, producing the final
|
|
/// compilation artifact and auxiliary information.
|
|
///
|
|
/// This function will consume the final results of compiling a wasm module
|
|
/// and finish the ELF image in-progress as part of `obj` by appending any
|
|
/// compiler-agnostic sections.
|
|
///
|
|
/// The auxiliary `CompiledModuleInfo` structure returned here has also been
|
|
/// serialized into the object returned, but if the caller will quickly
|
|
/// turn-around and invoke `CompiledModule::from_artifacts` after this then the
|
|
/// information can be passed to that method to avoid extra deserialization.
|
|
/// This is done to avoid a serialize-then-deserialize for API calls like
|
|
/// `Module::new` where the compiled module is immediately going to be used.
|
|
///
|
|
/// The `MmapVec` returned here contains the compiled image and resides in
|
|
/// mmap'd memory for easily switching permissions to executable afterwards.
|
|
pub fn finish_compile(
|
|
translation: ModuleTranslation<'_>,
|
|
mut obj: Object,
|
|
funcs: PrimaryMap<DefinedFuncIndex, FunctionInfo>,
|
|
trampolines: Vec<Trampoline>,
|
|
tunables: &Tunables,
|
|
) -> Result<(MmapVec, CompiledModuleInfo)> {
|
|
let ModuleTranslation {
|
|
mut module,
|
|
debuginfo,
|
|
has_unparsed_debuginfo,
|
|
data,
|
|
data_align,
|
|
passive_data,
|
|
..
|
|
} = translation;
|
|
|
|
// Place all data from the wasm module into a section which will the
|
|
// source of the data later at runtime.
|
|
let data_id = obj.add_section(
|
|
obj.segment_name(StandardSegment::Data).to_vec(),
|
|
ELF_WASM_DATA.as_bytes().to_vec(),
|
|
SectionKind::ReadOnlyData,
|
|
);
|
|
let mut total_data_len = 0;
|
|
for (i, data) in data.iter().enumerate() {
|
|
// The first data segment has its alignment specified as the alignment
|
|
// for the entire section, but everything afterwards is adjacent so it
|
|
// has alignment of 1.
|
|
let align = if i == 0 { data_align.unwrap_or(1) } else { 1 };
|
|
obj.append_section_data(data_id, data, align);
|
|
total_data_len += data.len();
|
|
}
|
|
for data in passive_data.iter() {
|
|
obj.append_section_data(data_id, data, 1);
|
|
}
|
|
|
|
// If any names are present in the module then the `ELF_NAME_DATA` section
|
|
// is create and appended.
|
|
let mut func_names = Vec::new();
|
|
if debuginfo.name_section.func_names.len() > 0 {
|
|
let name_id = obj.add_section(
|
|
obj.segment_name(StandardSegment::Data).to_vec(),
|
|
ELF_NAME_DATA.as_bytes().to_vec(),
|
|
SectionKind::ReadOnlyData,
|
|
);
|
|
let mut sorted_names = debuginfo.name_section.func_names.iter().collect::<Vec<_>>();
|
|
sorted_names.sort_by_key(|(idx, _name)| *idx);
|
|
for (idx, name) in sorted_names {
|
|
let offset = obj.append_section_data(name_id, name.as_bytes(), 1);
|
|
let offset = match u32::try_from(offset) {
|
|
Ok(offset) => offset,
|
|
Err(_) => bail!("name section too large (> 4gb)"),
|
|
};
|
|
let len = u32::try_from(name.len()).unwrap();
|
|
func_names.push(FunctionName {
|
|
idx: *idx,
|
|
offset,
|
|
len,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Update passive data offsets since they're all located after the other
|
|
// data in the module.
|
|
for (_, range) in module.passive_data_map.iter_mut() {
|
|
range.start = range.start.checked_add(total_data_len as u32).unwrap();
|
|
range.end = range.end.checked_add(total_data_len as u32).unwrap();
|
|
}
|
|
|
|
// Insert the wasm raw wasm-based debuginfo into the output, if
|
|
// requested. Note that this is distinct from the native debuginfo
|
|
// possibly generated by the native compiler, hence these sections
|
|
// getting wasm-specific names.
|
|
if tunables.parse_wasm_debuginfo {
|
|
push_debug(&mut obj, &debuginfo.dwarf.debug_abbrev);
|
|
push_debug(&mut obj, &debuginfo.dwarf.debug_addr);
|
|
push_debug(&mut obj, &debuginfo.dwarf.debug_aranges);
|
|
push_debug(&mut obj, &debuginfo.dwarf.debug_info);
|
|
push_debug(&mut obj, &debuginfo.dwarf.debug_line);
|
|
push_debug(&mut obj, &debuginfo.dwarf.debug_line_str);
|
|
push_debug(&mut obj, &debuginfo.dwarf.debug_str);
|
|
push_debug(&mut obj, &debuginfo.dwarf.debug_str_offsets);
|
|
push_debug(&mut obj, &debuginfo.debug_ranges);
|
|
push_debug(&mut obj, &debuginfo.debug_rnglists);
|
|
}
|
|
|
|
// Encode a `CompiledModuleInfo` structure into the `ELF_WASMTIME_INFO`
|
|
// section of this image. This is not necessary when the returned module
|
|
// is never serialized to disk, which is also why we return a copy of
|
|
// the `CompiledModuleInfo` structure to the caller in case they don't
|
|
// want to deserialize this value immediately afterwards from the
|
|
// section. Otherwise, though, this is necessary to reify a `Module` on
|
|
// the other side from disk-serialized artifacts in
|
|
// `Module::deserialize` (a Wasmtime API).
|
|
let info_id = obj.add_section(
|
|
obj.segment_name(StandardSegment::Data).to_vec(),
|
|
ELF_WASMTIME_INFO.as_bytes().to_vec(),
|
|
SectionKind::ReadOnlyData,
|
|
);
|
|
let mut bytes = Vec::new();
|
|
let info = CompiledModuleInfo {
|
|
module,
|
|
funcs,
|
|
trampolines,
|
|
func_names,
|
|
meta: Metadata {
|
|
native_debug_info_present: tunables.generate_native_debuginfo,
|
|
has_unparsed_debuginfo,
|
|
code_section_offset: debuginfo.wasm_file.code_section_offset,
|
|
has_wasm_debuginfo: tunables.parse_wasm_debuginfo,
|
|
},
|
|
};
|
|
bincode::serialize_into(&mut bytes, &info)?;
|
|
obj.append_section_data(info_id, &bytes, 1);
|
|
|
|
return Ok((mmap_vec_from_obj(obj)?, info));
|
|
|
|
fn push_debug<'a, T>(obj: &mut Object, section: &T)
|
|
where
|
|
T: gimli::Section<gimli::EndianSlice<'a, gimli::LittleEndian>>,
|
|
{
|
|
let data = section.reader().slice();
|
|
if data.is_empty() {
|
|
return;
|
|
}
|
|
let section_id = obj.add_section(
|
|
obj.segment_name(StandardSegment::Debug).to_vec(),
|
|
wasm_section_name(T::id()).as_bytes().to_vec(),
|
|
SectionKind::Debug,
|
|
);
|
|
obj.append_section_data(section_id, data, 1);
|
|
}
|
|
}
|
|
|
|
/// Creates a new `MmapVec` from serializing the specified `obj`.
|
|
///
|
|
/// The returned `MmapVec` will contain the serialized version of `obj` and
|
|
/// is sized appropriately to the exact size of the object serialized.
|
|
pub fn mmap_vec_from_obj(obj: Object) -> Result<MmapVec> {
|
|
let mut result = ObjectMmap::default();
|
|
return match obj.emit(&mut result) {
|
|
Ok(()) => {
|
|
assert!(result.mmap.is_some(), "no reserve");
|
|
let mmap = result.mmap.expect("reserve not called");
|
|
assert_eq!(mmap.len(), result.len);
|
|
Ok(mmap)
|
|
}
|
|
Err(e) => match result.err.take() {
|
|
Some(original) => Err(original.context(e)),
|
|
None => Err(e.into()),
|
|
},
|
|
};
|
|
|
|
/// Helper struct to implement the `WritableBuffer` trait from the `object`
|
|
/// crate.
|
|
///
|
|
/// This enables writing an object directly into an mmap'd memory so it's
|
|
/// immediately usable for execution after compilation. This implementation
|
|
/// relies on a call to `reserve` happening once up front with all the needed
|
|
/// data, and the mmap internally does not attempt to grow afterwards.
|
|
#[derive(Default)]
|
|
struct ObjectMmap {
|
|
mmap: Option<MmapVec>,
|
|
len: usize,
|
|
err: Option<Error>,
|
|
}
|
|
|
|
impl WritableBuffer for ObjectMmap {
|
|
fn len(&self) -> usize {
|
|
self.len
|
|
}
|
|
|
|
fn reserve(&mut self, additional: usize) -> Result<(), ()> {
|
|
assert!(self.mmap.is_none(), "cannot reserve twice");
|
|
self.mmap = match MmapVec::with_capacity(additional) {
|
|
Ok(mmap) => Some(mmap),
|
|
Err(e) => {
|
|
self.err = Some(e);
|
|
return Err(());
|
|
}
|
|
};
|
|
Ok(())
|
|
}
|
|
|
|
fn resize(&mut self, new_len: usize) {
|
|
// Resizing always appends 0 bytes and since new mmaps start out as 0
|
|
// bytes we don't actually need to do anything as part of this other
|
|
// than update our own length.
|
|
if new_len <= self.len {
|
|
return;
|
|
}
|
|
self.len = new_len;
|
|
}
|
|
|
|
fn write_bytes(&mut self, val: &[u8]) {
|
|
let mmap = self.mmap.as_mut().expect("write before reserve");
|
|
mmap[self.len..][..val.len()].copy_from_slice(val);
|
|
self.len += val.len();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A compiled wasm module, ready to be instantiated.
|
|
pub struct CompiledModule {
|
|
wasm_data: Range<usize>,
|
|
address_map_data: Range<usize>,
|
|
trap_data: Range<usize>,
|
|
module: Arc<Module>,
|
|
funcs: PrimaryMap<DefinedFuncIndex, FunctionInfo>,
|
|
trampolines: Vec<Trampoline>,
|
|
meta: Metadata,
|
|
code: Range<usize>,
|
|
code_memory: CodeMemory,
|
|
dbg_jit_registration: Option<GdbJitImageRegistration>,
|
|
/// A unique ID used to register this module with the engine.
|
|
unique_id: CompiledModuleId,
|
|
func_names: Vec<FunctionName>,
|
|
func_name_data: Range<usize>,
|
|
}
|
|
|
|
impl CompiledModule {
|
|
/// Creates `CompiledModule` directly from a precompiled artifact.
|
|
///
|
|
/// The `mmap` argument is expecte to be the result of a previous call to
|
|
/// `finish_compile` above. This is an ELF image, at this time, which
|
|
/// contains all necessary information to create a `CompiledModule` from a
|
|
/// compilation.
|
|
///
|
|
/// This method also takes `info`, an optionally-provided deserialization of
|
|
/// the artifacts' compilation metadata section. If this information is not
|
|
/// provided (e.g. it's set to `None`) then the information will be
|
|
/// deserialized from the image of the compilation artifacts. Otherwise it
|
|
/// will be assumed to be what would otherwise happen if the section were to
|
|
/// be deserialized.
|
|
///
|
|
/// The `profiler` argument here is used to inform JIT profiling runtimes
|
|
/// about new code that is loaded.
|
|
pub fn from_artifacts(
|
|
mmap: MmapVec,
|
|
info: Option<CompiledModuleInfo>,
|
|
profiler: &dyn ProfilingAgent,
|
|
id_allocator: &CompiledModuleIdAllocator,
|
|
) -> Result<Self> {
|
|
// Transfer ownership of `obj` to a `CodeMemory` object which will
|
|
// manage permissions, such as the executable bit. Once it's located
|
|
// there we also publish it for being able to execute. Note that this
|
|
// step will also resolve pending relocations in the compiled image.
|
|
let mut code_memory = CodeMemory::new(mmap);
|
|
let code = code_memory
|
|
.publish()
|
|
.context("failed to publish code memory")?;
|
|
|
|
let section = |name: &str| {
|
|
code.obj
|
|
.section_by_name(name)
|
|
.and_then(|s| s.data().ok())
|
|
.ok_or_else(|| anyhow!("missing section `{}` in compilation artifacts", name))
|
|
};
|
|
|
|
// Acquire the `CompiledModuleInfo`, either because it was passed in or
|
|
// by deserializing it from the compiliation image.
|
|
let info = match info {
|
|
Some(info) => info,
|
|
None => bincode::deserialize(section(ELF_WASMTIME_INFO)?)
|
|
.context("failed to deserialize wasmtime module info")?,
|
|
};
|
|
|
|
let func_name_data = match code
|
|
.obj
|
|
.section_by_name(ELF_NAME_DATA)
|
|
.and_then(|s| s.data().ok())
|
|
{
|
|
Some(data) => subslice_range(data, code.mmap),
|
|
None => 0..0,
|
|
};
|
|
|
|
let mut ret = Self {
|
|
module: Arc::new(info.module),
|
|
funcs: info.funcs,
|
|
trampolines: info.trampolines,
|
|
wasm_data: subslice_range(section(ELF_WASM_DATA)?, code.mmap),
|
|
address_map_data: code
|
|
.obj
|
|
.section_by_name(ELF_WASMTIME_ADDRMAP)
|
|
.and_then(|s| s.data().ok())
|
|
.map(|slice| subslice_range(slice, code.mmap))
|
|
.unwrap_or(0..0),
|
|
trap_data: subslice_range(section(ELF_WASMTIME_TRAPS)?, code.mmap),
|
|
code: subslice_range(code.text, code.mmap),
|
|
dbg_jit_registration: None,
|
|
code_memory,
|
|
meta: info.meta,
|
|
unique_id: id_allocator.alloc(),
|
|
func_names: info.func_names,
|
|
func_name_data,
|
|
};
|
|
ret.register_debug_and_profiling(profiler)?;
|
|
|
|
Ok(ret)
|
|
}
|
|
|
|
fn register_debug_and_profiling(&mut self, profiler: &dyn ProfilingAgent) -> Result<()> {
|
|
// Register GDB JIT images; initialize profiler and load the wasm module.
|
|
if self.meta.native_debug_info_present {
|
|
let code = self.code();
|
|
let bytes = create_gdbjit_image(self.mmap().to_vec(), (code.as_ptr(), code.len()))
|
|
.map_err(SetupError::DebugInfo)?;
|
|
profiler.module_load(self, Some(&bytes));
|
|
let reg = GdbJitImageRegistration::register(bytes);
|
|
self.dbg_jit_registration = Some(reg);
|
|
} else {
|
|
profiler.module_load(self, None);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Get this module's unique ID. It is unique with respect to a
|
|
/// single allocator (which is ordinarily held on a Wasm engine).
|
|
pub fn unique_id(&self) -> CompiledModuleId {
|
|
self.unique_id
|
|
}
|
|
|
|
/// Returns the underlying memory which contains the compiled module's
|
|
/// image.
|
|
pub fn mmap(&self) -> &MmapVec {
|
|
self.code_memory.mmap()
|
|
}
|
|
|
|
/// Returns the concatenated list of all data associated with this wasm
|
|
/// module.
|
|
///
|
|
/// This is used for initialization of memories and all data ranges stored
|
|
/// in a `Module` are relative to the slice returned here.
|
|
pub fn wasm_data(&self) -> &[u8] {
|
|
&self.mmap()[self.wasm_data.clone()]
|
|
}
|
|
|
|
/// Returns the encoded address map section used to pass to
|
|
/// `wasmtime_environ::lookup_file_pos`.
|
|
pub fn address_map_data(&self) -> &[u8] {
|
|
&self.mmap()[self.address_map_data.clone()]
|
|
}
|
|
|
|
/// Returns the encoded trap information for this compiled image.
|
|
///
|
|
/// For more information see `wasmtime_environ::trap_encoding`.
|
|
pub fn trap_data(&self) -> &[u8] {
|
|
&self.mmap()[self.trap_data.clone()]
|
|
}
|
|
|
|
/// Returns the text section of the ELF image for this compiled module.
|
|
///
|
|
/// This memory should have the read/execute permissions.
|
|
pub fn code(&self) -> &[u8] {
|
|
&self.mmap()[self.code.clone()]
|
|
}
|
|
|
|
/// Return a reference-counting pointer to a module.
|
|
pub fn module(&self) -> &Arc<Module> {
|
|
&self.module
|
|
}
|
|
|
|
/// Looks up the `name` section name for the function index `idx`, if one
|
|
/// was specified in the original wasm module.
|
|
pub fn func_name(&self, idx: FuncIndex) -> Option<&str> {
|
|
// Find entry for `idx`, if present.
|
|
let i = self.func_names.binary_search_by_key(&idx, |n| n.idx).ok()?;
|
|
let name = &self.func_names[i];
|
|
|
|
// Here we `unwrap` the `from_utf8` but this can theoretically be a
|
|
// `from_utf8_unchecked` if we really wanted since this section is
|
|
// guaranteed to only have valid utf-8 data. Until it's a problem it's
|
|
// probably best to double-check this though.
|
|
let data = &self.mmap()[self.func_name_data.clone()];
|
|
Some(str::from_utf8(&data[name.offset as usize..][..name.len as usize]).unwrap())
|
|
}
|
|
|
|
/// Return a reference to a mutable module (if possible).
|
|
pub fn module_mut(&mut self) -> Option<&mut Module> {
|
|
Arc::get_mut(&mut self.module)
|
|
}
|
|
|
|
/// Returns the map of all finished JIT functions compiled for this module
|
|
#[inline]
|
|
pub fn finished_functions(
|
|
&self,
|
|
) -> impl ExactSizeIterator<Item = (DefinedFuncIndex, *const [VMFunctionBody])> + '_ {
|
|
let code = self.code();
|
|
self.funcs.iter().map(move |(i, info)| {
|
|
let func = &code[info.start as usize..][..info.length as usize];
|
|
(
|
|
i,
|
|
std::ptr::slice_from_raw_parts(func.as_ptr().cast::<VMFunctionBody>(), func.len()),
|
|
)
|
|
})
|
|
}
|
|
|
|
/// Returns the per-signature trampolines for this module.
|
|
pub fn trampolines(&self) -> impl Iterator<Item = (SignatureIndex, VMTrampoline, usize)> + '_ {
|
|
let code = self.code();
|
|
self.trampolines.iter().map(move |info| {
|
|
(
|
|
info.signature,
|
|
unsafe {
|
|
let ptr = &code[info.start as usize];
|
|
std::mem::transmute::<*const u8, VMTrampoline>(ptr)
|
|
},
|
|
info.length as usize,
|
|
)
|
|
})
|
|
}
|
|
|
|
/// Returns the stack map information for all functions defined in this
|
|
/// module.
|
|
///
|
|
/// The iterator returned iterates over the span of the compiled function in
|
|
/// memory with the stack maps associated with those bytes.
|
|
pub fn stack_maps(
|
|
&self,
|
|
) -> impl Iterator<Item = (*const [VMFunctionBody], &[StackMapInformation])> {
|
|
self.finished_functions()
|
|
.map(|(_, f)| f)
|
|
.zip(self.funcs.values().map(|f| f.stack_maps.as_slice()))
|
|
}
|
|
|
|
/// Lookups a defined function by a program counter value.
|
|
///
|
|
/// Returns the defined function index and the relative address of
|
|
/// `text_offset` within the function itself.
|
|
pub fn func_by_text_offset(&self, text_offset: usize) -> Option<(DefinedFuncIndex, u32)> {
|
|
let text_offset = text_offset as u64;
|
|
|
|
let index = match self
|
|
.funcs
|
|
.binary_search_values_by_key(&text_offset, |info| {
|
|
debug_assert!(info.length > 0);
|
|
// Return the inclusive "end" of the function
|
|
info.start + u64::from(info.length) - 1
|
|
}) {
|
|
Ok(k) => {
|
|
// Exact match, pc is at the end of this function
|
|
k
|
|
}
|
|
Err(k) => {
|
|
// Not an exact match, k is where `pc` would be "inserted"
|
|
// Since we key based on the end, function `k` might contain `pc`,
|
|
// so we'll validate on the range check below
|
|
k
|
|
}
|
|
};
|
|
|
|
let body = self.funcs.get(index)?;
|
|
let start = body.start;
|
|
let end = body.start + u64::from(body.length);
|
|
|
|
if text_offset < start || end < text_offset {
|
|
return None;
|
|
}
|
|
|
|
Some((index, (text_offset - body.start) as u32))
|
|
}
|
|
|
|
/// Gets the function information for a given function index.
|
|
pub fn func_info(&self, index: DefinedFuncIndex) -> &FunctionInfo {
|
|
self.funcs
|
|
.get(index)
|
|
.expect("defined function should be present")
|
|
}
|
|
|
|
/// Creates a new symbolication context which can be used to further
|
|
/// symbolicate stack traces.
|
|
///
|
|
/// Basically this makes a thing which parses debuginfo and can tell you
|
|
/// what filename and line number a wasm pc comes from.
|
|
pub fn symbolize_context(&self) -> Result<Option<SymbolizeContext<'_>>> {
|
|
use gimli::EndianSlice;
|
|
if !self.meta.has_wasm_debuginfo {
|
|
return Ok(None);
|
|
}
|
|
let obj = File::parse(&self.mmap()[..])
|
|
.context("failed to parse internal ELF file representation")?;
|
|
let dwarf = gimli::Dwarf::load(|id| -> Result<_> {
|
|
let data = obj
|
|
.section_by_name(wasm_section_name(id))
|
|
.and_then(|s| s.data().ok())
|
|
.unwrap_or(&[]);
|
|
Ok(EndianSlice::new(data, gimli::LittleEndian))
|
|
})?;
|
|
let cx = addr2line::Context::from_dwarf(dwarf)
|
|
.context("failed to create addr2line dwarf mapping context")?;
|
|
Ok(Some(SymbolizeContext {
|
|
inner: cx,
|
|
code_section_offset: self.meta.code_section_offset,
|
|
}))
|
|
}
|
|
|
|
/// Returns whether the original wasm module had unparsed debug information
|
|
/// based on the tunables configuration.
|
|
pub fn has_unparsed_debuginfo(&self) -> bool {
|
|
self.meta.has_unparsed_debuginfo
|
|
}
|
|
|
|
/// Indicates whether this module came with n address map such that lookups
|
|
/// via `wasmtime_environ::lookup_file_pos` will succeed.
|
|
///
|
|
/// If this function returns `false` then `lookup_file_pos` will always
|
|
/// return `None`.
|
|
pub fn has_address_map(&self) -> bool {
|
|
!self.address_map_data().is_empty()
|
|
}
|
|
|
|
/// Returns the bounds, in host memory, of where this module's compiled
|
|
/// image resides.
|
|
pub fn image_range(&self) -> Range<usize> {
|
|
let base = self.mmap().as_ptr() as usize;
|
|
let len = self.mmap().len();
|
|
base..base + len
|
|
}
|
|
}
|
|
|
|
type Addr2LineContext<'a> = addr2line::Context<gimli::EndianSlice<'a, gimli::LittleEndian>>;
|
|
|
|
/// A context which contains dwarf debug information to translate program
|
|
/// counters back to filenames and line numbers.
|
|
pub struct SymbolizeContext<'a> {
|
|
inner: Addr2LineContext<'a>,
|
|
code_section_offset: u64,
|
|
}
|
|
|
|
impl<'a> SymbolizeContext<'a> {
|
|
/// Returns access to the [`addr2line::Context`] which can be used to query
|
|
/// frame information with.
|
|
pub fn addr2line(&self) -> &Addr2LineContext<'a> {
|
|
&self.inner
|
|
}
|
|
|
|
/// Returns the offset of the code section in the original wasm file, used
|
|
/// to calculate lookup values into the DWARF.
|
|
pub fn code_section_offset(&self) -> u64 {
|
|
self.code_section_offset
|
|
}
|
|
}
|
|
|
|
/// Returns the range of `inner` within `outer`, such that `outer[range]` is the
|
|
/// same as `inner`.
|
|
///
|
|
/// This method requires that `inner` is a sub-slice of `outer`, and if that
|
|
/// isn't true then this method will panic.
|
|
pub fn subslice_range(inner: &[u8], outer: &[u8]) -> Range<usize> {
|
|
if inner.len() == 0 {
|
|
return 0..0;
|
|
}
|
|
|
|
assert!(outer.as_ptr() <= inner.as_ptr());
|
|
assert!((&inner[inner.len() - 1] as *const _) <= (&outer[outer.len() - 1] as *const _));
|
|
|
|
let start = inner.as_ptr() as usize - outer.as_ptr() as usize;
|
|
start..start + inner.len()
|
|
}
|
|
|
|
/// Returns the Wasmtime-specific section name for dwarf debugging sections.
|
|
///
|
|
/// These sections, if configured in Wasmtime, will contain the original raw
|
|
/// dwarf debugging information found in the wasm file, unmodified. These tables
|
|
/// are then consulted later to convert wasm program counters to original wasm
|
|
/// source filenames/line numbers with `addr2line`.
|
|
fn wasm_section_name(id: gimli::SectionId) -> &'static str {
|
|
use gimli::SectionId::*;
|
|
match id {
|
|
DebugAbbrev => ".debug_abbrev.wasm",
|
|
DebugAddr => ".debug_addr.wasm",
|
|
DebugAranges => ".debug_aranges.wasm",
|
|
DebugFrame => ".debug_frame.wasm",
|
|
EhFrame => ".eh_frame.wasm",
|
|
EhFrameHdr => ".eh_frame_hdr.wasm",
|
|
DebugInfo => ".debug_info.wasm",
|
|
DebugLine => ".debug_line.wasm",
|
|
DebugLineStr => ".debug_line_str.wasm",
|
|
DebugLoc => ".debug_loc.wasm",
|
|
DebugLocLists => ".debug_loc_lists.wasm",
|
|
DebugMacinfo => ".debug_macinfo.wasm",
|
|
DebugMacro => ".debug_macro.wasm",
|
|
DebugPubNames => ".debug_pub_names.wasm",
|
|
DebugPubTypes => ".debug_pub_types.wasm",
|
|
DebugRanges => ".debug_ranges.wasm",
|
|
DebugRngLists => ".debug_rng_lists.wasm",
|
|
DebugStr => ".debug_str.wasm",
|
|
DebugStrOffsets => ".debug_str_offsets.wasm",
|
|
DebugTypes => ".debug_types.wasm",
|
|
DebugCuIndex => ".debug_cu_index.wasm",
|
|
DebugTuIndex => ".debug_tu_index.wasm",
|
|
}
|
|
}
|