Use a StoreOpaque during backtraces for metadata (#4325)

Previous to this commit Wasmtime would use the `GlobalModuleRegistry`
when learning information about a trap such as its trap code, the
symbols for each frame, etc. This has a downside though of holding a
global read-write lock for the duration of this operation which hinders
registration of new modules in parallel. In addition there was a fair
amount of internal duplication between this "global module registry" and
the store-local module registry. Finally relying on global state for
information like this gets a bit more brittle over time as it seems best
to scope global queries to precisely what's necessary rather than
holding extra information.

With the refactoring in wasm backtraces done in #4183 it's now possible
to always have a `StoreOpaque` reference when a backtrace is collected
for symbolication and otherwise Trap-identification purposes. This
commit adds a `StoreOpaque` parameter to the `Trap::from_runtime`
constructor and then plumbs that everywhere. Note that while doing this
I changed the internal `traphandlers::lazy_per_thread_init` function to
no longer return a `Result` and instead just `panic!` on Unix if memory
couldn't be allocated for a stack. This removed quite a lot of
error-handling code for a case that's expected to quite rarely happen.
If necessary in the future we can add a fallible initialization point
but this feels like a better default balance for the code here.

With a `StoreOpaque` in use when a trap is being symbolicated that means
we have a `ModuleRegistry` which can be used for queries and such. This
meant that the `GlobalModuleRegistry` state could largely be dismantled
and moved to per-`Store` state (within the `ModuleRegistry`, mostly just
moving methods around).

The final state is that the global rwlock is not exclusively scoped
around insertions/deletions/`is_wasm_trap_pc` which is just a lookup and
atomic add. Otherwise symbolication for a backtrace exclusively uses
store-local state now (as intended).

The original motivation for this commit was that frame information
lookup and pieces were looking to get somewhat complicated with the
addition of components which are a new vector of traps coming out of
Cranelift-generated code. My hope is that by having a `Store` around for
more operations it's easier to plumb all this through.
This commit is contained in:
Alex Crichton
2022-06-27 15:24:59 -05:00
committed by GitHub
parent 5c2c285dd7
commit 82a31680d6
12 changed files with 397 additions and 445 deletions

View File

@@ -78,7 +78,7 @@ fn lazy_thread_instantiate(engine: Engine, module: Module) -> Duration {
fn eager_thread_instantiate(engine: Engine, module: Module) -> (Duration, Duration) { fn eager_thread_instantiate(engine: Engine, module: Module) -> (Duration, Duration) {
thread::spawn(move || { thread::spawn(move || {
let init_start = Instant::now(); let init_start = Instant::now();
Engine::tls_eager_initialize().expect("eager init"); Engine::tls_eager_initialize();
let init_duration = init_start.elapsed(); let init_duration = init_start.elapsed();
(init_duration, duration_of_call(&engine, &module)) (init_duration, duration_of_call(&engine, &module))

View File

@@ -132,22 +132,6 @@ pub enum TrapReason {
/// A trap raised from a wasm libcall /// A trap raised from a wasm libcall
Wasm(TrapCode), Wasm(TrapCode),
/// A trap indicating that the runtime was unable to allocate sufficient memory.
OOM,
}
impl Trap {
/// Construct a new OOM trap.
///
/// Internally saves a backtrace when passed across a setjmp boundary, if the
/// engine is configured to save backtraces.
pub fn oom() -> Self {
Trap {
reason: TrapReason::OOM,
backtrace: None,
}
}
} }
/// Catches any wasm traps that happen within the execution of `closure`, /// Catches any wasm traps that happen within the execution of `closure`,
@@ -213,7 +197,7 @@ impl CallThreadState {
} }
fn with(self, closure: impl FnOnce(&CallThreadState) -> i32) -> Result<(), Box<Trap>> { fn with(self, closure: impl FnOnce(&CallThreadState) -> i32) -> Result<(), Box<Trap>> {
let ret = tls::set(&self, || closure(&self))?; let ret = tls::set(&self, || closure(&self));
if ret != 0 { if ret != 0 {
Ok(()) Ok(())
} else { } else {
@@ -329,7 +313,6 @@ impl<T: Copy> Drop for ResetCell<'_, T> {
// the caller to the trap site. // the caller to the trap site.
mod tls { mod tls {
use super::CallThreadState; use super::CallThreadState;
use crate::Trap;
use std::ptr; use std::ptr;
pub use raw::Ptr; pub use raw::Ptr;
@@ -350,7 +333,6 @@ mod tls {
// these functions are free to be inlined. // these functions are free to be inlined.
mod raw { mod raw {
use super::CallThreadState; use super::CallThreadState;
use crate::Trap;
use std::cell::Cell; use std::cell::Cell;
use std::ptr; use std::ptr;
@@ -365,17 +347,17 @@ mod tls {
#[cfg_attr(feature = "async", inline(never))] // see module docs #[cfg_attr(feature = "async", inline(never))] // see module docs
#[cfg_attr(not(feature = "async"), inline)] #[cfg_attr(not(feature = "async"), inline)]
pub fn replace(val: Ptr) -> Result<Ptr, Box<Trap>> { pub fn replace(val: Ptr) -> Ptr {
PTR.with(|p| { PTR.with(|p| {
// When a new value is configured that means that we may be // When a new value is configured that means that we may be
// entering WebAssembly so check to see if this thread has // entering WebAssembly so check to see if this thread has
// performed per-thread initialization for traps. // performed per-thread initialization for traps.
let (prev, initialized) = p.get(); let (prev, initialized) = p.get();
if !initialized { if !initialized {
super::super::sys::lazy_per_thread_init()?; super::super::sys::lazy_per_thread_init();
} }
p.set((val, true)); p.set((val, true));
Ok(prev) prev
}) })
} }
@@ -383,15 +365,14 @@ mod tls {
/// lazily by the runtime if users do not perform it eagerly. /// lazily by the runtime if users do not perform it eagerly.
#[cfg_attr(feature = "async", inline(never))] // see module docs #[cfg_attr(feature = "async", inline(never))] // see module docs
#[cfg_attr(not(feature = "async"), inline)] #[cfg_attr(not(feature = "async"), inline)]
pub fn initialize() -> Result<(), Box<Trap>> { pub fn initialize() {
PTR.with(|p| { PTR.with(|p| {
let (state, initialized) = p.get(); let (state, initialized) = p.get();
if initialized { if initialized {
return Ok(()); return;
} }
super::super::sys::lazy_per_thread_init()?; super::super::sys::lazy_per_thread_init();
p.set((state, true)); p.set((state, true));
Ok(())
}) })
} }
@@ -414,7 +395,7 @@ mod tls {
/// ///
/// This is not a safe operation since it's intended to only be used /// This is not a safe operation since it's intended to only be used
/// with stack switching found with fibers and async wasmtime. /// with stack switching found with fibers and async wasmtime.
pub unsafe fn take() -> Result<TlsRestore, Box<Trap>> { pub unsafe fn take() -> TlsRestore {
// Our tls pointer must be set at this time, and it must not be // Our tls pointer must be set at this time, and it must not be
// null. We need to restore the previous pointer since we're // null. We need to restore the previous pointer since we're
// removing ourselves from the call-stack, and in the process we // removing ourselves from the call-stack, and in the process we
@@ -423,30 +404,29 @@ mod tls {
let raw = raw::get(); let raw = raw::get();
if !raw.is_null() { if !raw.is_null() {
let prev = (*raw).prev.replace(ptr::null()); let prev = (*raw).prev.replace(ptr::null());
raw::replace(prev)?; raw::replace(prev);
} }
// Null case: we aren't in a wasm context, so theres no tls // Null case: we aren't in a wasm context, so theres no tls
// to save for restoration. // to save for restoration.
Ok(TlsRestore(raw)) TlsRestore(raw)
} }
/// Restores a previous tls state back into this thread's TLS. /// Restores a previous tls state back into this thread's TLS.
/// ///
/// This is unsafe because it's intended to only be used within the /// This is unsafe because it's intended to only be used within the
/// context of stack switching within wasmtime. /// context of stack switching within wasmtime.
pub unsafe fn replace(self) -> Result<(), Box<super::Trap>> { pub unsafe fn replace(self) {
// Null case: we aren't in a wasm context, so theres no tls // Null case: we aren't in a wasm context, so theres no tls
// to restore. // to restore.
if self.0.is_null() { if self.0.is_null() {
return Ok(()); return;
} }
// We need to configure our previous TLS pointer to whatever is in // We need to configure our previous TLS pointer to whatever is in
// TLS at this time, and then we set the current state to ourselves. // TLS at this time, and then we set the current state to ourselves.
let prev = raw::get(); let prev = raw::get();
assert!((*self.0).prev.get().is_null()); assert!((*self.0).prev.get().is_null());
(*self.0).prev.set(prev); (*self.0).prev.set(prev);
raw::replace(self.0)?; raw::replace(self.0);
Ok(())
} }
} }
@@ -454,21 +434,20 @@ mod tls {
/// execution of `closure` any call to `with` will yield `ptr`, unless this /// execution of `closure` any call to `with` will yield `ptr`, unless this
/// is recursively called again. /// is recursively called again.
#[inline] #[inline]
pub fn set<R>(state: &CallThreadState, closure: impl FnOnce() -> R) -> Result<R, Box<Trap>> { pub fn set<R>(state: &CallThreadState, closure: impl FnOnce() -> R) -> R {
struct Reset<'a>(&'a CallThreadState); struct Reset<'a>(&'a CallThreadState);
impl Drop for Reset<'_> { impl Drop for Reset<'_> {
#[inline] #[inline]
fn drop(&mut self) { fn drop(&mut self) {
raw::replace(self.0.prev.replace(ptr::null())) raw::replace(self.0.prev.replace(ptr::null()));
.expect("tls should be previously initialized");
} }
} }
let prev = raw::replace(state)?; let prev = raw::replace(state);
state.prev.set(prev); state.prev.set(prev);
let _reset = Reset(state); let _reset = Reset(state);
Ok(closure()) closure()
} }
/// Returns the last pointer configured with `set` above. Panics if `set` /// Returns the last pointer configured with `set` above. Panics if `set`

View File

@@ -33,7 +33,7 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::traphandlers::{tls, wasmtime_longjmp, Trap}; use crate::traphandlers::{tls, wasmtime_longjmp};
use mach::exception_types::*; use mach::exception_types::*;
use mach::kern_return::*; use mach::kern_return::*;
use mach::mach_init::*; use mach::mach_init::*;
@@ -410,7 +410,7 @@ unsafe extern "C" fn unwind(wasm_pc: *const u8) -> ! {
/// task-level port which is where we'd expected things like breakpad/crashpad /// task-level port which is where we'd expected things like breakpad/crashpad
/// exception handlers to get registered. /// exception handlers to get registered.
#[cold] #[cold]
pub fn lazy_per_thread_init() -> Result<(), Box<Trap>> { pub fn lazy_per_thread_init() {
unsafe { unsafe {
assert!(WASMTIME_PORT != MACH_PORT_NULL); assert!(WASMTIME_PORT != MACH_PORT_NULL);
let this_thread = mach_thread_self(); let this_thread = mach_thread_self();
@@ -424,5 +424,4 @@ pub fn lazy_per_thread_init() -> Result<(), Box<Trap>> {
mach_port_deallocate(mach_task_self(), this_thread); mach_port_deallocate(mach_task_self(), this_thread);
assert_eq!(kret, KERN_SUCCESS, "failed to set thread exception port"); assert_eq!(kret, KERN_SUCCESS, "failed to set thread exception port");
} }
Ok(())
} }

View File

@@ -1,4 +1,4 @@
use crate::traphandlers::{tls, wasmtime_longjmp, Trap}; use crate::traphandlers::{tls, wasmtime_longjmp};
use std::cell::RefCell; use std::cell::RefCell;
use std::io; use std::io;
use std::mem::{self, MaybeUninit}; use std::mem::{self, MaybeUninit};
@@ -252,7 +252,7 @@ unsafe fn set_pc(cx: *mut libc::c_void, pc: usize, arg1: usize) {
/// and registering our own alternate stack that is large enough and has a guard /// and registering our own alternate stack that is large enough and has a guard
/// page. /// page.
#[cold] #[cold]
pub fn lazy_per_thread_init() -> Result<(), Box<Trap>> { pub fn lazy_per_thread_init() {
// This thread local is purely used to register a `Stack` to get deallocated // This thread local is purely used to register a `Stack` to get deallocated
// when the thread exists. Otherwise this function is only ever called at // when the thread exists. Otherwise this function is only ever called at
// most once per-thread. // most once per-thread.
@@ -270,11 +270,10 @@ pub fn lazy_per_thread_init() -> Result<(), Box<Trap>> {
} }
return STACK.with(|s| { return STACK.with(|s| {
*s.borrow_mut() = unsafe { allocate_sigaltstack()? }; *s.borrow_mut() = unsafe { allocate_sigaltstack() };
Ok(())
}); });
unsafe fn allocate_sigaltstack() -> Result<Option<Stack>, Box<Trap>> { unsafe fn allocate_sigaltstack() -> Option<Stack> {
// Check to see if the existing sigaltstack, if it exists, is big // Check to see if the existing sigaltstack, if it exists, is big
// enough. If so we don't need to allocate our own. // enough. If so we don't need to allocate our own.
let mut old_stack = mem::zeroed(); let mut old_stack = mem::zeroed();
@@ -286,7 +285,7 @@ pub fn lazy_per_thread_init() -> Result<(), Box<Trap>> {
io::Error::last_os_error() io::Error::last_os_error()
); );
if old_stack.ss_flags & libc::SS_DISABLE == 0 && old_stack.ss_size >= MIN_STACK_SIZE { if old_stack.ss_flags & libc::SS_DISABLE == 0 && old_stack.ss_size >= MIN_STACK_SIZE {
return Ok(None); return None;
} }
// ... but failing that we need to allocate our own, so do all that // ... but failing that we need to allocate our own, so do all that
@@ -301,7 +300,7 @@ pub fn lazy_per_thread_init() -> Result<(), Box<Trap>> {
rustix::mm::ProtFlags::empty(), rustix::mm::ProtFlags::empty(),
rustix::mm::MapFlags::PRIVATE, rustix::mm::MapFlags::PRIVATE,
) )
.map_err(|_| Box::new(Trap::oom()))?; .expect("failed to allocate memory for sigaltstack");
// Prepare the stack with readable/writable memory and then register it // Prepare the stack with readable/writable memory and then register it
// with `sigaltstack`. // with `sigaltstack`.
@@ -325,10 +324,10 @@ pub fn lazy_per_thread_init() -> Result<(), Box<Trap>> {
io::Error::last_os_error() io::Error::last_os_error()
); );
Ok(Some(Stack { Some(Stack {
mmap_ptr: ptr, mmap_ptr: ptr,
mmap_size: alloc_size, mmap_size: alloc_size,
})) })
} }
impl Drop for Stack { impl Drop for Stack {

View File

@@ -1,4 +1,4 @@
use crate::traphandlers::{tls, wasmtime_longjmp, Trap}; use crate::traphandlers::{tls, wasmtime_longjmp};
use std::io; use std::io;
use winapi::um::errhandlingapi::*; use winapi::um::errhandlingapi::*;
use winapi::um::minwinbase::*; use winapi::um::minwinbase::*;
@@ -74,7 +74,6 @@ unsafe extern "system" fn exception_handler(exception_info: PEXCEPTION_POINTERS)
}) })
} }
pub fn lazy_per_thread_init() -> Result<(), Box<Trap>> { pub fn lazy_per_thread_init() {
// Unused on Windows // Unused on Windows
Ok(())
} }

View File

@@ -1,5 +1,5 @@
use crate::signatures::SignatureRegistry; use crate::signatures::SignatureRegistry;
use crate::{Config, Trap}; use crate::Config;
use anyhow::Result; use anyhow::Result;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
#[cfg(feature = "parallel-compilation")] #[cfg(feature = "parallel-compilation")]
@@ -71,7 +71,7 @@ impl Engine {
// Ensure that wasmtime_runtime's signal handlers are configured. This // Ensure that wasmtime_runtime's signal handlers are configured. This
// is the per-program initialization required for handling traps, such // is the per-program initialization required for handling traps, such
// as configuring signals, vectored exception handlers, etc. // as configuring signals, vectored exception handlers, etc.
wasmtime_runtime::init_traps(crate::module::GlobalModuleRegistry::is_wasm_trap_pc); wasmtime_runtime::init_traps(crate::module::is_wasm_trap_pc);
debug_builtins::ensure_exported(); debug_builtins::ensure_exported();
let registry = SignatureRegistry::new(); let registry = SignatureRegistry::new();
@@ -117,8 +117,8 @@ impl Engine {
/// on calls into WebAssembly. This is provided for use cases where the /// on calls into WebAssembly. This is provided for use cases where the
/// latency of WebAssembly calls are extra-important, which is not /// latency of WebAssembly calls are extra-important, which is not
/// necessarily true of all embeddings. /// necessarily true of all embeddings.
pub fn tls_eager_initialize() -> Result<(), Trap> { pub fn tls_eager_initialize() {
wasmtime_runtime::tls_eager_initialize().map_err(Trap::from_runtime_box) wasmtime_runtime::tls_eager_initialize();
} }
/// Returns the configuration settings that this engine is using. /// Returns the configuration settings that this engine is using.

View File

@@ -1241,7 +1241,7 @@ pub(crate) fn invoke_wasm_and_catch_traps<T>(
); );
exit_wasm(store, exit); exit_wasm(store, exit);
store.0.call_hook(CallHook::ReturningFromWasm)?; store.0.call_hook(CallHook::ReturningFromWasm)?;
result.map_err(Trap::from_runtime_box) result.map_err(|t| Trap::from_runtime_box(store.0, t))
} }
} }

View File

@@ -413,7 +413,7 @@ pub use crate::instance::{Instance, InstancePre};
pub use crate::limits::*; pub use crate::limits::*;
pub use crate::linker::*; pub use crate::linker::*;
pub use crate::memory::*; pub use crate::memory::*;
pub use crate::module::{FrameInfo, FrameSymbol, Module}; pub use crate::module::Module;
pub use crate::r#ref::ExternRef; pub use crate::r#ref::ExternRef;
#[cfg(feature = "async")] #[cfg(feature = "async")]
pub use crate::store::CallHookHandler; pub use crate::store::CallHookHandler;

View File

@@ -25,7 +25,7 @@ use wasmtime_runtime::{
mod registry; mod registry;
mod serialization; mod serialization;
pub use registry::{FrameInfo, FrameSymbol, GlobalModuleRegistry, ModuleRegistry}; pub use registry::{is_wasm_trap_pc, ModuleRegistry};
pub use serialization::SerializedModule; pub use serialization::SerializedModule;
/// A compiled WebAssembly module, ready to be instantiated. /// A compiled WebAssembly module, ready to be instantiated.
@@ -511,7 +511,7 @@ impl Module {
// into the global registry of modules so we can resolve traps // into the global registry of modules so we can resolve traps
// appropriately. Note that the corresponding `unregister` happens below // appropriately. Note that the corresponding `unregister` happens below
// in `Drop for ModuleInner`. // in `Drop for ModuleInner`.
registry::register(engine, &module); registry::register(&module);
Ok(Self { Ok(Self {
inner: Arc::new(ModuleInner { inner: Arc::new(ModuleInner {

View File

@@ -2,19 +2,15 @@
#[cfg(feature = "component-model")] #[cfg(feature = "component-model")]
use crate::component::Component; use crate::component::Component;
use crate::{Engine, Module}; use crate::{FrameInfo, Module};
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
use wasmtime_environ::{EntityRef, FilePos, TrapCode}; use wasmtime_environ::TrapCode;
use wasmtime_jit::CompiledModule; use wasmtime_jit::CompiledModule;
use wasmtime_runtime::{ModuleInfo, VMCallerCheckedAnyfunc, VMTrampoline}; use wasmtime_runtime::{ModuleInfo, VMCallerCheckedAnyfunc, VMTrampoline};
lazy_static::lazy_static! {
static ref GLOBAL_MODULES: RwLock<GlobalModuleRegistry> = Default::default();
}
/// Used for registering modules with a store. /// Used for registering modules with a store.
/// ///
/// Note that the primary reason for this registry is to ensure that everything /// Note that the primary reason for this registry is to ensure that everything
@@ -49,23 +45,23 @@ fn start(module: &Module) -> usize {
impl ModuleRegistry { impl ModuleRegistry {
/// Fetches information about a registered module given a program counter value. /// Fetches information about a registered module given a program counter value.
pub fn lookup_module(&self, pc: usize) -> Option<&dyn ModuleInfo> { pub fn lookup_module(&self, pc: usize) -> Option<&dyn ModuleInfo> {
self.module(pc).map(|m| m.module_info()) self.module(pc).map(|(m, _)| m.module_info())
} }
fn module(&self, pc: usize) -> Option<&Module> { fn module(&self, pc: usize) -> Option<(&Module, usize)> {
match self.module_or_component(pc)? { match self.module_or_component(pc)? {
ModuleOrComponent::Module(m) => Some(m), (ModuleOrComponent::Module(m), offset) => Some((m, offset)),
#[cfg(feature = "component-model")] #[cfg(feature = "component-model")]
ModuleOrComponent::Component(_) => None, (ModuleOrComponent::Component(_), _) => None,
} }
} }
fn module_or_component(&self, pc: usize) -> Option<&ModuleOrComponent> { fn module_or_component(&self, pc: usize) -> Option<(&ModuleOrComponent, usize)> {
let (end, (start, module)) = self.modules_with_code.range(pc..).next()?; let (end, (start, module)) = self.modules_with_code.range(pc..).next()?;
if pc < *start || *end < pc { if pc < *start || *end < pc {
return None; return None;
} }
Some(module) Some((module, pc - *start))
} }
/// Registers a new module with the registry. /// Registers a new module with the registry.
@@ -138,64 +134,21 @@ impl ModuleRegistry {
/// Looks up a trampoline from an anyfunc. /// Looks up a trampoline from an anyfunc.
pub fn lookup_trampoline(&self, anyfunc: &VMCallerCheckedAnyfunc) -> Option<VMTrampoline> { pub fn lookup_trampoline(&self, anyfunc: &VMCallerCheckedAnyfunc) -> Option<VMTrampoline> {
let signatures = match self.module_or_component(anyfunc.func_ptr.as_ptr() as usize)? { let signatures = match self
.module_or_component(anyfunc.func_ptr.as_ptr() as usize)?
.0
{
ModuleOrComponent::Module(m) => m.signatures(), ModuleOrComponent::Module(m) => m.signatures(),
#[cfg(feature = "component-model")] #[cfg(feature = "component-model")]
ModuleOrComponent::Component(c) => c.signatures(), ModuleOrComponent::Component(c) => c.signatures(),
}; };
signatures.trampoline(anyfunc.type_index) signatures.trampoline(anyfunc.type_index)
} }
}
// Counterpart to `RegisteredModule`, but stored in the global registry. /// Fetches trap information about a program counter in a backtrace.
struct GlobalRegisteredModule { pub fn lookup_trap_code(&self, pc: usize) -> Option<TrapCode> {
start: usize, let (module, offset) = self.module(pc)?;
module: Arc<CompiledModule>, wasmtime_environ::lookup_trap_code(module.compiled_module().trap_data(), offset)
wasm_backtrace_details_env_used: bool,
}
/// This is the global module registry that stores information for all modules
/// that are currently in use by any `Store`.
///
/// The purpose of this map is to be called from signal handlers to determine
/// whether a program counter is a wasm trap or not. Specifically macOS has
/// no contextual information about the thread available, hence the necessity
/// for global state rather than using thread local state.
///
/// This is similar to `ModuleRegistry` except that it has less information and
/// supports removal. Any time anything is registered with a `ModuleRegistry`
/// it is also automatically registered with the singleton global module
/// registry. When a `ModuleRegistry` is destroyed then all of its entries
/// are removed from the global module registry.
#[derive(Default)]
pub struct GlobalModuleRegistry(BTreeMap<usize, GlobalRegisteredModule>);
impl GlobalModuleRegistry {
/// Returns whether the `pc`, according to globally registered information,
/// is a wasm trap or not.
pub(crate) fn is_wasm_trap_pc(pc: usize) -> bool {
let (module, text_offset) = match GLOBAL_MODULES.read().unwrap().module(pc) {
Some((module, offset)) => (module.module.clone(), offset),
None => return false,
};
wasmtime_environ::lookup_trap_code(module.trap_data(), text_offset).is_some()
}
/// Returns, if found, the corresponding module for the `pc` as well as the
/// pc transformed to a relative offset within the text section.
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))
}
// Work with the global instance of `GlobalModuleRegistry`. Note that only
// shared access is allowed, this isn't intended to mutate the contents.
pub(crate) fn with<R>(f: impl FnOnce(&GlobalModuleRegistry) -> R) -> R {
f(&GLOBAL_MODULES.read().unwrap())
} }
/// Fetches frame information about a program counter in a backtrace. /// Fetches frame information about a program counter in a backtrace.
@@ -206,22 +159,49 @@ impl GlobalModuleRegistry {
/// debug information due to the compiler's configuration. The second /// debug information due to the compiler's configuration. The second
/// boolean indicates whether the engine used to compile this module is /// boolean indicates whether the engine used to compile this module is
/// using environment variables to control debuginfo parsing. /// using environment variables to control debuginfo parsing.
pub(crate) fn lookup_frame_info(&self, pc: usize) -> Option<(FrameInfo, bool, bool)> { pub(crate) fn lookup_frame_info(&self, pc: usize) -> Option<(FrameInfo, &Module)> {
let (module, offset) = self.module(pc)?; let (module, offset) = self.module(pc)?;
module.lookup_frame_info(offset).map(|info| { let info = FrameInfo::new(module, offset)?;
( Some((info, module))
info,
module.has_unparsed_debuginfo(),
module.wasm_backtrace_details_env_used,
)
})
} }
}
/// Fetches trap information about a program counter in a backtrace. // This is the global module registry that stores information for all modules
pub(crate) fn lookup_trap_code(&self, pc: usize) -> Option<TrapCode> { // that are currently in use by any `Store`.
let (module, offset) = self.module(pc)?; //
wasmtime_environ::lookup_trap_code(module.module.trap_data(), offset) // The purpose of this map is to be called from signal handlers to determine
} // whether a program counter is a wasm trap or not. Specifically macOS has
// no contextual information about the thread available, hence the necessity
// for global state rather than using thread local state.
//
// This is similar to `ModuleRegistry` except that it has less information and
// supports removal. Any time anything is registered with a `ModuleRegistry`
// it is also automatically registered with the singleton global module
// registry. When a `ModuleRegistry` is destroyed then all of its entries
// are removed from the global module registry.
lazy_static::lazy_static! {
static ref GLOBAL_MODULES: RwLock<GlobalModuleRegistry> = Default::default();
}
type GlobalModuleRegistry = BTreeMap<usize, (usize, Arc<CompiledModule>)>;
/// Returns whether the `pc`, according to globally registered information,
/// is a wasm trap or not.
pub fn is_wasm_trap_pc(pc: usize) -> bool {
let (module, text_offset) = {
let all_modules = GLOBAL_MODULES.read().unwrap();
let (end, (start, module)) = match all_modules.range(pc..).next() {
Some(info) => info,
None => return false,
};
if pc < *start || *end < pc {
return false;
}
(module.clone(), pc - *start)
};
wasmtime_environ::lookup_trap_code(module.trap_data(), text_offset).is_some()
} }
/// Registers a new region of code. /// Registers a new region of code.
@@ -232,19 +212,17 @@ impl GlobalModuleRegistry {
/// This is required to enable traps to work correctly since the signal handler /// This is required to enable traps to work correctly since the signal handler
/// will lookup in the `GLOBAL_MODULES` list to determine which a particular pc /// will lookup in the `GLOBAL_MODULES` list to determine which a particular pc
/// is a trap or not. /// is a trap or not.
pub fn register(engine: &Engine, module: &Arc<CompiledModule>) { pub fn register(module: &Arc<CompiledModule>) {
let code = module.code(); let code = module.code();
if code.is_empty() { if code.is_empty() {
return; return;
} }
let start = code.as_ptr() as usize; let start = code.as_ptr() as usize;
let end = start + code.len() - 1; let end = start + code.len() - 1;
let module = GlobalRegisteredModule { let prev = GLOBAL_MODULES
start, .write()
wasm_backtrace_details_env_used: engine.config().wasm_backtrace_details_env_used, .unwrap()
module: module.clone(), .insert(end, (start, module.clone()));
};
let prev = GLOBAL_MODULES.write().unwrap().0.insert(end, module);
assert!(prev.is_none()); assert!(prev.is_none());
} }
@@ -257,238 +235,10 @@ pub fn unregister(module: &Arc<CompiledModule>) {
return; return;
} }
let end = (code.as_ptr() as usize) + code.len() - 1; let end = (code.as_ptr() as usize) + code.len() - 1;
let module = GLOBAL_MODULES.write().unwrap().0.remove(&end); let module = GLOBAL_MODULES.write().unwrap().remove(&end);
assert!(module.is_some()); assert!(module.is_some());
} }
impl GlobalRegisteredModule {
/// Determines if the related module has unparsed debug information.
pub fn has_unparsed_debuginfo(&self) -> bool {
self.module.has_unparsed_debuginfo()
}
/// Fetches frame information about a program counter in a backtrace.
///
/// Returns an object if this `pc` is known to this module, or returns `None`
/// if no information can be found.
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);
// In debug mode for now assert that we found a mapping for `pc` within
// the function, because otherwise something is buggy along the way and
// not accounting for all the instructions. This isn't super critical
// though so we can omit this check in release mode.
//
// Note that if the module doesn't even have an address map due to
// compilation settings then it's expected that `instr` is `None`.
debug_assert!(
instr.is_some() || !self.module.has_address_map(),
"failed to find instruction for {:#x}",
text_offset
);
// Use our wasm-relative pc to symbolize this frame. If there's a
// symbolication context (dwarf debug info) available then we can try to
// look this up there.
//
// Note that dwarf pcs are code-section-relative, hence the subtraction
// from the location of `instr`. Also note that all errors are ignored
// here for now since technically wasm modules can always have any
// custom section contents.
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: self.module.func_name(index).map(|s| s.to_string()),
instr,
func_start: info.start_srcloc,
symbols,
})
}
}
/// Description of a frame in a backtrace for a [`Trap`].
///
/// Whenever a WebAssembly trap occurs an instance of [`Trap`] is created. Each
/// [`Trap`] has a backtrace of the WebAssembly frames that led to the trap, and
/// each frame is described by this structure.
///
/// [`Trap`]: crate::Trap
#[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 {
/// Returns the WebAssembly function index for this frame.
///
/// This function index is the index in the function index space of the
/// WebAssembly module that this frame comes from.
pub fn func_index(&self) -> u32 {
self.func_index
}
/// Returns the identifer of the module that this frame is for.
///
/// Module identifiers are present in the `name` section of a WebAssembly
/// binary, but this may not return the exact item in the `name` section.
/// Module names can be overwritten at construction time or perhaps inferred
/// from file names. The primary purpose of this function is to assist in
/// debugging and therefore may be tweaked over time.
///
/// This function returns `None` when no name can be found or inferred.
pub fn module_name(&self) -> Option<&str> {
self.module_name.as_deref()
}
/// Returns a descriptive name of the function for this frame, if one is
/// available.
///
/// The name of this function may come from the `name` section of the
/// WebAssembly binary, or wasmtime may try to infer a better name for it if
/// not available, for example the name of the export if it's exported.
///
/// This return value is primarily used for debugging and human-readable
/// purposes for things like traps. Note that the exact return value may be
/// tweaked over time here and isn't guaranteed to be something in
/// particular about a wasm module due to its primary purpose of assisting
/// in debugging.
///
/// This function returns `None` when no name could be inferred.
pub fn func_name(&self) -> Option<&str> {
self.func_name.as_deref()
}
/// Returns the offset within the original wasm module this frame's program
/// counter was at.
///
/// The offset here is the offset from the beginning of the original wasm
/// module to the instruction that this frame points to.
///
/// Note that `None` may be returned if the original module was not
/// compiled with mapping information to yield this information. This is
/// controlled by the
/// [`Config::generate_address_map`](crate::Config::generate_address_map)
/// configuration option.
pub fn module_offset(&self) -> Option<usize> {
Some(self.instr?.file_offset()? as usize)
}
/// Returns the offset from the original wasm module's function to this
/// frame's program counter.
///
/// The offset here is the offset from the beginning of the defining
/// function of this frame (within the wasm module) to the instruction this
/// frame points to.
///
/// Note that `None` may be returned if the original module was not
/// compiled with mapping information to yield this information. This is
/// controlled by the
/// [`Config::generate_address_map`](crate::Config::generate_address_map)
/// configuration option.
pub fn func_offset(&self) -> Option<usize> {
let instr_offset = self.instr?.file_offset()?;
Some((instr_offset - self.func_start.file_offset()?) as usize)
}
/// Returns the debug symbols found, if any, for this function frame.
///
/// When a wasm program is compiled with DWARF debug information then this
/// function may be populated to return symbols which contain extra debug
/// information about a frame including the filename and line number. If no
/// debug information was found or if it was malformed then this will return
/// an empty array.
pub fn symbols(&self) -> &[FrameSymbol] {
&self.symbols
}
}
/// Debug information for a symbol that is attached to a [`FrameInfo`].
///
/// When DWARF debug information is present in a wasm file then this structure
/// can be found on a [`FrameInfo`] and can be used to learn about filenames,
/// line numbers, etc, which are the origin of a function in a stack trace.
#[derive(Debug)]
pub struct FrameSymbol {
name: Option<String>,
file: Option<String>,
line: Option<u32>,
column: Option<u32>,
}
impl FrameSymbol {
/// Returns the function name associated with this symbol.
///
/// Note that this may not be present with malformed debug information, or
/// the debug information may not include it. Also note that the symbol is
/// frequently mangled, so you might need to run some form of demangling
/// over it.
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
/// Returns the source code filename this symbol was defined in.
///
/// Note that this may not be present with malformed debug information, or
/// the debug information may not include it.
pub fn file(&self) -> Option<&str> {
self.file.as_deref()
}
/// Returns the 1-indexed source code line number this symbol was defined
/// on.
///
/// Note that this may not be present with malformed debug information, or
/// the debug information may not include it.
pub fn line(&self) -> Option<u32> {
self.line
}
/// Returns the 1-indexed source code column number this symbol was defined
/// on.
///
/// Note that this may not be present with malformed debug information, or
/// the debug information may not include it.
pub fn column(&self) -> Option<u32> {
self.column
}
}
#[test] #[test]
fn test_frame_info() -> Result<(), anyhow::Error> { fn test_frame_info() -> Result<(), anyhow::Error> {
use crate::*; use crate::*;
@@ -510,24 +260,27 @@ fn test_frame_info() -> Result<(), anyhow::Error> {
// Create an instance to ensure the frame information is registered. // Create an instance to ensure the frame information is registered.
Instance::new(&mut store, &module, &[])?; Instance::new(&mut store, &module, &[])?;
GlobalModuleRegistry::with(|modules| { for (i, alloc) in module.compiled_module().finished_functions() {
for (i, alloc) in module.compiled_module().finished_functions() { let (start, end) = unsafe {
let (start, end) = unsafe { let ptr = (*alloc).as_ptr();
let ptr = (*alloc).as_ptr(); let len = (*alloc).len();
let len = (*alloc).len(); (ptr as usize, ptr as usize + len)
(ptr as usize, ptr as usize + len) };
}; for pc in start..end {
for pc in start..end { let (frame, _) = store
let (frame, _, _) = modules.lookup_frame_info(pc).unwrap(); .as_context()
assert!( .0
frame.func_index() == i.as_u32(), .modules()
"lookup of {:#x} returned {}, expected {}", .lookup_frame_info(pc)
pc, .unwrap();
frame.func_index(), assert!(
i.as_u32() frame.func_index() == i.as_u32(),
); "lookup of {:#x} returned {}, expected {}",
} pc,
frame.func_index(),
i.as_u32()
);
} }
}); }
Ok(()) Ok(())
} }

View File

@@ -1159,6 +1159,11 @@ impl StoreOpaque {
&mut self.store_data &mut self.store_data
} }
#[inline]
pub(crate) fn modules(&self) -> &ModuleRegistry {
&self.modules
}
#[inline] #[inline]
pub(crate) fn modules_mut(&mut self) -> &mut ModuleRegistry { pub(crate) fn modules_mut(&mut self) -> &mut ModuleRegistry {
&mut self.modules &mut self.modules
@@ -1767,9 +1772,9 @@ impl AsyncCx {
Poll::Pending => {} Poll::Pending => {}
} }
let before = wasmtime_runtime::TlsRestore::take().map_err(Trap::from_runtime_box)?; let before = wasmtime_runtime::TlsRestore::take();
let res = (*suspend).suspend(()); let res = (*suspend).suspend(());
before.replace().map_err(Trap::from_runtime_box)?; before.replace();
res?; res?;
} }
} }

View File

@@ -1,9 +1,9 @@
use crate::module::GlobalModuleRegistry; use crate::store::StoreOpaque;
use crate::FrameInfo; use crate::Module;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use std::fmt; use std::fmt;
use std::sync::Arc; use std::sync::Arc;
use wasmtime_environ::TrapCode as EnvTrapCode; use wasmtime_environ::{EntityRef, FilePos, TrapCode as EnvTrapCode};
use wasmtime_jit::{demangle_function_name, demangle_function_name_or_index}; use wasmtime_jit::{demangle_function_name, demangle_function_name_or_index};
use wasmtime_runtime::Backtrace; use wasmtime_runtime::Backtrace;
@@ -136,44 +136,44 @@ pub(crate) struct TrapBacktrace {
} }
impl TrapBacktrace { impl TrapBacktrace {
pub fn new(native_trace: Backtrace, trap_pc: Option<usize>) -> Self { pub fn new(store: &StoreOpaque, native_trace: Backtrace, trap_pc: Option<usize>) -> Self {
let mut wasm_trace = Vec::<FrameInfo>::new(); let mut wasm_trace = Vec::<FrameInfo>::new();
let mut hint_wasm_backtrace_details_env = false; let mut hint_wasm_backtrace_details_env = false;
let wasm_backtrace_details_env_used =
store.engine().config().wasm_backtrace_details_env_used;
GlobalModuleRegistry::with(|registry| { for frame in native_trace.frames() {
for frame in native_trace.frames() { let pc = frame.ip() as usize;
let pc = frame.ip() as usize; if pc == 0 {
if pc == 0 { continue;
continue; }
} // Note that we need to be careful about the pc we pass in
// Note that we need to be careful about the pc we pass in // here to lookup frame information. This program counter is
// here to lookup frame information. This program counter is // used to translate back to an original source location in
// used to translate back to an original source location in // the origin wasm module. If this pc is the exact pc that
// the origin wasm module. If this pc is the exact pc that // the trap happened at, then we look up that pc precisely.
// the trap happened at, then we look up that pc precisely. // Otherwise backtrace information typically points at the
// Otherwise backtrace information typically points at the // pc *after* the call instruction (because otherwise it's
// pc *after* the call instruction (because otherwise it's // likely a call instruction on the stack). In that case we
// likely a call instruction on the stack). In that case we // want to lookup information for the previous instruction
// want to lookup information for the previous instruction // (the call instruction) so we subtract one as the lookup.
// (the call instruction) so we subtract one as the lookup. let pc_to_lookup = if Some(pc) == trap_pc { pc } else { pc - 1 };
let pc_to_lookup = if Some(pc) == trap_pc { pc } else { pc - 1 }; if let Some((info, module)) = store.modules().lookup_frame_info(pc_to_lookup) {
if let Some((info, has_unparsed_debuginfo, wasm_backtrace_details_env_used)) = wasm_trace.push(info);
registry.lookup_frame_info(pc_to_lookup)
{
wasm_trace.push(info);
// If this frame has unparsed debug information and the // If this frame has unparsed debug information and the
// store's configuration indicates that we were // store's configuration indicates that we were
// respecting the environment variable of whether to // respecting the environment variable of whether to
// do this then we will print out a helpful note in // do this then we will print out a helpful note in
// `Display` to indicate that more detailed information // `Display` to indicate that more detailed information
// in a trap may be available. // in a trap may be available.
if has_unparsed_debuginfo && wasm_backtrace_details_env_used { let has_unparsed_debuginfo = module.compiled_module().has_unparsed_debuginfo();
hint_wasm_backtrace_details_env = true; if has_unparsed_debuginfo && wasm_backtrace_details_env_used {
} hint_wasm_backtrace_details_env = true;
} }
} }
}); }
Self { Self {
wasm_trace, wasm_trace,
native_trace, native_trace,
@@ -212,39 +212,36 @@ impl Trap {
} }
#[cold] // see Trap::new #[cold] // see Trap::new
pub(crate) fn from_runtime_box(runtime_trap: Box<wasmtime_runtime::Trap>) -> Self { pub(crate) fn from_runtime_box(
Self::from_runtime(*runtime_trap) store: &StoreOpaque,
runtime_trap: Box<wasmtime_runtime::Trap>,
) -> Self {
Self::from_runtime(store, *runtime_trap)
} }
#[cold] // see Trap::new #[cold] // see Trap::new
pub(crate) fn from_runtime(runtime_trap: wasmtime_runtime::Trap) -> Self { pub(crate) fn from_runtime(store: &StoreOpaque, runtime_trap: wasmtime_runtime::Trap) -> Self {
let wasmtime_runtime::Trap { reason, backtrace } = runtime_trap; let wasmtime_runtime::Trap { reason, backtrace } = runtime_trap;
match reason { match reason {
wasmtime_runtime::TrapReason::User(error) => { wasmtime_runtime::TrapReason::User(error) => {
let trap = Trap::from(error); let trap = Trap::from(error);
if let Some(backtrace) = backtrace { if let Some(backtrace) = backtrace {
trap.record_backtrace(TrapBacktrace::new(backtrace, None)); trap.record_backtrace(TrapBacktrace::new(store, backtrace, None));
} }
trap trap
} }
wasmtime_runtime::TrapReason::Jit(pc) => { wasmtime_runtime::TrapReason::Jit(pc) => {
let code = GlobalModuleRegistry::with(|modules| { let code = store
modules .modules()
.lookup_trap_code(pc) .lookup_trap_code(pc)
.unwrap_or(EnvTrapCode::StackOverflow) .unwrap_or(EnvTrapCode::StackOverflow);
}); let backtrace = backtrace.map(|bt| TrapBacktrace::new(store, bt, Some(pc)));
let backtrace = backtrace.map(|bt| TrapBacktrace::new(bt, Some(pc)));
Trap::new_wasm(code, backtrace) Trap::new_wasm(code, backtrace)
} }
wasmtime_runtime::TrapReason::Wasm(trap_code) => { wasmtime_runtime::TrapReason::Wasm(trap_code) => {
let backtrace = backtrace.map(|bt| TrapBacktrace::new(bt, None)); let backtrace = backtrace.map(|bt| TrapBacktrace::new(store, bt, None));
Trap::new_wasm(trap_code, backtrace) Trap::new_wasm(trap_code, backtrace)
} }
wasmtime_runtime::TrapReason::OOM => {
let reason = TrapReason::Message("out of memory".to_string());
let backtrace = backtrace.map(|bt| TrapBacktrace::new(bt, None));
Trap::new_with_trace(reason, backtrace)
}
} }
} }
@@ -442,3 +439,224 @@ impl From<Box<dyn std::error::Error + Send + Sync>> for Trap {
} }
} }
} }
/// Description of a frame in a backtrace for a [`Trap`].
///
/// Whenever a WebAssembly trap occurs an instance of [`Trap`] is created. Each
/// [`Trap`] has a backtrace of the WebAssembly frames that led to the trap, and
/// each frame is described by this structure.
///
/// [`Trap`]: crate::Trap
#[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 {
/// Fetches frame information about a program counter in a backtrace.
///
/// Returns an object if this `pc` is known to this module, or returns `None`
/// if no information can be found.
pub(crate) fn new(module: &Module, text_offset: usize) -> Option<FrameInfo> {
let module = module.compiled_module();
let (index, _func_offset) = module.func_by_text_offset(text_offset)?;
let info = module.func_info(index);
let instr = wasmtime_environ::lookup_file_pos(module.address_map_data(), text_offset);
// In debug mode for now assert that we found a mapping for `pc` within
// the function, because otherwise something is buggy along the way and
// not accounting for all the instructions. This isn't super critical
// though so we can omit this check in release mode.
//
// Note that if the module doesn't even have an address map due to
// compilation settings then it's expected that `instr` is `None`.
debug_assert!(
instr.is_some() || !module.has_address_map(),
"failed to find instruction for {:#x}",
text_offset
);
// Use our wasm-relative pc to symbolize this frame. If there's a
// symbolication context (dwarf debug info) available then we can try to
// look this up there.
//
// Note that dwarf pcs are code-section-relative, hence the subtraction
// from the location of `instr`. Also note that all errors are ignored
// here for now since technically wasm modules can always have any
// custom section contents.
let mut symbols = Vec::new();
if let Some(s) = &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 index = module.module().func_index(index);
Some(FrameInfo {
module_name: module.module().name.clone(),
func_index: index.index() as u32,
func_name: module.func_name(index).map(|s| s.to_string()),
instr,
func_start: info.start_srcloc,
symbols,
})
}
/// Returns the WebAssembly function index for this frame.
///
/// This function index is the index in the function index space of the
/// WebAssembly module that this frame comes from.
pub fn func_index(&self) -> u32 {
self.func_index
}
/// Returns the identifer of the module that this frame is for.
///
/// Module identifiers are present in the `name` section of a WebAssembly
/// binary, but this may not return the exact item in the `name` section.
/// Module names can be overwritten at construction time or perhaps inferred
/// from file names. The primary purpose of this function is to assist in
/// debugging and therefore may be tweaked over time.
///
/// This function returns `None` when no name can be found or inferred.
pub fn module_name(&self) -> Option<&str> {
self.module_name.as_deref()
}
/// Returns a descriptive name of the function for this frame, if one is
/// available.
///
/// The name of this function may come from the `name` section of the
/// WebAssembly binary, or wasmtime may try to infer a better name for it if
/// not available, for example the name of the export if it's exported.
///
/// This return value is primarily used for debugging and human-readable
/// purposes for things like traps. Note that the exact return value may be
/// tweaked over time here and isn't guaranteed to be something in
/// particular about a wasm module due to its primary purpose of assisting
/// in debugging.
///
/// This function returns `None` when no name could be inferred.
pub fn func_name(&self) -> Option<&str> {
self.func_name.as_deref()
}
/// Returns the offset within the original wasm module this frame's program
/// counter was at.
///
/// The offset here is the offset from the beginning of the original wasm
/// module to the instruction that this frame points to.
///
/// Note that `None` may be returned if the original module was not
/// compiled with mapping information to yield this information. This is
/// controlled by the
/// [`Config::generate_address_map`](crate::Config::generate_address_map)
/// configuration option.
pub fn module_offset(&self) -> Option<usize> {
Some(self.instr?.file_offset()? as usize)
}
/// Returns the offset from the original wasm module's function to this
/// frame's program counter.
///
/// The offset here is the offset from the beginning of the defining
/// function of this frame (within the wasm module) to the instruction this
/// frame points to.
///
/// Note that `None` may be returned if the original module was not
/// compiled with mapping information to yield this information. This is
/// controlled by the
/// [`Config::generate_address_map`](crate::Config::generate_address_map)
/// configuration option.
pub fn func_offset(&self) -> Option<usize> {
let instr_offset = self.instr?.file_offset()?;
Some((instr_offset - self.func_start.file_offset()?) as usize)
}
/// Returns the debug symbols found, if any, for this function frame.
///
/// When a wasm program is compiled with DWARF debug information then this
/// function may be populated to return symbols which contain extra debug
/// information about a frame including the filename and line number. If no
/// debug information was found or if it was malformed then this will return
/// an empty array.
pub fn symbols(&self) -> &[FrameSymbol] {
&self.symbols
}
}
/// Debug information for a symbol that is attached to a [`FrameInfo`].
///
/// When DWARF debug information is present in a wasm file then this structure
/// can be found on a [`FrameInfo`] and can be used to learn about filenames,
/// line numbers, etc, which are the origin of a function in a stack trace.
#[derive(Debug)]
pub struct FrameSymbol {
name: Option<String>,
file: Option<String>,
line: Option<u32>,
column: Option<u32>,
}
impl FrameSymbol {
/// Returns the function name associated with this symbol.
///
/// Note that this may not be present with malformed debug information, or
/// the debug information may not include it. Also note that the symbol is
/// frequently mangled, so you might need to run some form of demangling
/// over it.
pub fn name(&self) -> Option<&str> {
self.name.as_deref()
}
/// Returns the source code filename this symbol was defined in.
///
/// Note that this may not be present with malformed debug information, or
/// the debug information may not include it.
pub fn file(&self) -> Option<&str> {
self.file.as_deref()
}
/// Returns the 1-indexed source code line number this symbol was defined
/// on.
///
/// Note that this may not be present with malformed debug information, or
/// the debug information may not include it.
pub fn line(&self) -> Option<u32> {
self.line
}
/// Returns the 1-indexed source code column number this symbol was defined
/// on.
///
/// Note that this may not be present with malformed debug information, or
/// the debug information may not include it.
pub fn column(&self) -> Option<u32> {
self.column
}
}