Skip to main content

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