1 //! Stack-walking of a Wasm stack.
2 //!
3 //! A stack walk requires a first and last frame pointer (FP), and it
4 //! only works on code that has been compiled with frame pointers
5 //! enabled (`preserve_frame_pointers` Cranelift option enabled). The
6 //! stack walk follows the singly-linked list of saved frame pointer
7 //! and return address pairs on the stack that is naturally built by
8 //! function prologues.
9 //!
10 //! This crate makes use of the fact that Wasmtime surrounds Wasm
11 //! frames by trampolines both at entry and exit, and is "up the
12 //! stack" from the point doing the unwinding: in other words, host
13 //! code invokes Wasm code via an entry trampoline, that code may call
14 //! other Wasm code, and ultimately it calls back to host code via an
15 //! exit trampoline. That exit trampoline is able to provide the
16 //! "start FP" (FP at exit trampoline) and "end FP" (FP at entry
17 //! trampoline) and this stack-walker can visit all Wasm frames
18 //! active on the stack between those two.
19 //!
20 //! This module provides a visitor interface to frames, but is
21 //! agnostic to the desired use-case or consumer of the frames, and to
22 //! the overall runtime structure.
23 
24 use core::ops::ControlFlow;
25 
26 /// Implementation necessary to unwind the stack, used by `Backtrace`.
27 ///
28 /// # Safety
29 ///
30 /// This trait is `unsafe` because the return values of each function are
31 /// required to be semantically correct when connected to the `visit_frames`
32 /// function below. Incorrect and/or arbitrary values in this trait will cause
33 /// unwinding to segfault or otherwise result in UB.
34 pub unsafe trait Unwind {
35     /// Returns the offset, from the current frame pointer, of where to get to
36     /// the previous frame pointer on the stack.
next_older_fp_from_fp_offset(&self) -> usize37     fn next_older_fp_from_fp_offset(&self) -> usize;
38 
39     /// Returns the offset, from the current frame pointer, of the
40     /// stack pointer of the next older frame.
next_older_sp_from_fp_offset(&self) -> usize41     fn next_older_sp_from_fp_offset(&self) -> usize;
42 
43     /// Load the return address of a frame given the frame pointer for that
44     /// frame.
45     ///
46     /// # Safety
47     ///
48     /// This function is expected to read raw memory from `fp` and thus is not
49     /// safe to operate on any value of `fp` passed in, instead it must be a
50     /// trusted Cranelift-defined frame pointer.
get_next_older_pc_from_fp(&self, fp: usize) -> usize51     unsafe fn get_next_older_pc_from_fp(&self, fp: usize) -> usize;
52 
53     /// Debug assertion that the frame pointer is aligned.
assert_fp_is_aligned(&self, fp: usize)54     fn assert_fp_is_aligned(&self, fp: usize);
55 }
56 
57 /// A stack frame within a Wasm stack trace.
58 #[derive(Debug)]
59 pub struct Frame {
60     /// The program counter in this frame. Because every frame in the
61     /// stack-walk is paused at a call (as we are in host code called
62     /// by Wasm code below these frames), the PC is at the return
63     /// address, i.e., points to the instruction after the call
64     /// instruction.
65     pc: usize,
66     /// The frame pointer value corresponding to this frame.
67     fp: usize,
68 }
69 
70 impl Frame {
71     /// Get this frame's program counter.
pc(&self) -> usize72     pub fn pc(&self) -> usize {
73         self.pc
74     }
75 
76     /// Get this frame's frame pointer.
fp(&self) -> usize77     pub fn fp(&self) -> usize {
78         self.fp
79     }
80 
81     /// Read out a machine-word-sized value at the given offset from
82     /// FP in this frame.
83     ///
84     /// # Safety
85     ///
86     /// Requires that this frame is a valid, active frame. A `Frame`
87     /// provided by `visit_frames()` will be valid for the duration of
88     /// the invoked closure.
89     ///
90     /// Requires that `offset` falls within the size of this
91     /// frame. This ordinarily requires knowledge passed from the
92     /// compiler that produced the running function, e.g., Cranelift.
read_slot_from_fp(&self, offset: isize) -> usize93     pub unsafe fn read_slot_from_fp(&self, offset: isize) -> usize {
94         // SAFETY: we required that this is a valid frame, and that
95         // `offset` is a valid offset within that frame.
96         unsafe { *(self.fp.wrapping_add_signed(offset) as *mut usize) }
97     }
98 }
99 
100 /// A cursor over `Frame`s in a single activation of Wasm.
101 #[derive(Clone)]
102 pub struct FrameCursor {
103     pc: usize,
104     fp: usize,
105     trampoline_fp: usize,
106 }
107 
108 impl FrameCursor {
109     /// Provide a cursor that walks through a contiguous sequence of Wasm
110     /// frames starting with the frame at the given PC and FP and ending
111     /// at `trampoline_fp`. This FP should correspond to that of a
112     /// trampoline that was used to enter the Wasm code.
113     ///
114     /// We require that the initial PC, FP, and `trampoline_fp` values are
115     /// non-null (non-zero).
116     ///
117     /// The returned type is a cursor, not a literal `Iterator`
118     /// implementation, because we do not want to capture the `&dyn
119     /// Unwind` (rather the cursor's `next` method requires the `&dyn
120     /// Unwind` separately, which permits more flexible usage).
121     ///
122     /// # Safety
123     ///
124     /// This function is not safe as `unwind`, `pc`, `fp`, and `trampoline_fp` must
125     /// all be "correct" in that if they're wrong or mistakenly have the wrong value
126     /// then this method may segfault. These values must point to valid Wasmtime
127     /// compiled code which respects the frame pointers that Wasmtime currently
128     /// requires.
129     ///
130     /// The iterator that this function returns *must* be consumed while
131     /// the frames are still active. That is, it cannot be stashed and
132     /// consumed after returning back into the Wasm activation that is
133     /// being iterated over.
134     ///
135     /// Ordinarily this can be ensured by holding the unsafe iterator
136     /// together with a borrow of the `Store` that owns the stack;
137     /// higher-level layers wrap the two together.
new(pc: usize, fp: usize, trampoline_fp: usize) -> FrameCursor138     pub unsafe fn new(pc: usize, fp: usize, trampoline_fp: usize) -> FrameCursor {
139         log::trace!("=== Tracing through contiguous sequence of Wasm frames ===");
140         log::trace!("trampoline_fp = 0x{trampoline_fp:016x}");
141         log::trace!("   initial pc = 0x{pc:016x}");
142         log::trace!("   initial fp = 0x{fp:016x}");
143 
144         // Safety requirements documented above.
145         assert_ne!(pc, 0);
146         assert_ne!(fp, 0);
147         assert_ne!(trampoline_fp, 0);
148 
149         FrameCursor {
150             pc,
151             fp,
152             trampoline_fp,
153         }
154     }
155 
156     /// Get the frame that this cursor currently points at.
frame(&self) -> Frame157     pub fn frame(&self) -> Frame {
158         assert!(!self.done());
159         Frame {
160             pc: self.pc,
161             fp: self.fp,
162         }
163     }
164 
165     /// Returns whether the cursor is "done", i.e., no other frame is
166     /// available in this activation.
done(&self) -> bool167     pub fn done(&self) -> bool {
168         self.fp == self.trampoline_fp
169     }
170 
171     /// Move to the next frame in this activation, if any.
172     ///
173     /// # Safety
174     ///
175     /// The `unwind` passed in must correspond to the host
176     /// implementation from which this stack came.
advance(&mut self, unwind: &dyn Unwind)177     pub unsafe fn advance(&mut self, unwind: &dyn Unwind) {
178         // This logic will walk the linked list of frame pointers starting
179         // at `fp` and going up until `trampoline_fp`. We know that both
180         // `fp` and `trampoline_fp` are "trusted values" aka generated and
181         // maintained by Wasmtime. This means that it should be safe to
182         // walk the linked list of pointers and inspect Wasm frames.
183         //
184         // Note, though, that any frames outside of this range are not
185         // guaranteed to have valid frame pointers. For example native code
186         // might be using the frame pointer as a general purpose register. Thus
187         // we need to be careful to only walk frame pointers in this one
188         // contiguous linked list.
189         //
190         // To know when to stop iteration all architectures' stacks currently
191         // look something like this:
192         //
193         //     | ...               |
194         //     | Native Frames     |
195         //     | ...               |
196         //     |-------------------|
197         //     | ...               | <-- Trampoline FP            |
198         //     | Trampoline Frame  |                              |
199         //     | ...               | <-- Trampoline SP            |
200         //     |-------------------|                            Stack
201         //     | Return Address    |                            Grows
202         //     | Previous FP       | <-- Wasm FP                Down
203         //     | ...               |                              |
204         //     | Cranelift Frames  |                              |
205         //     | ...               |                              V
206         //
207         // The trampoline records its own frame pointer (`trampoline_fp`),
208         // which is guaranteed to be above all Wasm code. To check when
209 
210         // to check when the next frame pointer is equal to
211         // `trampoline_fp`. Once that's hit then we know that the entire
212         // linked list has been traversed.
213         //
214         // Note that it might be possible that this loop doesn't execute
215         // at all.  For example if the entry trampoline called Wasm code
216         // which `return_call`'d an exit trampoline, then `fp ==
217         // trampoline_fp` on the entry of this function, meaning the loop
218         // won't actually execute anything.
219         if self.fp == self.trampoline_fp {
220             log::trace!("=== Done tracing contiguous sequence of Wasm frames ===");
221             return;
222         }
223 
224         // At the start of each iteration of the loop, we know that
225         // `fp` is a frame pointer from Wasm code. Therefore, we know
226         // it is not being used as an extra general-purpose register,
227         // and it is safe dereference to get the PC and the next older
228         // frame pointer.
229         //
230         // The stack also grows down, and therefore any frame pointer
231         // we are dealing with should be less than the frame pointer
232         // on entry to Wasm code. Finally also assert that it's
233         // aligned correctly as an additional sanity check.
234         assert!(
235             self.trampoline_fp > self.fp,
236             "{:#x} > {:#x}",
237             self.trampoline_fp,
238             self.fp
239         );
240         unwind.assert_fp_is_aligned(self.fp);
241 
242         log::trace!("--- Tracing through one Wasm frame ---");
243         log::trace!("pc = {:p}", self.pc as *const ());
244         log::trace!("fp = {:p}", self.fp as *const ());
245 
246         // SAFETY: this unsafe traversal of the linked list on the stack is
247         // reflected in the contract of this function where `pc`, `fp`,
248         // `trampoline_fp`, and `unwind` must all be trusted/correct values.
249         unsafe {
250             self.pc = unwind.get_next_older_pc_from_fp(self.fp);
251 
252             // We rely on this offset being zero for all supported
253             // architectures in
254             // `crates/cranelift/src/component/compiler.s`r when we set
255             // the Wasm exit FP. If this ever changes, we will need to
256             // update that code as well!
257             assert_eq!(unwind.next_older_fp_from_fp_offset(), 0);
258 
259             // Get the next older frame pointer from the current Wasm
260             // frame pointer.
261             let next_older_fp = *(self.fp as *mut usize).add(unwind.next_older_fp_from_fp_offset());
262 
263             // Because the stack always grows down, the older FP must be greater
264             // than the current FP.
265             assert!(
266                 next_older_fp > self.fp,
267                 "{next_older_fp:#x} > {fp:#x}",
268                 fp = self.fp
269             );
270             self.fp = next_older_fp;
271         }
272     }
273 }
274 
275 /// Wrap `FrameCursor` in a true iterator.
276 ///
277 /// This captures the `unwind` borrow for the duration of the
278 /// iterator's lifetime.
279 ///
280 /// # Safety
281 ///
282 /// Safety conditions for this function are the same as for the
283 /// [`FrameCursor::new`] function, plus the condition on `unwind` from
284 /// the [`FrameCursor::advance`] method.
frame_iterator( unwind: &dyn Unwind, pc: usize, fp: usize, trampoline_fp: usize, ) -> impl Iterator<Item = Frame>285 pub unsafe fn frame_iterator(
286     unwind: &dyn Unwind,
287     pc: usize,
288     fp: usize,
289     trampoline_fp: usize,
290 ) -> impl Iterator<Item = Frame> {
291     // SAFETY: our safety conditions include those of
292     // `FrameCursor::new`.
293     let mut cursor = unsafe { FrameCursor::new(pc, fp, trampoline_fp) };
294     core::iter::from_fn(move || {
295         if cursor.done() {
296             None
297         } else {
298             let frame = cursor.frame();
299             // SAFETY: `unwind` is associated with the stack as per
300             // our safety conditions.
301             unsafe {
302                 cursor.advance(unwind);
303             }
304             Some(frame)
305         }
306     })
307 }
308 
309 /// Walk through a contiguous sequence of Wasm frames starting with
310 /// the frame at the given PC and FP and ending at
311 /// `trampoline_fp`. This FP should correspond to that of a trampoline
312 /// that was used to enter the Wasm code.
313 ///
314 /// We require that the initial PC, FP, and `trampoline_fp` values are
315 /// non-null (non-zero).
316 ///
317 /// # Safety
318 ///
319 /// This function is not safe as `unwind`, `pc`, `fp`, and `trampoline_fp` must
320 /// all be "correct" in that if they're wrong or mistakenly have the wrong value
321 /// then this method may segfault. These values must point to valid Wasmtime
322 /// compiled code which respects the frame pointers that Wasmtime currently
323 /// requires.
visit_frames<R>( unwind: &dyn Unwind, pc: usize, fp: usize, trampoline_fp: usize, mut f: impl FnMut(Frame) -> ControlFlow<R>, ) -> ControlFlow<R>324 pub unsafe fn visit_frames<R>(
325     unwind: &dyn Unwind,
326     pc: usize,
327     fp: usize,
328     trampoline_fp: usize,
329     mut f: impl FnMut(Frame) -> ControlFlow<R>,
330 ) -> ControlFlow<R> {
331     let iter = unsafe { frame_iterator(unwind, pc, fp, trampoline_fp) };
332     for frame in iter {
333         f(frame)?;
334     }
335 
336     ControlFlow::Continue(())
337 }
338