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,
};
const ELF_WASM_DATA: &'static str = ".rodata.wasm";
const ELF_WASMTIME_INFO: &'static str = ".wasmtime.info";
const ELF_NAME_DATA: &'static str = ".name.wasm";
#[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>,
func_names: Vec<FunctionName>,
trampolines: Vec<Trampoline>,
meta: Metadata,
}
#[derive(Serialize, Deserialize)]
struct FunctionName {
idx: FuncIndex,
offset: u32,
len: u32,
}
#[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,
data_align,
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 (i, data) in data.iter().enumerate() {
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);
}
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,
});
}
}
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,
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);
}
}
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()),
},
};
#[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) {
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();
}
}
}
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>,
unique_id: CompiledModuleId,
func_names: Vec<FunctionName>,
func_name_data: Range<usize>,
}
impl CompiledModule {
pub fn from_artifacts(
mmap: MmapVec,
info: Option<CompiledModuleInfo>,
profiler: &dyn ProfilingAgent,
id_allocator: &CompiledModuleIdAllocator,
) -> Result<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 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<()> {
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 unique_id(&self) -> CompiledModuleId {
self.unique_id
}
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 func_name(&self, idx: FuncIndex) -> Option<&str> {
let i = self.func_names.binary_search_by_key(&idx, |n| n.idx).ok()?;
let name = &self.func_names[i];
let data = &self.mmap()[self.func_name_data.clone()];
Some(str::from_utf8(&data[name.offset as usize..][..name.len as usize]).unwrap())
}
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, *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()),
)
})
}
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 = (*const [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
}
pub fn has_address_map(&self) -> bool {
!self.address_map_data().is_empty()
}
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>>;
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",
DebugCuIndex => ".debug_cu_index.wasm",
DebugTuIndex => ".debug_tu_index.wasm",
}
}