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