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