use crate::{signatures::SignatureCollection, Module};
use std::{
collections::BTreeMap,
sync::{Arc, RwLock},
};
use wasmtime_environ::{EntityRef, FilePos, StackMap, TrapCode};
use wasmtime_jit::CompiledModule;
use wasmtime_runtime::{ModuleInfo, VMCallerCheckedAnyfunc, VMTrampoline};
lazy_static::lazy_static! {
static ref GLOBAL_MODULES: RwLock<GlobalModuleRegistry> = Default::default();
}
#[derive(Default)]
pub struct ModuleRegistry {
modules_with_code: BTreeMap<usize, Arc<RegisteredModule>>,
modules_without_code: Vec<Arc<CompiledModule>>,
}
impl ModuleRegistry {
pub fn lookup_module(&self, pc: usize) -> Option<Arc<dyn ModuleInfo>> {
self.module(pc)
.map(|m| -> Arc<dyn ModuleInfo> { m.clone() })
}
fn module(&self, pc: usize) -> Option<&Arc<RegisteredModule>> {
let (end, info) = self.modules_with_code.range(pc..).next()?;
if pc < info.start || *end < pc {
return None;
}
Some(info)
}
pub fn register(&mut self, module: &Module) {
let compiled_module = module.compiled_module();
if compiled_module.finished_functions().len() == 0 {
self.modules_without_code.push(compiled_module.clone());
return;
}
let code = compiled_module.code();
assert!(!code.is_empty());
let start = code.as_ptr() as usize;
let end = start + code.len() - 1;
if let Some(m) = self.modules_with_code.get(&end) {
assert_eq!(m.start, start);
return;
}
if let Some((_, prev)) = self.modules_with_code.range(end..).next() {
assert!(prev.start > end);
}
if let Some((prev_end, _)) = self.modules_with_code.range(..=start).next_back() {
assert!(*prev_end < start);
}
let prev = self.modules_with_code.insert(
end,
Arc::new(RegisteredModule {
start,
module: compiled_module.clone(),
signatures: module.signatures().clone(),
}),
);
assert!(prev.is_none());
GLOBAL_MODULES.write().unwrap().register(start, end, module);
}
pub fn lookup_trampoline(&self, anyfunc: &VMCallerCheckedAnyfunc) -> Option<VMTrampoline> {
let module = self.module(anyfunc.func_ptr.as_ptr() as usize)?;
module.signatures.trampoline(anyfunc.type_index)
}
}
impl Drop for ModuleRegistry {
fn drop(&mut self) {
let mut info = GLOBAL_MODULES.write().unwrap();
for end in self.modules_with_code.keys() {
info.unregister(*end);
}
}
}
struct RegisteredModule {
start: usize,
module: Arc<CompiledModule>,
signatures: Arc<SignatureCollection>,
}
impl ModuleInfo for RegisteredModule {
fn lookup_stack_map(&self, pc: usize) -> Option<&StackMap> {
let text_offset = pc - self.start;
let (index, func_offset) = self.module.func_by_text_offset(text_offset)?;
let info = self.module.func_info(index);
let index = match info
.stack_maps
.binary_search_by_key(&func_offset, |i| i.code_offset)
{
Ok(i) => i,
Err(_) => return None,
};
Some(&info.stack_maps[index].stack_map)
}
}
struct GlobalRegisteredModule {
start: usize,
module: Arc<CompiledModule>,
wasm_backtrace_details_env_used: bool,
references: usize,
}
#[derive(Default)]
pub struct GlobalModuleRegistry(BTreeMap<usize, GlobalRegisteredModule>);
impl GlobalModuleRegistry {
pub(crate) fn is_wasm_trap_pc(pc: usize) -> bool {
let modules = GLOBAL_MODULES.read().unwrap();
match modules.module(pc) {
Some((entry, text_offset)) => {
wasmtime_environ::lookup_trap_code(entry.module.trap_data(), text_offset).is_some()
}
None => false,
}
}
fn module(&self, pc: usize) -> Option<(&GlobalRegisteredModule, usize)> {
let (end, info) = self.0.range(pc..).next()?;
if pc < info.start || *end < pc {
return None;
}
Some((info, pc - info.start))
}
pub(crate) fn with<R>(f: impl FnOnce(&GlobalModuleRegistry) -> R) -> R {
f(&GLOBAL_MODULES.read().unwrap())
}
pub(crate) fn lookup_frame_info(&self, pc: usize) -> Option<(FrameInfo, bool, bool)> {
let (module, offset) = self.module(pc)?;
module.lookup_frame_info(offset).map(|info| {
(
info,
module.has_unparsed_debuginfo(),
module.wasm_backtrace_details_env_used,
)
})
}
pub(crate) fn lookup_trap_code(&self, pc: usize) -> Option<TrapCode> {
let (module, offset) = self.module(pc)?;
wasmtime_environ::lookup_trap_code(module.module.trap_data(), offset)
}
fn register(&mut self, start: usize, end: usize, module: &Module) {
let info = self.0.entry(end).or_insert_with(|| GlobalRegisteredModule {
start,
module: module.compiled_module().clone(),
wasm_backtrace_details_env_used: module
.engine()
.config()
.wasm_backtrace_details_env_used,
references: 0,
});
assert_eq!(info.start, start);
info.references += 1;
}
fn unregister(&mut self, end: usize) {
let info = self.0.get_mut(&end).unwrap();
info.references -= 1;
if info.references == 0 {
self.0.remove(&end);
}
}
}
impl GlobalRegisteredModule {
pub fn has_unparsed_debuginfo(&self) -> bool {
self.module.has_unparsed_debuginfo()
}
pub fn lookup_frame_info(&self, text_offset: usize) -> Option<FrameInfo> {
let (index, _func_offset) = self.module.func_by_text_offset(text_offset)?;
let info = self.module.func_info(index);
let instr = wasmtime_environ::lookup_file_pos(self.module.address_map_data(), text_offset);
debug_assert!(
instr.is_some() || !self.module.has_address_map(),
"failed to find instruction for {:#x}",
text_offset
);
let mut symbols = Vec::new();
if let Some(s) = &self.module.symbolize_context().ok().and_then(|c| c) {
if let Some(offset) = instr.and_then(|i| i.file_offset()) {
let to_lookup = u64::from(offset) - s.code_section_offset();
if let Ok(mut frames) = s.addr2line().find_frames(to_lookup) {
while let Ok(Some(frame)) = frames.next() {
symbols.push(FrameSymbol {
name: frame
.function
.as_ref()
.and_then(|l| l.raw_name().ok())
.map(|s| s.to_string()),
file: frame
.location
.as_ref()
.and_then(|l| l.file)
.map(|s| s.to_string()),
line: frame.location.as_ref().and_then(|l| l.line),
column: frame.location.as_ref().and_then(|l| l.column),
});
}
}
}
}
let module = self.module.module();
let index = module.func_index(index);
Some(FrameInfo {
module_name: module.name.clone(),
func_index: index.index() as u32,
func_name: module.func_names.get(&index).cloned(),
instr,
func_start: info.start_srcloc,
symbols,
})
}
}
#[derive(Debug)]
pub struct FrameInfo {
module_name: Option<String>,
func_index: u32,
func_name: Option<String>,
func_start: FilePos,
instr: Option<FilePos>,
symbols: Vec<FrameSymbol>,
}
impl FrameInfo {
pub fn func_index(&self) -> u32 {
self.func_index
}
pub fn module_name(&self) -> Option<&str> {
self.module_name.as_deref()
}
pub fn func_name(&self) -> Option<&str> {
self.func_name.as_deref()
}
pub fn module_offset(&self) -> Option<usize> {
Some(self.instr?.file_offset()? as usize)
}
pub fn func_offset(&self) -> Option<usize> {
let instr_offset = self.instr?.file_offset()?;
Some((instr_offset - self.func_start.file_offset()?) as usize)
}
pub fn symbols(&self) -> &[FrameSymbol] {
&self.symbols
}
}
#[derive(Debug)]
pub struct FrameSymbol {
name: Option<String>,
file: Option<String>,
line: Option<u32>,
column: Option<u32>,
}
impl FrameSymbol {
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
pub fn file(&self) -> Option<&str> {
self.file.as_deref()
}
pub fn line(&self) -> Option<u32> {
self.line
}
pub fn column(&self) -> Option<u32> {
self.column
}
}
#[test]
fn test_frame_info() -> Result<(), anyhow::Error> {
use crate::*;
let mut store = Store::<()>::default();
let module = Module::new(
store.engine(),
r#"
(module
(func (export "add") (param $x i32) (param $y i32) (result i32) (i32.add (local.get $x) (local.get $y)))
(func (export "sub") (param $x i32) (param $y i32) (result i32) (i32.sub (local.get $x) (local.get $y)))
(func (export "mul") (param $x i32) (param $y i32) (result i32) (i32.mul (local.get $x) (local.get $y)))
(func (export "div_s") (param $x i32) (param $y i32) (result i32) (i32.div_s (local.get $x) (local.get $y)))
(func (export "div_u") (param $x i32) (param $y i32) (result i32) (i32.div_u (local.get $x) (local.get $y)))
(func (export "rem_s") (param $x i32) (param $y i32) (result i32) (i32.rem_s (local.get $x) (local.get $y)))
(func (export "rem_u") (param $x i32) (param $y i32) (result i32) (i32.rem_u (local.get $x) (local.get $y)))
)
"#,
)?;
Instance::new(&mut store, &module, &[])?;
GlobalModuleRegistry::with(|modules| {
for (i, alloc) in module.compiled_module().finished_functions() {
let (start, end) = unsafe {
let ptr = (*alloc).as_ptr();
let len = (*alloc).len();
(ptr as usize, ptr as usize + len)
};
for pc in start..end {
let (frame, _, _) = modules.lookup_frame_info(pc).unwrap();
assert!(
frame.func_index() == i.as_u32(),
"lookup of {:#x} returned {}, expected {}",
pc,
frame.func_index(),
i.as_u32()
);
}
}
});
Ok(())
}