1 //! Backtrace and stack walking functionality for Wasm.
2 //!
3 //! Walking the Wasm stack is comprised of
4 //!
5 //! 1. identifying sequences of contiguous Wasm frames on the stack
6 //!    (i.e. skipping over native host frames), and
7 //!
8 //! 2. walking the Wasm frames within such a sequence.
9 //!
10 //! To perform (1) we maintain the entry stack pointer (SP) and exit frame
11 //! pointer (FP) and program counter (PC) each time we call into Wasm and Wasm
12 //! calls into the host via trampolines (see
13 //! `crates/wasmtime/src/runtime/vm/trampolines`). The most recent entry is
14 //! stored in `VMStoreContext` and older entries are saved in
15 //! `CallThreadState`. This lets us identify ranges of contiguous Wasm frames on
16 //! the stack.
17 //!
18 //! To solve (2) and walk the Wasm frames within a region of contiguous Wasm
19 //! frames on the stack, we configure Cranelift's `preserve_frame_pointers =
20 //! true` setting. Then we can do simple frame pointer traversal starting at the
21 //! exit FP and stopping once we reach the entry SP (meaning that the next older
22 //! frame is a host frame).
23 
24 use crate::prelude::*;
25 use crate::runtime::store::StoreOpaque;
26 use crate::runtime::vm::stack_switching::VMStackChain;
27 use crate::runtime::vm::{
28     Unwind, VMStoreContext,
29     traphandlers::{CallThreadState, tls},
30 };
31 #[cfg(all(feature = "gc", feature = "stack-switching"))]
32 use crate::vm::stack_switching::{VMContRef, VMStackState};
33 use core::ops::ControlFlow;
34 use wasmtime_unwinder::Frame;
35 #[cfg(feature = "debug")]
36 use wasmtime_unwinder::FrameCursor;
37 
38 /// A WebAssembly stack trace.
39 #[derive(Debug)]
40 pub struct Backtrace(Vec<Frame>);
41 
42 /// One activation: information sufficient to trace an activation on a
43 /// frame as long as that frame remains alive.
44 pub(crate) struct Activation {
45     exit_pc: usize,
46     exit_fp: usize,
47     entry_trampoline_fp: usize,
48 }
49 
50 impl Activation {
51     /// Create a frame cursor starting at the exit frame of this activation.
52     ///
53     /// # Safety
54     ///
55     /// This activation must currently be valid (i.e., execution must
56     /// not have returned into the activation to unwind any frames,
57     /// and the stack must not have been freed).
58     #[cfg(feature = "debug")]
cursor(&self) -> FrameCursor59     pub(crate) unsafe fn cursor(&self) -> FrameCursor {
60         // SAFETY: validity of this activation is ensured by our
61         // safety condition.
62         unsafe { FrameCursor::new(self.exit_pc, self.exit_fp, self.entry_trampoline_fp) }
63     }
64 }
65 
66 impl Backtrace {
67     /// Returns an empty backtrace
empty() -> Backtrace68     pub fn empty() -> Backtrace {
69         Backtrace(Vec::new())
70     }
71 
72     /// Capture the current Wasm stack in a backtrace.
new(store: &StoreOpaque) -> Backtrace73     pub fn new(store: &StoreOpaque) -> Backtrace {
74         let vm_store_context = store.vm_store_context();
75         let unwind = store.unwinder();
76         tls::with(|state| match state {
77             Some(state) => unsafe {
78                 Self::new_with_trap_state(vm_store_context, unwind, state, None)
79             },
80             None => Backtrace(vec![]),
81         })
82     }
83 
84     /// Capture the current Wasm stack trace.
85     ///
86     /// If Wasm hit a trap, and we calling this from the trap handler, then the
87     /// Wasm exit trampoline didn't run, and we use the provided PC and FP
88     /// instead of looking them up in `VMStoreContext`.
new_with_trap_state( vm_store_context: *const VMStoreContext, unwind: &dyn Unwind, state: &CallThreadState, trap_pc_and_fp: Option<(usize, usize)>, ) -> Backtrace89     pub(crate) unsafe fn new_with_trap_state(
90         vm_store_context: *const VMStoreContext,
91         unwind: &dyn Unwind,
92         state: &CallThreadState,
93         trap_pc_and_fp: Option<(usize, usize)>,
94     ) -> Backtrace {
95         let mut frames = vec![];
96         let f = |activation: Activation| unsafe {
97             wasmtime_unwinder::visit_frames(
98                 unwind,
99                 activation.exit_pc,
100                 activation.exit_fp,
101                 activation.entry_trampoline_fp,
102                 |frame| {
103                     frames.push(frame);
104                     ControlFlow::Continue(())
105                 },
106             )
107         };
108         unsafe {
109             Self::trace_with_trap_state(vm_store_context, state, trap_pc_and_fp, f);
110         }
111         Backtrace(frames)
112     }
113 
114     /// Walk the current Wasm stack, calling `f` for each frame we walk.
115     #[cfg(feature = "gc")]
trace(store: &StoreOpaque, mut f: impl FnMut(Frame) -> ControlFlow<()>)116     pub fn trace(store: &StoreOpaque, mut f: impl FnMut(Frame) -> ControlFlow<()>) {
117         let vm_store_context = store.vm_store_context();
118         let unwind = store.unwinder();
119         tls::with(|state| match state {
120             Some(state) => unsafe {
121                 let f = |activation: Activation| {
122                     wasmtime_unwinder::visit_frames(
123                         unwind,
124                         activation.exit_pc,
125                         activation.exit_fp,
126                         activation.entry_trampoline_fp,
127                         &mut f,
128                     )
129                 };
130                 Self::trace_with_trap_state(vm_store_context, state, None, f)
131             },
132             None => {}
133         });
134     }
135 
136     // Walk the stack of the given continuation, which must be suspended, and
137     // all of its parent continuations (if any).
138     #[cfg(all(feature = "gc", feature = "stack-switching"))]
trace_suspended_continuation( store: &StoreOpaque, continuation: &VMContRef, mut f: impl FnMut(Frame) -> ControlFlow<()>, )139     pub fn trace_suspended_continuation(
140         store: &StoreOpaque,
141         continuation: &VMContRef,
142         mut f: impl FnMut(Frame) -> ControlFlow<()>,
143     ) {
144         log::trace!("====== Capturing Backtrace (suspended continuation) ======");
145 
146         assert_eq!(
147             continuation.common_stack_information.state,
148             VMStackState::Suspended
149         );
150 
151         let unwind = store.unwinder();
152 
153         let pc = continuation.stack.control_context_instruction_pointer();
154         let fp = continuation.stack.control_context_frame_pointer();
155         let trampoline_fp = continuation
156             .common_stack_information
157             .limits
158             .last_wasm_entry_fp;
159 
160         unsafe {
161             // FIXME(frank-emrich) Casting from *const to *mut pointer is
162             // terrible, but we won't actually modify any of the continuations
163             // here.
164             let stack_chain =
165                 VMStackChain::Continuation(continuation as *const VMContRef as *mut VMContRef);
166 
167             if let ControlFlow::Break(()) = Self::trace_through_continuations(
168                 stack_chain,
169                 pc,
170                 fp,
171                 trampoline_fp,
172                 |activation| {
173                     wasmtime_unwinder::visit_frames(
174                         unwind,
175                         activation.exit_pc,
176                         activation.exit_fp,
177                         activation.entry_trampoline_fp,
178                         &mut f,
179                     )
180                 },
181             ) {
182                 log::trace!("====== Done Capturing Backtrace (closure break) ======");
183                 return;
184             }
185         }
186 
187         log::trace!("====== Done Capturing Backtrace (reached end of stack chain) ======");
188     }
189 
190     /// Walk the current Wasm stack, calling `f` for each frame we walk.
191     ///
192     /// If Wasm hit a trap, and we calling this from the trap handler, then the
193     /// Wasm exit trampoline didn't run, and we use the provided PC and FP
194     /// instead of looking them up in `VMStoreContext`.
195     ///
196     /// We define "current Wasm stack" here as "all activations
197     /// associated with the given store". That is: if we have a stack like
198     ///
199     /// ```plain
200     ///     host --> (Wasm functions in store A) --> host --> (Wasm functions in store B) --> host
201     ///          --> (Wasm functions in store A) --> host --> call `trace_with_trap_state` with store A
202     /// ```
203     ///
204     /// then we will see the first and third Wasm activations (those
205     /// associated with store A), but not that with store B. In
206     /// essence, activations from another store might as well be some
207     /// other opaque host code; we don't know anything about it.
trace_with_trap_state( vm_store_context: *const VMStoreContext, state: &CallThreadState, trap_pc_and_fp: Option<(usize, usize)>, mut f: impl FnMut(Activation) -> ControlFlow<()>, )208     pub(crate) unsafe fn trace_with_trap_state(
209         vm_store_context: *const VMStoreContext,
210         state: &CallThreadState,
211         trap_pc_and_fp: Option<(usize, usize)>,
212         mut f: impl FnMut(Activation) -> ControlFlow<()>,
213     ) {
214         log::trace!("====== Capturing Backtrace ======");
215 
216         let (last_wasm_exit_pc, last_wasm_exit_fp) = match trap_pc_and_fp {
217             // If we exited Wasm by catching a trap, then the Wasm-to-host
218             // trampoline did not get a chance to save the last Wasm PC and FP,
219             // and we need to use the plumbed-through values instead.
220             Some((pc, fp)) => {
221                 assert!(core::ptr::eq(
222                     vm_store_context,
223                     state.vm_store_context.get().as_ptr()
224                 ));
225                 (pc, fp)
226             }
227             // Either there is no Wasm currently on the stack, or we exited Wasm
228             // through the Wasm-to-host trampoline.
229             None => unsafe {
230                 let pc = *(*vm_store_context).last_wasm_exit_pc.get();
231                 let fp = (*vm_store_context).last_wasm_exit_fp();
232                 (pc, fp)
233             },
234         };
235 
236         let stack_chain = unsafe { (*(*vm_store_context).stack_chain.get()).clone() };
237 
238         // The first value in `activations` is for the most recently running
239         // wasm. We thus provide the stack chain of `first_wasm_state` to
240         // traverse the potential continuation stacks. For the subsequent
241         // activations, we unconditionally use `None` as the corresponding stack
242         // chain. This is justified because only the most recent execution of
243         // wasm may execute off the initial stack (see comments in
244         // `wasmtime::invoke_wasm_and_catch_traps` for details).
245         let activations =
246             core::iter::once((stack_chain, last_wasm_exit_pc, last_wasm_exit_fp, unsafe {
247                 *(*vm_store_context).last_wasm_entry_fp.get()
248             }))
249             .chain(
250                 state
251                     .iter()
252                     .flat_map(|state| state.iter())
253                     .filter(|state| {
254                         core::ptr::eq(vm_store_context, state.vm_store_context.get().as_ptr())
255                     })
256                     .map(|state| unsafe {
257                         (
258                             state.old_stack_chain(),
259                             state.old_last_wasm_exit_pc(),
260                             state.old_last_wasm_exit_fp(),
261                             state.old_last_wasm_entry_fp(),
262                         )
263                     }),
264             )
265             .take_while(|(chain, pc, fp, sp)| {
266                 if *pc == 0 {
267                     debug_assert_eq!(*fp, 0);
268                     debug_assert_eq!(*sp, 0);
269                 } else {
270                     debug_assert_ne!(chain.clone(), VMStackChain::Absent)
271                 }
272                 *pc != 0
273             });
274 
275         for (chain, exit_pc, exit_fp, entry_trampoline_fp) in activations {
276             let res = unsafe {
277                 Self::trace_through_continuations(
278                     chain,
279                     exit_pc,
280                     exit_fp,
281                     entry_trampoline_fp,
282                     &mut f,
283                 )
284             };
285             if let ControlFlow::Break(()) = res {
286                 log::trace!("====== Done Capturing Backtrace (closure break) ======");
287                 return;
288             }
289         }
290 
291         log::trace!("====== Done Capturing Backtrace (reached end of activations) ======");
292     }
293 
294     /// Traces through a sequence of stacks, creating a backtrace for each one,
295     /// beginning at the given `pc` and `fp`.
296     ///
297     /// If `chain` is `InitialStack`, we are tracing through the initial stack,
298     /// and this function behaves like `trace_through_wasm`.
299     /// Otherwise, we can interpret `chain` as a linked list of stacks, which
300     /// ends with the initial stack. We then trace through each of these stacks
301     /// individually, up to (and including) the initial stack.
trace_through_continuations( chain: VMStackChain, exit_pc: usize, exit_fp: usize, entry_trampoline_fp: usize, mut f: impl FnMut(Activation) -> ControlFlow<()>, ) -> ControlFlow<()>302     unsafe fn trace_through_continuations(
303         chain: VMStackChain,
304         exit_pc: usize,
305         exit_fp: usize,
306         entry_trampoline_fp: usize,
307         mut f: impl FnMut(Activation) -> ControlFlow<()>,
308     ) -> ControlFlow<()> {
309         use crate::runtime::vm::stack_switching::{VMContRef, VMStackLimits};
310 
311         // Handle the stack that is currently running (which may be a
312         // continuation or the initial stack).
313         f(Activation {
314             exit_pc,
315             exit_fp,
316             entry_trampoline_fp,
317         })?;
318 
319         // Note that the rest of this function has no effect if `chain` is
320         // `Some(VMStackChain::InitialStack(_))` (i.e., there is only one stack to
321         // trace through: the initial stack)
322 
323         assert_ne!(chain, VMStackChain::Absent);
324         let stack_limits_vec: Vec<*mut VMStackLimits> =
325             unsafe { chain.clone().into_stack_limits_iter().collect() };
326         let continuations_vec: Vec<*mut VMContRef> =
327             unsafe { chain.clone().into_continuation_iter().collect() };
328 
329         // The VMStackLimits of the currently running stack (whether that's a
330         // continuation or the initial stack) contains undefined data, the
331         // information about that stack is saved in the Store's
332         // `VMStoreContext` and handled at the top of this function
333         // already. That's why we ignore `stack_limits_vec[0]`.
334         //
335         // Note that a continuation stack's control context stores
336         // information about how to resume execution *in its parent*. Thus,
337         // we combine the information from continuations_vec[i] with
338         // stack_limits_vec[i + 1] below to get information about a
339         // particular stack.
340         //
341         // There must be exactly one more `VMStackLimits` object than there
342         // are continuations, due to the initial stack having one, too.
343         assert_eq!(stack_limits_vec.len(), continuations_vec.len() + 1);
344 
345         for i in 0..continuations_vec.len() {
346             // The continuation whose control context we want to
347             // access, to get information about how to continue
348             // execution in its parent.
349             let continuation = unsafe { &*continuations_vec[i] };
350 
351             // The stack limits describing the parent of `continuation`.
352             let parent_limits = unsafe { &*stack_limits_vec[i + 1] };
353 
354             // The parent of `continuation` if present not the last in the chain.
355             let parent_continuation = continuations_vec.get(i + 1).map(|&c| unsafe { &*c });
356 
357             let fiber_stack = continuation.fiber_stack();
358             let resume_pc = fiber_stack.control_context_instruction_pointer();
359             let resume_fp = fiber_stack.control_context_frame_pointer();
360 
361             // If the parent is indeed a continuation, we know the
362             // boundaries of its stack and can perform some extra debugging
363             // checks.
364             let parent_stack_range = parent_continuation.and_then(|p| p.fiber_stack().range());
365             parent_stack_range.inspect(|parent_stack_range| {
366                 debug_assert!(parent_stack_range.contains(&resume_fp));
367                 debug_assert!(parent_stack_range.contains(&parent_limits.last_wasm_entry_fp));
368                 debug_assert!(parent_stack_range.contains(&parent_limits.stack_limit));
369             });
370 
371             f(Activation {
372                 exit_pc: resume_pc,
373                 exit_fp: resume_fp,
374                 entry_trampoline_fp: parent_limits.last_wasm_entry_fp,
375             })?;
376         }
377         ControlFlow::Continue(())
378     }
379 
380     /// Capture all Activations reachable from the current point
381     /// within a hostcall.
382     #[cfg(feature = "debug")]
activations(store: &StoreOpaque) -> Vec<Activation>383     pub(crate) fn activations(store: &StoreOpaque) -> Vec<Activation> {
384         let mut activations = vec![];
385         let vm_store_context = store.vm_store_context();
386         tls::with(|state| match state {
387             Some(state) => unsafe {
388                 Self::trace_with_trap_state(vm_store_context, state, None, |act| {
389                     activations.push(act);
390                     ControlFlow::Continue(())
391                 });
392             },
393             None => {}
394         });
395         activations
396     }
397 
398     /// Iterate over the frames inside this backtrace.
frames<'a>( &'a self, ) -> impl ExactSizeIterator<Item = &'a Frame> + DoubleEndedIterator + 'a399     pub fn frames<'a>(
400         &'a self,
401     ) -> impl ExactSizeIterator<Item = &'a Frame> + DoubleEndedIterator + 'a {
402         self.0.iter()
403     }
404 }
405