use crate::code_memory::CodeMemory;
use crate::debug::create_gdbjit_image;
use crate::{MmapVec, ProfilingAgent};
use anyhow::{anyhow, Context, Result};
use object::write::{Object, StandardSegment};
use object::{File, Object as _, ObjectSection, SectionKind};
use serde::{Deserialize, Serialize};
use std::ops::Range;
use std::sync::Arc;
use thiserror::Error;
use wasmtime_environ::{
CompileError, DefinedFuncIndex, FunctionInfo, InstanceSignature, InstanceTypeIndex, Module,
ModuleSignature, ModuleTranslation, ModuleTypeIndex, PrimaryMap, SignatureIndex,
StackMapInformation, Trampoline, Tunables, WasmFuncType, ELF_WASMTIME_ADDRMAP,
ELF_WASMTIME_TRAPS,
};
use wasmtime_runtime::{GdbJitImageRegistration, InstantiationError, VMFunctionBody, VMTrampoline};
const ELF_WASM_DATA: &'static str = ".rodata.wasm";
const ELF_WASMTIME_INFO: &'static str = ".wasmtime.info";
#[derive(Error, Debug)]
pub enum SetupError {
#[error("Validation error: {0}")]
Validate(String),
#[error("WebAssembly failed to compile")]
Compile(#[from] CompileError),
#[error("Instantiation failed during setup")]
Instantiate(#[from] InstantiationError),
#[error("Debug information error")]
DebugInfo(#[from] anyhow::Error),
}
#[derive(Serialize, Deserialize)]
pub struct CompiledModuleInfo {
module: Module,
funcs: PrimaryMap<DefinedFuncIndex, FunctionInfo>,
trampolines: Vec<Trampoline>,
meta: Metadata,
}
#[derive(Serialize, Deserialize)]
struct Metadata {
native_debug_info_present: bool,
has_unparsed_debuginfo: bool,
code_section_offset: u64,
has_wasm_debuginfo: bool,
}
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,
passive_data,
..
} = translation;
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 data in data.iter() {
obj.append_section_data(data_id, data, 1);
total_data_len += data.len();
}
for data in passive_data.iter() {
obj.append_section_data(data_id, data, 1);
}
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();
}
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);
}
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,
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((MmapVec::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);
}
}
#[derive(Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct TypeTables {
pub wasm_signatures: PrimaryMap<SignatureIndex, WasmFuncType>,
pub module_signatures: PrimaryMap<ModuleTypeIndex, ModuleSignature>,
pub instance_signatures: PrimaryMap<InstanceTypeIndex, InstanceSignature>,
}
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>,
}
impl CompiledModule {
pub fn from_artifacts(
mmap: MmapVec,
info: Option<CompiledModuleInfo>,
profiler: &dyn ProfilingAgent,
) -> Result<Arc<Self>> {
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))
};
let info = match info {
Some(info) => info,
None => bincode::deserialize(section(ELF_WASMTIME_INFO)?)
.context("failed to deserialize wasmtime module info")?,
};
let mut ret = Self {
meta: info.meta,
module: Arc::new(info.module),
funcs: info.funcs,
trampolines: info.trampolines,
wasm_data: subslice_range(section(ELF_WASM_DATA)?, code.mmap),
address_map_data: subslice_range(section(ELF_WASMTIME_ADDRMAP)?, code.mmap),
trap_data: subslice_range(section(ELF_WASMTIME_TRAPS)?, code.mmap),
code: subslice_range(code.text, code.mmap),
dbg_jit_registration: None,
code_memory,
};
ret.register_debug_and_profiling(profiler)?;
Ok(Arc::new(ret))
}
fn register_debug_and_profiling(&mut self, profiler: &dyn ProfilingAgent) -> Result<()> {
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(())
}
pub fn mmap(&self) -> &MmapVec {
self.code_memory.mmap()
}
pub fn wasm_data(&self) -> &[u8] {
&self.mmap()[self.wasm_data.clone()]
}
pub fn address_map_data(&self) -> &[u8] {
&self.mmap()[self.address_map_data.clone()]
}
pub fn trap_data(&self) -> &[u8] {
&self.mmap()[self.trap_data.clone()]
}
pub fn code(&self) -> &[u8] {
&self.mmap()[self.code.clone()]
}
pub fn module(&self) -> &Arc<Module> {
&self.module
}
pub fn functions(&self) -> &PrimaryMap<DefinedFuncIndex, FunctionInfo> {
&self.funcs
}
pub fn module_mut(&mut self) -> Option<&mut Module> {
Arc::get_mut(&mut self.module)
}
#[inline]
pub fn finished_functions(
&self,
) -> impl ExactSizeIterator<Item = (DefinedFuncIndex, *mut [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_mut(
func.as_ptr() as *mut VMFunctionBody,
func.len(),
),
)
})
}
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,
)
})
}
pub fn stack_maps(
&self,
) -> impl Iterator<Item = (*mut [VMFunctionBody], &[StackMapInformation])> {
self.finished_functions()
.map(|(_, f)| f)
.zip(self.funcs.values().map(|f| f.stack_maps.as_slice()))
}
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);
info.start + u64::from(info.length) - 1
}) {
Ok(k) => {
k
}
Err(k) => {
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))
}
pub fn func_info(&self, index: DefinedFuncIndex) -> &FunctionInfo {
self.funcs
.get(index)
.expect("defined function should be present")
}
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,
}))
}
pub fn has_unparsed_debuginfo(&self) -> bool {
self.meta.has_unparsed_debuginfo
}
}
type Addr2LineContext<'a> = addr2line::Context<gimli::EndianSlice<'a, gimli::LittleEndian>>;
pub struct SymbolizeContext<'a> {
inner: Addr2LineContext<'a>,
code_section_offset: u64,
}
impl<'a> SymbolizeContext<'a> {
pub fn addr2line(&self) -> &Addr2LineContext<'a> {
&self.inner
}
pub fn code_section_offset(&self) -> u64 {
self.code_section_offset
}
}
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()
}
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",
}
}