wasmtime/runtime/
trap.rs

1#[cfg(feature = "coredump")]
2use super::coredump::WasmCoreDump;
3use crate::prelude::*;
4use crate::store::StoreOpaque;
5use crate::{AsContext, Module};
6use core::fmt;
7use wasmtime_environ::{FilePos, demangle_function_name, demangle_function_name_or_index};
8
9/// Representation of a WebAssembly trap and what caused it to occur.
10///
11/// WebAssembly traps happen explicitly for instructions such as `unreachable`
12/// but can also happen as side effects of other instructions such as `i32.load`
13/// loading an out-of-bounds address. Traps halt the execution of WebAssembly
14/// and cause an error to be returned to the host. This enumeration is a list of
15/// all possible traps that can happen in wasm, in addition to some
16/// Wasmtime-specific trap codes listed here as well.
17///
18/// # Errors in Wasmtime
19///
20/// Error-handling in Wasmtime is primarily done through the
21/// [`anyhow`][mod@anyhow] crate where most results are a
22/// [`Result<T>`](anyhow::Result) which is an alias for [`Result<T,
23/// anyhow::Error>`](std::result::Result). Errors in Wasmtime are represented
24/// with [`anyhow::Error`] which acts as a container for any type of error in
25/// addition to optional context for this error. The "base" error or
26/// [`anyhow::Error::root_cause`] is a [`Trap`] whenever WebAssembly hits a
27/// trap, or otherwise it's whatever the host created the error with when
28/// returning an error for a host call.
29///
30/// Any error which happens while WebAssembly is executing will also, by
31/// default, capture a backtrace of the wasm frames while executing. This
32/// backtrace is represented with a [`WasmBacktrace`] instance and is attached
33/// to the [`anyhow::Error`] return value as a
34/// [`context`](anyhow::Error::context). Inspecting a [`WasmBacktrace`] can be
35/// done with the [`downcast_ref`](anyhow::Error::downcast_ref) function. For
36/// information on this see the [`WasmBacktrace`] documentation.
37///
38/// # Examples
39///
40/// ```
41/// # use wasmtime::*;
42/// # fn main() -> Result<()> {
43/// let engine = Engine::default();
44/// let module = Module::new(
45///     &engine,
46///     r#"
47///         (module
48///             (func (export "trap")
49///                 unreachable)
50///             (func $overflow (export "overflow")
51///                 call $overflow)
52///         )
53///     "#,
54/// )?;
55/// let mut store = Store::new(&engine, ());
56/// let instance = Instance::new(&mut store, &module, &[])?;
57///
58/// let trap = instance.get_typed_func::<(), ()>(&mut store, "trap")?;
59/// let error = trap.call(&mut store, ()).unwrap_err();
60/// assert_eq!(*error.downcast_ref::<Trap>().unwrap(), Trap::UnreachableCodeReached);
61/// assert!(error.root_cause().is::<Trap>());
62///
63/// let overflow = instance.get_typed_func::<(), ()>(&mut store, "overflow")?;
64/// let error = overflow.call(&mut store, ()).unwrap_err();
65/// assert_eq!(*error.downcast_ref::<Trap>().unwrap(), Trap::StackOverflow);
66/// # Ok(())
67/// # }
68/// ```
69pub use wasmtime_environ::Trap;
70
71#[cold] // traps are exceptional, this helps move handling off the main path
72pub(crate) fn from_runtime_box(
73    store: &mut StoreOpaque,
74    runtime_trap: Box<crate::runtime::vm::Trap>,
75) -> Error {
76    let crate::runtime::vm::Trap {
77        reason,
78        backtrace,
79        coredumpstack,
80    } = *runtime_trap;
81    let (mut error, pc) = match reason {
82        // For user-defined errors they're already an `anyhow::Error` so no
83        // conversion is really necessary here, but a `backtrace` may have
84        // been captured so it's attempted to get inserted here.
85        //
86        // If the error is actually a `Trap` then the backtrace is inserted
87        // directly into the `Trap` since there's storage there for it.
88        // Otherwise though this represents a host-defined error which isn't
89        // using a `Trap` but instead some other condition that was fatal to
90        // wasm itself. In that situation the backtrace is inserted as
91        // contextual information on error using `error.context(...)` to
92        // provide useful information to debug with for the embedder/caller,
93        // otherwise the information about what the wasm was doing when the
94        // error was generated would be lost.
95        crate::runtime::vm::TrapReason::User(error) => (error, None),
96        crate::runtime::vm::TrapReason::Jit {
97            pc,
98            faulting_addr,
99            trap,
100        } => {
101            let mut err: Error = trap.into();
102
103            // If a fault address was present, for example with segfaults,
104            // then simultaneously assert that it's within a known linear memory
105            // and additionally translate it to a wasm-local address to be added
106            // as context to the error.
107            if let Some(fault) = faulting_addr.and_then(|addr| store.wasm_fault(pc, addr)) {
108                err = err.context(fault);
109            }
110            (err, Some(pc))
111        }
112        crate::runtime::vm::TrapReason::Wasm(trap_code) => (trap_code.into(), None),
113    };
114
115    if let Some(bt) = backtrace {
116        let bt = WasmBacktrace::from_captured(store, bt, pc);
117        if !bt.wasm_trace.is_empty() {
118            error = error.context(bt);
119        }
120    }
121
122    let _ = &coredumpstack;
123    #[cfg(feature = "coredump")]
124    if let Some(coredump) = coredumpstack {
125        let bt = WasmBacktrace::from_captured(store, coredump.bt, pc);
126        let cd = WasmCoreDump::new(store, bt);
127        error = error.context(cd);
128    }
129
130    error
131}
132
133/// Representation of a backtrace of function frames in a WebAssembly module for
134/// where an error happened.
135///
136/// This structure is attached to the [`anyhow::Error`] returned from many
137/// Wasmtime functions that execute WebAssembly such as [`Instance::new`] or
138/// [`Func::call`]. This can be acquired with the [`anyhow::Error::downcast`]
139/// family of methods to programmatically inspect the backtrace. Otherwise since
140/// it's part of the error returned this will get printed along with the rest of
141/// the error when the error is logged.
142///
143/// Capturing of wasm backtraces can be configured through the
144/// [`Config::wasm_backtrace`](crate::Config::wasm_backtrace) method.
145///
146/// For more information about errors in wasmtime see the documentation of the
147/// [`Trap`] type.
148///
149/// [`Func::call`]: crate::Func::call
150/// [`Instance::new`]: crate::Instance::new
151///
152/// # Examples
153///
154/// ```
155/// # use wasmtime::*;
156/// # fn main() -> Result<()> {
157/// let engine = Engine::default();
158/// let module = Module::new(
159///     &engine,
160///     r#"
161///         (module
162///             (func $start (export "run")
163///                 call $trap)
164///             (func $trap
165///                 unreachable)
166///         )
167///     "#,
168/// )?;
169/// let mut store = Store::new(&engine, ());
170/// let instance = Instance::new(&mut store, &module, &[])?;
171/// let func = instance.get_typed_func::<(), ()>(&mut store, "run")?;
172/// let error = func.call(&mut store, ()).unwrap_err();
173/// let bt = error.downcast_ref::<WasmBacktrace>().unwrap();
174/// let frames = bt.frames();
175/// assert_eq!(frames.len(), 2);
176/// assert_eq!(frames[0].func_name(), Some("trap"));
177/// assert_eq!(frames[1].func_name(), Some("start"));
178/// # Ok(())
179/// # }
180/// ```
181#[derive(Debug)]
182pub struct WasmBacktrace {
183    wasm_trace: Vec<FrameInfo>,
184    hint_wasm_backtrace_details_env: bool,
185    // This is currently only present for the `Debug` implementation for extra
186    // context.
187    _runtime_trace: crate::runtime::vm::Backtrace,
188}
189
190impl WasmBacktrace {
191    /// Captures a trace of the WebAssembly frames on the stack for the
192    /// provided store.
193    ///
194    /// This will return a [`WasmBacktrace`] which holds captured
195    /// [`FrameInfo`]s for each frame of WebAssembly on the call stack of the
196    /// current thread. If no WebAssembly is on the stack then the returned
197    /// backtrace will have no frames in it.
198    ///
199    /// Note that this function will respect the [`Config::wasm_backtrace`]
200    /// configuration option and will return an empty backtrace if that is
201    /// disabled. To always capture a backtrace use the
202    /// [`WasmBacktrace::force_capture`] method.
203    ///
204    /// Also note that this function will only capture frames from the
205    /// specified `store` on the stack, ignoring frames from other stores if
206    /// present.
207    ///
208    /// [`Config::wasm_backtrace`]: crate::Config::wasm_backtrace
209    ///
210    /// # Example
211    ///
212    /// ```
213    /// # use wasmtime::*;
214    /// # fn main() -> Result<()> {
215    /// let engine = Engine::default();
216    /// let module = Module::new(
217    ///     &engine,
218    ///     r#"
219    ///         (module
220    ///             (import "" "" (func $host))
221    ///             (func $foo (export "f") call $bar)
222    ///             (func $bar call $host)
223    ///         )
224    ///     "#,
225    /// )?;
226    ///
227    /// let mut store = Store::new(&engine, ());
228    /// let func = Func::wrap(&mut store, |cx: Caller<'_, ()>| {
229    ///     let trace = WasmBacktrace::capture(&cx);
230    ///     println!("{trace:?}");
231    /// });
232    /// let instance = Instance::new(&mut store, &module, &[func.into()])?;
233    /// let func = instance.get_typed_func::<(), ()>(&mut store, "f")?;
234    /// func.call(&mut store, ())?;
235    /// # Ok(())
236    /// # }
237    /// ```
238    pub fn capture(store: impl AsContext) -> WasmBacktrace {
239        let store = store.as_context();
240        if store.engine().config().wasm_backtrace {
241            Self::force_capture(store)
242        } else {
243            WasmBacktrace {
244                wasm_trace: Vec::new(),
245                hint_wasm_backtrace_details_env: false,
246                _runtime_trace: crate::runtime::vm::Backtrace::empty(),
247            }
248        }
249    }
250
251    /// Unconditionally captures a trace of the WebAssembly frames on the stack
252    /// for the provided store.
253    ///
254    /// Same as [`WasmBacktrace::capture`] except that it disregards the
255    /// [`Config::wasm_backtrace`](crate::Config::wasm_backtrace) setting and
256    /// always captures a backtrace.
257    pub fn force_capture(store: impl AsContext) -> WasmBacktrace {
258        let store = store.as_context();
259        Self::from_captured(store.0, crate::runtime::vm::Backtrace::new(store.0), None)
260    }
261
262    fn from_captured(
263        store: &StoreOpaque,
264        runtime_trace: crate::runtime::vm::Backtrace,
265        trap_pc: Option<usize>,
266    ) -> Self {
267        let mut wasm_trace = Vec::<FrameInfo>::with_capacity(runtime_trace.frames().len());
268        let mut hint_wasm_backtrace_details_env = false;
269        let wasm_backtrace_details_env_used =
270            store.engine().config().wasm_backtrace_details_env_used;
271
272        for frame in runtime_trace.frames() {
273            debug_assert!(frame.pc() != 0);
274
275            // Note that we need to be careful about the pc we pass in
276            // here to lookup frame information. This program counter is
277            // used to translate back to an original source location in
278            // the origin wasm module. If this pc is the exact pc that
279            // the trap happened at, then we look up that pc precisely.
280            // Otherwise backtrace information typically points at the
281            // pc *after* the call instruction (because otherwise it's
282            // likely a call instruction on the stack). In that case we
283            // want to lookup information for the previous instruction
284            // (the call instruction) so we subtract one as the lookup.
285            let pc_to_lookup = if Some(frame.pc()) == trap_pc {
286                frame.pc()
287            } else {
288                frame.pc() - 1
289            };
290
291            // NB: The PC we are looking up _must_ be a Wasm PC since
292            // `crate::runtime::vm::Backtrace` only contains Wasm frames.
293            //
294            // However, consider the case where we have multiple, nested calls
295            // across stores (with host code in between, by necessity, since
296            // only things in the same store can be linked directly together):
297            //
298            //     | ...             |
299            //     | Host            |  |
300            //     +-----------------+  | stack
301            //     | Wasm in store A |  | grows
302            //     +-----------------+  | down
303            //     | Host            |  |
304            //     +-----------------+  |
305            //     | Wasm in store B |  V
306            //     +-----------------+
307            //
308            // In this scenario, the `crate::runtime::vm::Backtrace` will
309            // contain two frames: Wasm in store B followed by Wasm in store
310            // A. But `store.modules()` will only have the module information
311            // for modules instantiated within this store. Therefore, we use `if
312            // let Some(..)` instead of the `unwrap` you might otherwise expect
313            // and we ignore frames from modules that were not registered in
314            // this store's module registry.
315            if let Some((info, module)) = store.modules().lookup_frame_info(pc_to_lookup) {
316                wasm_trace.push(info);
317
318                // If this frame has unparsed debug information and the
319                // store's configuration indicates that we were
320                // respecting the environment variable of whether to
321                // do this then we will print out a helpful note in
322                // `Display` to indicate that more detailed information
323                // in a trap may be available.
324                let has_unparsed_debuginfo = module.compiled_module().has_unparsed_debuginfo();
325                if has_unparsed_debuginfo
326                    && wasm_backtrace_details_env_used
327                    && cfg!(feature = "addr2line")
328                {
329                    hint_wasm_backtrace_details_env = true;
330                }
331            }
332        }
333
334        Self {
335            wasm_trace,
336            _runtime_trace: runtime_trace,
337            hint_wasm_backtrace_details_env,
338        }
339    }
340
341    /// Returns a list of function frames in WebAssembly this backtrace
342    /// represents.
343    pub fn frames(&self) -> &[FrameInfo] {
344        self.wasm_trace.as_slice()
345    }
346}
347
348impl fmt::Display for WasmBacktrace {
349    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350        writeln!(f, "error while executing at wasm backtrace:")?;
351
352        let mut needs_newline = false;
353        for (i, frame) in self.wasm_trace.iter().enumerate() {
354            // Avoid putting a trailing newline on the output
355            if needs_newline {
356                writeln!(f, "")?;
357            } else {
358                needs_newline = true;
359            }
360            let name = frame.module().name().unwrap_or("<unknown>");
361            write!(f, "  {i:>3}: ")?;
362
363            if let Some(offset) = frame.module_offset() {
364                write!(f, "{offset:#8x} - ")?;
365            }
366
367            let write_raw_func_name = |f: &mut fmt::Formatter<'_>| {
368                demangle_function_name_or_index(f, frame.func_name(), frame.func_index() as usize)
369            };
370            if frame.symbols().is_empty() {
371                write!(f, "{name}!")?;
372                write_raw_func_name(f)?;
373            } else {
374                for (i, symbol) in frame.symbols().iter().enumerate() {
375                    if i > 0 {
376                        if needs_newline {
377                            writeln!(f, "")?;
378                        } else {
379                            needs_newline = true;
380                        }
381                        write!(f, "                - ")?;
382                    } else {
383                        // ...
384                    }
385                    match symbol.name() {
386                        Some(name) => demangle_function_name(f, name)?,
387                        None if i == 0 => write_raw_func_name(f)?,
388                        None => write!(f, "<inlined function>")?,
389                    }
390                    if let Some(file) = symbol.file() {
391                        writeln!(f, "")?;
392                        write!(f, "                    at {file}")?;
393                        if let Some(line) = symbol.line() {
394                            write!(f, ":{line}")?;
395                            if let Some(col) = symbol.column() {
396                                write!(f, ":{col}")?;
397                            }
398                        }
399                    }
400                }
401            }
402        }
403        if self.hint_wasm_backtrace_details_env {
404            write!(
405                f,
406                "\nnote: using the `WASMTIME_BACKTRACE_DETAILS=1` \
407                 environment variable may show more debugging information"
408            )?;
409        }
410        Ok(())
411    }
412}
413
414/// Description of a frame in a backtrace for a [`WasmBacktrace`].
415///
416/// Whenever an error happens while WebAssembly is executing a
417/// [`WasmBacktrace`] will be attached to the error returned which can be used
418/// to acquire this `FrameInfo`. For more information see [`WasmBacktrace`].
419#[derive(Debug)]
420pub struct FrameInfo {
421    module: Module,
422    func_index: u32,
423    func_name: Option<String>,
424    func_start: FilePos,
425    instr: Option<FilePos>,
426    symbols: Vec<FrameSymbol>,
427}
428
429impl FrameInfo {
430    /// Fetches frame information about a program counter in a backtrace.
431    ///
432    /// Returns an object if this `pc` is known to this module, or returns `None`
433    /// if no information can be found.
434    pub(crate) fn new(module: Module, text_offset: usize) -> Option<FrameInfo> {
435        let compiled_module = module.compiled_module();
436        let (index, _func_offset) = compiled_module.func_by_text_offset(text_offset)?;
437        let func_start = compiled_module.func_start_srcloc(index);
438        let instr = wasmtime_environ::lookup_file_pos(
439            compiled_module.code_memory().address_map_data(),
440            text_offset,
441        );
442        let index = compiled_module.module().func_index(index);
443        let func_index = index.as_u32();
444        let func_name = compiled_module.func_name(index).map(|s| s.to_string());
445
446        // In debug mode for now assert that we found a mapping for `pc` within
447        // the function, because otherwise something is buggy along the way and
448        // not accounting for all the instructions. This isn't super critical
449        // though so we can omit this check in release mode.
450        //
451        // Note that if the module doesn't even have an address map due to
452        // compilation settings then it's expected that `instr` is `None`.
453        debug_assert!(
454            instr.is_some() || !compiled_module.has_address_map(),
455            "failed to find instruction for {text_offset:#x}"
456        );
457
458        // Use our wasm-relative pc to symbolize this frame. If there's a
459        // symbolication context (dwarf debug info) available then we can try to
460        // look this up there.
461        //
462        // Note that dwarf pcs are code-section-relative, hence the subtraction
463        // from the location of `instr`. Also note that all errors are ignored
464        // here for now since technically wasm modules can always have any
465        // custom section contents.
466        let mut symbols = Vec::new();
467
468        let _ = &mut symbols;
469        #[cfg(feature = "addr2line")]
470        if let Some(s) = &compiled_module.symbolize_context().ok().and_then(|c| c) {
471            if let Some(offset) = instr.and_then(|i| i.file_offset()) {
472                let to_lookup = u64::from(offset) - s.code_section_offset();
473                if let Ok(mut frames) = s.addr2line().find_frames(to_lookup).skip_all_loads() {
474                    while let Ok(Some(frame)) = frames.next() {
475                        symbols.push(FrameSymbol {
476                            name: frame
477                                .function
478                                .as_ref()
479                                .and_then(|l| l.raw_name().ok())
480                                .map(|s| s.to_string()),
481                            file: frame
482                                .location
483                                .as_ref()
484                                .and_then(|l| l.file)
485                                .map(|s| s.to_string()),
486                            line: frame.location.as_ref().and_then(|l| l.line),
487                            column: frame.location.as_ref().and_then(|l| l.column),
488                        });
489                    }
490                }
491            }
492        }
493
494        Some(FrameInfo {
495            module,
496            func_index,
497            func_name,
498            instr,
499            func_start,
500            symbols,
501        })
502    }
503
504    /// Returns the WebAssembly function index for this frame.
505    ///
506    /// This function index is the index in the function index space of the
507    /// WebAssembly module that this frame comes from.
508    pub fn func_index(&self) -> u32 {
509        self.func_index
510    }
511
512    /// Returns the module for this frame.
513    ///
514    /// This is the module who's code was being run in this frame.
515    pub fn module(&self) -> &Module {
516        &self.module
517    }
518
519    /// Returns a descriptive name of the function for this frame, if one is
520    /// available.
521    ///
522    /// The name of this function may come from the `name` section of the
523    /// WebAssembly binary, or wasmtime may try to infer a better name for it if
524    /// not available, for example the name of the export if it's exported.
525    ///
526    /// This return value is primarily used for debugging and human-readable
527    /// purposes for things like traps. Note that the exact return value may be
528    /// tweaked over time here and isn't guaranteed to be something in
529    /// particular about a wasm module due to its primary purpose of assisting
530    /// in debugging.
531    ///
532    /// This function returns `None` when no name could be inferred.
533    pub fn func_name(&self) -> Option<&str> {
534        self.func_name.as_deref()
535    }
536
537    /// Returns the offset within the original wasm module this frame's program
538    /// counter was at.
539    ///
540    /// The offset here is the offset from the beginning of the original wasm
541    /// module to the instruction that this frame points to.
542    ///
543    /// Note that `None` may be returned if the original module was not
544    /// compiled with mapping information to yield this information. This is
545    /// controlled by the
546    /// [`Config::generate_address_map`](crate::Config::generate_address_map)
547    /// configuration option.
548    pub fn module_offset(&self) -> Option<usize> {
549        Some(self.instr?.file_offset()? as usize)
550    }
551
552    /// Returns the offset from the original wasm module's function to this
553    /// frame's program counter.
554    ///
555    /// The offset here is the offset from the beginning of the defining
556    /// function of this frame (within the wasm module) to the instruction this
557    /// frame points to.
558    ///
559    /// Note that `None` may be returned if the original module was not
560    /// compiled with mapping information to yield this information. This is
561    /// controlled by the
562    /// [`Config::generate_address_map`](crate::Config::generate_address_map)
563    /// configuration option.
564    pub fn func_offset(&self) -> Option<usize> {
565        let instr_offset = self.instr?.file_offset()?;
566        Some((instr_offset - self.func_start.file_offset()?) as usize)
567    }
568
569    /// Returns the debug symbols found, if any, for this function frame.
570    ///
571    /// When a wasm program is compiled with DWARF debug information then this
572    /// function may be populated to return symbols which contain extra debug
573    /// information about a frame including the filename and line number. If no
574    /// debug information was found or if it was malformed then this will return
575    /// an empty array.
576    pub fn symbols(&self) -> &[FrameSymbol] {
577        &self.symbols
578    }
579}
580
581/// Debug information for a symbol that is attached to a [`FrameInfo`].
582///
583/// When DWARF debug information is present in a wasm file then this structure
584/// can be found on a [`FrameInfo`] and can be used to learn about filenames,
585/// line numbers, etc, which are the origin of a function in a stack trace.
586#[derive(Debug)]
587pub struct FrameSymbol {
588    name: Option<String>,
589    file: Option<String>,
590    line: Option<u32>,
591    column: Option<u32>,
592}
593
594impl FrameSymbol {
595    /// Returns the function name associated with this symbol.
596    ///
597    /// Note that this may not be present with malformed debug information, or
598    /// the debug information may not include it. Also note that the symbol is
599    /// frequently mangled, so you might need to run some form of demangling
600    /// over it.
601    pub fn name(&self) -> Option<&str> {
602        self.name.as_deref()
603    }
604
605    /// Returns the source code filename this symbol was defined in.
606    ///
607    /// Note that this may not be present with malformed debug information, or
608    /// the debug information may not include it.
609    pub fn file(&self) -> Option<&str> {
610        self.file.as_deref()
611    }
612
613    /// Returns the 1-indexed source code line number this symbol was defined
614    /// on.
615    ///
616    /// Note that this may not be present with malformed debug information, or
617    /// the debug information may not include it.
618    pub fn line(&self) -> Option<u32> {
619        self.line
620    }
621
622    /// Returns the 1-indexed source code column number this symbol was defined
623    /// on.
624    ///
625    /// Note that this may not be present with malformed debug information, or
626    /// the debug information may not include it.
627    pub fn column(&self) -> Option<u32> {
628        self.column
629    }
630}