diff --git a/benches/thread_eager_init.rs b/benches/thread_eager_init.rs index 9bbfeb0414..dbd5617a6f 100644 --- a/benches/thread_eager_init.rs +++ b/benches/thread_eager_init.rs @@ -78,7 +78,7 @@ fn lazy_thread_instantiate(engine: Engine, module: Module) -> Duration { fn eager_thread_instantiate(engine: Engine, module: Module) -> (Duration, Duration) { thread::spawn(move || { let init_start = Instant::now(); - Engine::tls_eager_initialize().expect("eager init"); + Engine::tls_eager_initialize(); let init_duration = init_start.elapsed(); (init_duration, duration_of_call(&engine, &module)) diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs index 60e8291d66..244f9ffb92 100644 --- a/crates/runtime/src/traphandlers.rs +++ b/crates/runtime/src/traphandlers.rs @@ -132,22 +132,6 @@ pub enum TrapReason { /// A trap raised from a wasm libcall 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`, @@ -213,7 +197,7 @@ impl CallThreadState { } fn with(self, closure: impl FnOnce(&CallThreadState) -> i32) -> Result<(), Box> { - let ret = tls::set(&self, || closure(&self))?; + let ret = tls::set(&self, || closure(&self)); if ret != 0 { Ok(()) } else { @@ -329,7 +313,6 @@ impl Drop for ResetCell<'_, T> { // the caller to the trap site. mod tls { use super::CallThreadState; - use crate::Trap; use std::ptr; pub use raw::Ptr; @@ -350,7 +333,6 @@ mod tls { // these functions are free to be inlined. mod raw { use super::CallThreadState; - use crate::Trap; use std::cell::Cell; use std::ptr; @@ -365,17 +347,17 @@ mod tls { #[cfg_attr(feature = "async", inline(never))] // see module docs #[cfg_attr(not(feature = "async"), inline)] - pub fn replace(val: Ptr) -> Result> { + pub fn replace(val: Ptr) -> Ptr { PTR.with(|p| { // When a new value is configured that means that we may be // entering WebAssembly so check to see if this thread has // performed per-thread initialization for traps. let (prev, initialized) = p.get(); if !initialized { - super::super::sys::lazy_per_thread_init()?; + super::super::sys::lazy_per_thread_init(); } p.set((val, true)); - Ok(prev) + prev }) } @@ -383,15 +365,14 @@ mod tls { /// lazily by the runtime if users do not perform it eagerly. #[cfg_attr(feature = "async", inline(never))] // see module docs #[cfg_attr(not(feature = "async"), inline)] - pub fn initialize() -> Result<(), Box> { + pub fn initialize() { PTR.with(|p| { let (state, initialized) = p.get(); if initialized { - return Ok(()); + return; } - super::super::sys::lazy_per_thread_init()?; + super::super::sys::lazy_per_thread_init(); 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 /// with stack switching found with fibers and async wasmtime. - pub unsafe fn take() -> Result> { + pub unsafe fn take() -> TlsRestore { // 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 // removing ourselves from the call-stack, and in the process we @@ -423,30 +404,29 @@ mod tls { let raw = raw::get(); if !raw.is_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 // to save for restoration. - Ok(TlsRestore(raw)) + TlsRestore(raw) } /// Restores a previous tls state back into this thread's TLS. /// /// This is unsafe because it's intended to only be used within the /// context of stack switching within wasmtime. - pub unsafe fn replace(self) -> Result<(), Box> { + pub unsafe fn replace(self) { // Null case: we aren't in a wasm context, so theres no tls // to restore. if self.0.is_null() { - return Ok(()); + return; } // 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. let prev = raw::get(); assert!((*self.0).prev.get().is_null()); (*self.0).prev.set(prev); - raw::replace(self.0)?; - Ok(()) + raw::replace(self.0); } } @@ -454,21 +434,20 @@ mod tls { /// execution of `closure` any call to `with` will yield `ptr`, unless this /// is recursively called again. #[inline] - pub fn set(state: &CallThreadState, closure: impl FnOnce() -> R) -> Result> { + pub fn set(state: &CallThreadState, closure: impl FnOnce() -> R) -> R { struct Reset<'a>(&'a CallThreadState); impl Drop for Reset<'_> { #[inline] fn drop(&mut self) { - raw::replace(self.0.prev.replace(ptr::null())) - .expect("tls should be previously initialized"); + raw::replace(self.0.prev.replace(ptr::null())); } } - let prev = raw::replace(state)?; + let prev = raw::replace(state); state.prev.set(prev); let _reset = Reset(state); - Ok(closure()) + closure() } /// Returns the last pointer configured with `set` above. Panics if `set` diff --git a/crates/runtime/src/traphandlers/macos.rs b/crates/runtime/src/traphandlers/macos.rs index 1520e95f91..6044eef735 100644 --- a/crates/runtime/src/traphandlers/macos.rs +++ b/crates/runtime/src/traphandlers/macos.rs @@ -33,7 +33,7 @@ #![allow(non_snake_case)] -use crate::traphandlers::{tls, wasmtime_longjmp, Trap}; +use crate::traphandlers::{tls, wasmtime_longjmp}; use mach::exception_types::*; use mach::kern_return::*; 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 /// exception handlers to get registered. #[cold] -pub fn lazy_per_thread_init() -> Result<(), Box> { +pub fn lazy_per_thread_init() { unsafe { assert!(WASMTIME_PORT != MACH_PORT_NULL); let this_thread = mach_thread_self(); @@ -424,5 +424,4 @@ pub fn lazy_per_thread_init() -> Result<(), Box> { mach_port_deallocate(mach_task_self(), this_thread); assert_eq!(kret, KERN_SUCCESS, "failed to set thread exception port"); } - Ok(()) } diff --git a/crates/runtime/src/traphandlers/unix.rs b/crates/runtime/src/traphandlers/unix.rs index 6b43b8da17..dc0131957f 100644 --- a/crates/runtime/src/traphandlers/unix.rs +++ b/crates/runtime/src/traphandlers/unix.rs @@ -1,4 +1,4 @@ -use crate::traphandlers::{tls, wasmtime_longjmp, Trap}; +use crate::traphandlers::{tls, wasmtime_longjmp}; use std::cell::RefCell; use std::io; 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 /// page. #[cold] -pub fn lazy_per_thread_init() -> Result<(), Box> { +pub fn lazy_per_thread_init() { // 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 // most once per-thread. @@ -270,11 +270,10 @@ pub fn lazy_per_thread_init() -> Result<(), Box> { } return STACK.with(|s| { - *s.borrow_mut() = unsafe { allocate_sigaltstack()? }; - Ok(()) + *s.borrow_mut() = unsafe { allocate_sigaltstack() }; }); - unsafe fn allocate_sigaltstack() -> Result, Box> { + unsafe fn allocate_sigaltstack() -> Option { // Check to see if the existing sigaltstack, if it exists, is big // enough. If so we don't need to allocate our own. let mut old_stack = mem::zeroed(); @@ -286,7 +285,7 @@ pub fn lazy_per_thread_init() -> Result<(), Box> { io::Error::last_os_error() ); 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 @@ -301,7 +300,7 @@ pub fn lazy_per_thread_init() -> Result<(), Box> { rustix::mm::ProtFlags::empty(), 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 // with `sigaltstack`. @@ -325,10 +324,10 @@ pub fn lazy_per_thread_init() -> Result<(), Box> { io::Error::last_os_error() ); - Ok(Some(Stack { + Some(Stack { mmap_ptr: ptr, mmap_size: alloc_size, - })) + }) } impl Drop for Stack { diff --git a/crates/runtime/src/traphandlers/windows.rs b/crates/runtime/src/traphandlers/windows.rs index 5ca3037a27..99ca07dc4a 100644 --- a/crates/runtime/src/traphandlers/windows.rs +++ b/crates/runtime/src/traphandlers/windows.rs @@ -1,4 +1,4 @@ -use crate::traphandlers::{tls, wasmtime_longjmp, Trap}; +use crate::traphandlers::{tls, wasmtime_longjmp}; use std::io; use winapi::um::errhandlingapi::*; 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> { +pub fn lazy_per_thread_init() { // Unused on Windows - Ok(()) } diff --git a/crates/wasmtime/src/engine.rs b/crates/wasmtime/src/engine.rs index b0361ad3cd..299e0fea22 100644 --- a/crates/wasmtime/src/engine.rs +++ b/crates/wasmtime/src/engine.rs @@ -1,5 +1,5 @@ use crate::signatures::SignatureRegistry; -use crate::{Config, Trap}; +use crate::Config; use anyhow::Result; use once_cell::sync::OnceCell; #[cfg(feature = "parallel-compilation")] @@ -71,7 +71,7 @@ impl Engine { // Ensure that wasmtime_runtime's signal handlers are configured. This // is the per-program initialization required for handling traps, such // 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(); let registry = SignatureRegistry::new(); @@ -117,8 +117,8 @@ impl Engine { /// on calls into WebAssembly. This is provided for use cases where the /// latency of WebAssembly calls are extra-important, which is not /// necessarily true of all embeddings. - pub fn tls_eager_initialize() -> Result<(), Trap> { - wasmtime_runtime::tls_eager_initialize().map_err(Trap::from_runtime_box) + pub fn tls_eager_initialize() { + wasmtime_runtime::tls_eager_initialize(); } /// Returns the configuration settings that this engine is using. diff --git a/crates/wasmtime/src/func.rs b/crates/wasmtime/src/func.rs index 2a9d8b5e31..88472a6f26 100644 --- a/crates/wasmtime/src/func.rs +++ b/crates/wasmtime/src/func.rs @@ -1241,7 +1241,7 @@ pub(crate) fn invoke_wasm_and_catch_traps( ); exit_wasm(store, exit); store.0.call_hook(CallHook::ReturningFromWasm)?; - result.map_err(Trap::from_runtime_box) + result.map_err(|t| Trap::from_runtime_box(store.0, t)) } } diff --git a/crates/wasmtime/src/lib.rs b/crates/wasmtime/src/lib.rs index 64cf5f84de..c86fbeab10 100644 --- a/crates/wasmtime/src/lib.rs +++ b/crates/wasmtime/src/lib.rs @@ -413,7 +413,7 @@ pub use crate::instance::{Instance, InstancePre}; pub use crate::limits::*; pub use crate::linker::*; pub use crate::memory::*; -pub use crate::module::{FrameInfo, FrameSymbol, Module}; +pub use crate::module::Module; pub use crate::r#ref::ExternRef; #[cfg(feature = "async")] pub use crate::store::CallHookHandler; diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs index 1ee0285583..1d23d8b8d0 100644 --- a/crates/wasmtime/src/module.rs +++ b/crates/wasmtime/src/module.rs @@ -25,7 +25,7 @@ use wasmtime_runtime::{ mod registry; mod serialization; -pub use registry::{FrameInfo, FrameSymbol, GlobalModuleRegistry, ModuleRegistry}; +pub use registry::{is_wasm_trap_pc, ModuleRegistry}; pub use serialization::SerializedModule; /// 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 // appropriately. Note that the corresponding `unregister` happens below // in `Drop for ModuleInner`. - registry::register(engine, &module); + registry::register(&module); Ok(Self { inner: Arc::new(ModuleInner { diff --git a/crates/wasmtime/src/module/registry.rs b/crates/wasmtime/src/module/registry.rs index bd6e48b2c9..de59026b80 100644 --- a/crates/wasmtime/src/module/registry.rs +++ b/crates/wasmtime/src/module/registry.rs @@ -2,19 +2,15 @@ #[cfg(feature = "component-model")] use crate::component::Component; -use crate::{Engine, Module}; +use crate::{FrameInfo, Module}; use std::{ collections::BTreeMap, sync::{Arc, RwLock}, }; -use wasmtime_environ::{EntityRef, FilePos, TrapCode}; +use wasmtime_environ::TrapCode; use wasmtime_jit::CompiledModule; use wasmtime_runtime::{ModuleInfo, VMCallerCheckedAnyfunc, VMTrampoline}; -lazy_static::lazy_static! { - static ref GLOBAL_MODULES: RwLock = Default::default(); -} - /// Used for registering modules with a store. /// /// Note that the primary reason for this registry is to ensure that everything @@ -49,23 +45,23 @@ fn start(module: &Module) -> usize { impl ModuleRegistry { /// Fetches information about a registered module given a program counter value. 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)? { - ModuleOrComponent::Module(m) => Some(m), + (ModuleOrComponent::Module(m), offset) => Some((m, offset)), #[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()?; if pc < *start || *end < pc { return None; } - Some(module) + Some((module, pc - *start)) } /// Registers a new module with the registry. @@ -138,64 +134,21 @@ impl ModuleRegistry { /// Looks up a trampoline from an anyfunc. pub fn lookup_trampoline(&self, anyfunc: &VMCallerCheckedAnyfunc) -> Option { - 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(), #[cfg(feature = "component-model")] ModuleOrComponent::Component(c) => c.signatures(), }; signatures.trampoline(anyfunc.type_index) } -} -// Counterpart to `RegisteredModule`, but stored in the global registry. -struct GlobalRegisteredModule { - start: usize, - module: Arc, - 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); - -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(f: impl FnOnce(&GlobalModuleRegistry) -> R) -> R { - f(&GLOBAL_MODULES.read().unwrap()) + /// Fetches trap information about a program counter in a backtrace. + pub fn lookup_trap_code(&self, pc: usize) -> Option { + let (module, offset) = self.module(pc)?; + wasmtime_environ::lookup_trap_code(module.compiled_module().trap_data(), offset) } /// 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 /// boolean indicates whether the engine used to compile this module is /// 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)?; - module.lookup_frame_info(offset).map(|info| { - ( - info, - module.has_unparsed_debuginfo(), - module.wasm_backtrace_details_env_used, - ) - }) + let info = FrameInfo::new(module, offset)?; + Some((info, module)) } +} - /// Fetches trap information about a program counter in a backtrace. - pub(crate) fn lookup_trap_code(&self, pc: usize) -> Option { - let (module, offset) = self.module(pc)?; - wasmtime_environ::lookup_trap_code(module.module.trap_data(), offset) - } +// 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. +lazy_static::lazy_static! { + static ref GLOBAL_MODULES: RwLock = Default::default(); +} + +type GlobalModuleRegistry = BTreeMap)>; + +/// 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. @@ -232,19 +212,17 @@ impl GlobalModuleRegistry { /// 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 /// is a trap or not. -pub fn register(engine: &Engine, module: &Arc) { +pub fn register(module: &Arc) { let code = module.code(); if code.is_empty() { return; } let start = code.as_ptr() as usize; let end = start + code.len() - 1; - let module = GlobalRegisteredModule { - start, - wasm_backtrace_details_env_used: engine.config().wasm_backtrace_details_env_used, - module: module.clone(), - }; - let prev = GLOBAL_MODULES.write().unwrap().0.insert(end, module); + let prev = GLOBAL_MODULES + .write() + .unwrap() + .insert(end, (start, module.clone())); assert!(prev.is_none()); } @@ -257,238 +235,10 @@ pub fn unregister(module: &Arc) { return; } 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()); } -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 { - 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, - func_index: u32, - func_name: Option, - func_start: FilePos, - instr: Option, - symbols: Vec, -} - -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 { - 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 { - 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, - file: Option, - line: Option, - column: Option, -} - -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 { - 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 { - self.column - } -} - #[test] fn test_frame_info() -> Result<(), anyhow::Error> { use crate::*; @@ -510,24 +260,27 @@ fn test_frame_info() -> Result<(), anyhow::Error> { // Create an instance to ensure the frame information is registered. 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() - ); - } + 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, _) = store + .as_context() + .0 + .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(()) } diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs index 3eae8b4cdb..ce33b6042f 100644 --- a/crates/wasmtime/src/store.rs +++ b/crates/wasmtime/src/store.rs @@ -1159,6 +1159,11 @@ impl StoreOpaque { &mut self.store_data } + #[inline] + pub(crate) fn modules(&self) -> &ModuleRegistry { + &self.modules + } + #[inline] pub(crate) fn modules_mut(&mut self) -> &mut ModuleRegistry { &mut self.modules @@ -1767,9 +1772,9 @@ impl AsyncCx { Poll::Pending => {} } - let before = wasmtime_runtime::TlsRestore::take().map_err(Trap::from_runtime_box)?; + let before = wasmtime_runtime::TlsRestore::take(); let res = (*suspend).suspend(()); - before.replace().map_err(Trap::from_runtime_box)?; + before.replace(); res?; } } diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs index 2fae8457fa..432f14f407 100644 --- a/crates/wasmtime/src/trap.rs +++ b/crates/wasmtime/src/trap.rs @@ -1,9 +1,9 @@ -use crate::module::GlobalModuleRegistry; -use crate::FrameInfo; +use crate::store::StoreOpaque; +use crate::Module; use once_cell::sync::OnceCell; use std::fmt; 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_runtime::Backtrace; @@ -136,44 +136,44 @@ pub(crate) struct TrapBacktrace { } impl TrapBacktrace { - pub fn new(native_trace: Backtrace, trap_pc: Option) -> Self { + pub fn new(store: &StoreOpaque, native_trace: Backtrace, trap_pc: Option) -> Self { let mut wasm_trace = Vec::::new(); 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() { - let pc = frame.ip() as usize; - if pc == 0 { - continue; - } - // Note that we need to be careful about the pc we pass in - // here to lookup frame information. This program counter is - // used to translate back to an original source location in - // the origin wasm module. If this pc is the exact pc that - // the trap happened at, then we look up that pc precisely. - // Otherwise backtrace information typically points at the - // pc *after* the call instruction (because otherwise it's - // likely a call instruction on the stack). In that case we - // want to lookup information for the previous instruction - // (the call instruction) so we subtract one as the lookup. - let pc_to_lookup = if Some(pc) == trap_pc { pc } else { pc - 1 }; - if let Some((info, has_unparsed_debuginfo, wasm_backtrace_details_env_used)) = - registry.lookup_frame_info(pc_to_lookup) - { - wasm_trace.push(info); + for frame in native_trace.frames() { + let pc = frame.ip() as usize; + if pc == 0 { + continue; + } + // Note that we need to be careful about the pc we pass in + // here to lookup frame information. This program counter is + // used to translate back to an original source location in + // the origin wasm module. If this pc is the exact pc that + // the trap happened at, then we look up that pc precisely. + // Otherwise backtrace information typically points at the + // pc *after* the call instruction (because otherwise it's + // likely a call instruction on the stack). In that case we + // want to lookup information for the previous instruction + // (the call instruction) so we subtract one as the lookup. + 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) { + wasm_trace.push(info); - // If this frame has unparsed debug information and the - // store's configuration indicates that we were - // respecting the environment variable of whether to - // do this then we will print out a helpful note in - // `Display` to indicate that more detailed information - // in a trap may be available. - if has_unparsed_debuginfo && wasm_backtrace_details_env_used { - hint_wasm_backtrace_details_env = true; - } + // If this frame has unparsed debug information and the + // store's configuration indicates that we were + // respecting the environment variable of whether to + // do this then we will print out a helpful note in + // `Display` to indicate that more detailed information + // in a trap may be available. + let has_unparsed_debuginfo = module.compiled_module().has_unparsed_debuginfo(); + if has_unparsed_debuginfo && wasm_backtrace_details_env_used { + hint_wasm_backtrace_details_env = true; } } - }); + } + Self { wasm_trace, native_trace, @@ -212,39 +212,36 @@ impl Trap { } #[cold] // see Trap::new - pub(crate) fn from_runtime_box(runtime_trap: Box) -> Self { - Self::from_runtime(*runtime_trap) + pub(crate) fn from_runtime_box( + store: &StoreOpaque, + runtime_trap: Box, + ) -> Self { + Self::from_runtime(store, *runtime_trap) } #[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; match reason { wasmtime_runtime::TrapReason::User(error) => { let trap = Trap::from(error); if let Some(backtrace) = backtrace { - trap.record_backtrace(TrapBacktrace::new(backtrace, None)); + trap.record_backtrace(TrapBacktrace::new(store, backtrace, None)); } trap } wasmtime_runtime::TrapReason::Jit(pc) => { - let code = GlobalModuleRegistry::with(|modules| { - modules - .lookup_trap_code(pc) - .unwrap_or(EnvTrapCode::StackOverflow) - }); - let backtrace = backtrace.map(|bt| TrapBacktrace::new(bt, Some(pc))); + let code = store + .modules() + .lookup_trap_code(pc) + .unwrap_or(EnvTrapCode::StackOverflow); + let backtrace = backtrace.map(|bt| TrapBacktrace::new(store, bt, Some(pc))); Trap::new_wasm(code, backtrace) } 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) } - 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> 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, + func_index: u32, + func_name: Option, + func_start: FilePos, + instr: Option, + symbols: Vec, +} + +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 { + 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 { + 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 { + 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, + file: Option, + line: Option, + column: Option, +} + +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 { + 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 { + self.column + } +}