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