use crate::{signatures::SignatureCollection, Module};
use std::{
collections::BTreeMap,
sync::{Arc, RwLock},
};
use wasmtime_environ::{
entity::EntityRef,
ir::{self, StackMap},
wasm::DefinedFuncIndex,
FunctionAddressMap, TrapInformation,
};
use wasmtime_jit::CompiledModule;
use wasmtime_runtime::{ModuleInfo, VMCallerCheckedAnyfunc, VMTrampoline};
lazy_static::lazy_static! {
static ref GLOBAL_MODULES: RwLock<GlobalModuleRegistry> = Default::default();
}
fn func_by_pc(module: &CompiledModule, pc: usize) -> Option<(DefinedFuncIndex, u32)> {
let (index, start, _) = module.func_by_pc(pc)?;
Some((index, (pc - start) as u32))
}
#[derive(Default)]
pub struct ModuleRegistry(BTreeMap<usize, Arc<RegisteredModule>>);
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.0.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();
let (start, end) = compiled_module.code().range();
if start == end || compiled_module.finished_functions().is_empty() {
return;
}
let end = end - 1;
if let Some(m) = self.0.get(&end) {
assert_eq!(m.start, start);
return;
}
if let Some((_, prev)) = self.0.range(end..).next() {
assert!(prev.start > end);
}
if let Some((prev_end, _)) = self.0.range(..=start).next_back() {
assert!(*prev_end < start);
}
let prev = self.0.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.0.keys() {
info.unregister(*end);
}
}
}
struct RegisteredModule {
start: usize,
module: Arc<CompiledModule>,
signatures: Arc<SignatureCollection>,
}
impl RegisteredModule {
fn instr_pos(offset: u32, addr_map: &FunctionAddressMap) -> Option<usize> {
match addr_map
.instructions
.binary_search_by_key(&offset, |map| map.code_offset)
{
Ok(pos) => Some(pos),
Err(0) => None,
Err(n) => Some(n - 1),
}
}
}
impl ModuleInfo for RegisteredModule {
fn lookup_stack_map(&self, pc: usize) -> Option<&StackMap> {
let (index, offset) = func_by_pc(&self.module, pc)?;
let info = self.module.func_info(index);
let index = match info
.stack_maps
.binary_search_by_key(&offset, |i| i.code_offset)
{
Ok(i) => i,
Err(0) => return None,
Err(i) => i - 1,
};
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_pc(pc: usize) -> bool {
let modules = GLOBAL_MODULES.read().unwrap();
match modules.module(pc) {
Some(entry) => match func_by_pc(&entry.module, pc) {
Some((index, offset)) => {
let info = entry.module.func_info(index);
RegisteredModule::instr_pos(offset, &info.address_map).is_some()
}
None => false,
},
None => false,
}
}
fn module(&self, pc: usize) -> Option<&GlobalRegisteredModule> {
let (end, info) = self.0.range(pc..).next()?;
if pc < info.start || *end < pc {
return None;
}
Some(info)
}
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 = self.module(pc)?;
module.lookup_frame_info(pc).map(|info| {
(
info,
module.has_unparsed_debuginfo(),
module.wasm_backtrace_details_env_used,
)
})
}
pub(crate) fn lookup_trap_info(&self, pc: usize) -> Option<&TrapInformation> {
self.module(pc)?.lookup_trap_info(pc)
}
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, pc: usize) -> Option<FrameInfo> {
let (index, offset) = func_by_pc(&self.module, pc)?;
let info = self.module.func_info(index);
let pos = RegisteredModule::instr_pos(offset, &info.address_map);
debug_assert!(pos.is_some(), "failed to find instruction for {:x}", pc);
let instr = match pos {
Some(pos) => info.address_map.instructions[pos].srcloc,
None => info.address_map.start_srcloc,
};
let mut symbols = Vec::new();
if let Some(s) = &self.module.symbolize_context().ok().and_then(|c| c) {
let to_lookup = (instr.bits() as u64) - 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.address_map.start_srcloc,
symbols,
})
}
pub fn lookup_trap_info(&self, pc: usize) -> Option<&TrapInformation> {
let (index, offset) = func_by_pc(&self.module, pc)?;
let info = self.module.func_info(index);
let idx = info
.traps
.binary_search_by_key(&offset, |info| info.code_offset)
.ok()?;
Some(&info.traps[idx])
}
}
#[derive(Debug)]
pub struct FrameInfo {
module_name: Option<String>,
func_index: u32,
func_name: Option<String>,
func_start: ir::SourceLoc,
instr: ir::SourceLoc,
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) -> usize {
self.instr.bits() as usize
}
pub fn func_offset(&self) -> usize {
(self.instr.bits() - self.func_start.bits()) 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());
}
}
});
Ok(())
}