wasmtime/runtime/
trap.rs

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