wasmtime/
trap.rs

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