wasmtime_internal_unwinder/
stackwalk.rs

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
24use 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.
34pub 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.
37    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.
41    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.
51    unsafe fn get_next_older_pc_from_fp(&self, fp: usize) -> usize;
52
53    /// Debug assertion that the frame pointer is aligned.
54    fn assert_fp_is_aligned(&self, fp: usize);
55}
56
57/// A stack frame within a Wasm stack trace.
58#[derive(Debug)]
59pub 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
70impl Frame {
71    /// Get this frame's program counter.
72    pub fn pc(&self) -> usize {
73        self.pc
74    }
75
76    /// Get this frame's frame pointer.
77    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.
93    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/// Provide an iterator that walks through a contiguous sequence of
101/// Wasm frames starting with the frame at the given PC and FP and
102/// ending at `trampoline_fp`. This FP should correspond to that of a
103/// trampoline that was used to enter the Wasm code.
104///
105/// We require that the initial PC, FP, and `trampoline_fp` values are
106/// non-null (non-zero).
107///
108/// # Safety
109///
110/// This function is not safe as `unwind`, `pc`, `fp`, and `trampoline_fp` must
111/// all be "correct" in that if they're wrong or mistakenly have the wrong value
112/// then this method may segfault. These values must point to valid Wasmtime
113/// compiled code which respects the frame pointers that Wasmtime currently
114/// requires.
115///
116/// The iterator that this function returns *must* be consumed while
117/// the frames are still active. That is, it cannot be stashed and
118/// consumed after returning back into the Wasm activation that is
119/// being iterated over.
120///
121/// Ordinarily this can be ensured by holding the unsafe iterator
122/// together with a borrow of the `Store` that owns the stack;
123/// higher-level layers wrap the two together.
124pub unsafe fn frame_iterator(
125    unwind: &dyn Unwind,
126    mut pc: usize,
127    mut fp: usize,
128    trampoline_fp: usize,
129) -> impl Iterator<Item = Frame> {
130    log::trace!("=== Tracing through contiguous sequence of Wasm frames ===");
131    log::trace!("trampoline_fp = 0x{trampoline_fp:016x}");
132    log::trace!("   initial pc = 0x{pc:016x}");
133    log::trace!("   initial fp = 0x{fp:016x}");
134
135    // Safety requirements documented above.
136    assert_ne!(pc, 0);
137    assert_ne!(fp, 0);
138    assert_ne!(trampoline_fp, 0);
139
140    // This loop will walk the linked list of frame pointers starting
141    // at `fp` and going up until `trampoline_fp`. We know that both
142    // `fp` and `trampoline_fp` are "trusted values" aka generated and
143    // maintained by Wasmtime. This means that it should be safe to
144    // walk the linked list of pointers and inspect Wasm frames.
145    //
146    // Note, though, that any frames outside of this range are not
147    // guaranteed to have valid frame pointers. For example native code
148    // might be using the frame pointer as a general purpose register. Thus
149    // we need to be careful to only walk frame pointers in this one
150    // contiguous linked list.
151    //
152    // To know when to stop iteration all architectures' stacks currently
153    // look something like this:
154    //
155    //     | ...               |
156    //     | Native Frames     |
157    //     | ...               |
158    //     |-------------------|
159    //     | ...               | <-- Trampoline FP            |
160    //     | Trampoline Frame  |                              |
161    //     | ...               | <-- Trampoline SP            |
162    //     |-------------------|                            Stack
163    //     | Return Address    |                            Grows
164    //     | Previous FP       | <-- Wasm FP                Down
165    //     | ...               |                              |
166    //     | Cranelift Frames  |                              |
167    //     | ...               |                              V
168    //
169    // The trampoline records its own frame pointer (`trampoline_fp`),
170    // which is guaranteed to be above all Wasm code. To check when
171
172    // to check when the next frame pointer is equal to
173    // `trampoline_fp`. Once that's hit then we know that the entire
174    // linked list has been traversed.
175    //
176    // Note that it might be possible that this loop doesn't execute
177    // at all.  For example if the entry trampoline called Wasm code
178    // which `return_call`'d an exit trampoline, then `fp ==
179    // trampoline_fp` on the entry of this function, meaning the loop
180    // won't actually execute anything.
181    core::iter::from_fn(move || {
182        if fp == trampoline_fp {
183            log::trace!("=== Done tracing contiguous sequence of Wasm frames ===");
184            return None;
185        }
186
187        // At the start of each iteration of the loop, we know that
188        // `fp` is a frame pointer from Wasm code. Therefore, we know
189        // it is not being used as an extra general-purpose register,
190        // and it is safe dereference to get the PC and the next older
191        // frame pointer.
192        //
193        // The stack also grows down, and therefore any frame pointer
194        // we are dealing with should be less than the frame pointer
195        // on entry to Wasm code. Finally also assert that it's
196        // aligned correctly as an additional sanity check.
197        assert!(trampoline_fp > fp, "{trampoline_fp:#x} > {fp:#x}");
198        unwind.assert_fp_is_aligned(fp);
199
200        log::trace!("--- Tracing through one Wasm frame ---");
201        log::trace!("pc = {:p}", pc as *const ());
202        log::trace!("fp = {:p}", fp as *const ());
203
204        let frame = Frame { pc, fp };
205
206        // SAFETY: this unsafe traversal of the linked list on the stack is
207        // reflected in the contract of this function where `pc`, `fp`,
208        // `trampoline_fp`, and `unwind` must all be trusted/correct values.
209        unsafe {
210            pc = unwind.get_next_older_pc_from_fp(fp);
211
212            // We rely on this offset being zero for all supported
213            // architectures in
214            // `crates/cranelift/src/component/compiler.s`r when we set
215            // the Wasm exit FP. If this ever changes, we will need to
216            // update that code as well!
217            assert_eq!(unwind.next_older_fp_from_fp_offset(), 0);
218
219            // Get the next older frame pointer from the current Wasm
220            // frame pointer.
221            let next_older_fp = *(fp as *mut usize).add(unwind.next_older_fp_from_fp_offset());
222
223            // Because the stack always grows down, the older FP must be greater
224            // than the current FP.
225            assert!(next_older_fp > fp, "{next_older_fp:#x} > {fp:#x}");
226            fp = next_older_fp;
227        }
228
229        Some(frame)
230    })
231}
232
233/// Walk through a contiguous sequence of Wasm frames starting with
234/// the frame at the given PC and FP and ending at
235/// `trampoline_fp`. This FP should correspond to that of a trampoline
236/// that was used to enter the Wasm code.
237///
238/// We require that the initial PC, FP, and `trampoline_fp` values are
239/// non-null (non-zero).
240///
241/// # Safety
242///
243/// This function is not safe as `unwind`, `pc`, `fp`, and `trampoline_fp` must
244/// all be "correct" in that if they're wrong or mistakenly have the wrong value
245/// then this method may segfault. These values must point to valid Wasmtime
246/// compiled code which respects the frame pointers that Wasmtime currently
247/// requires.
248pub unsafe fn visit_frames<R>(
249    unwind: &dyn Unwind,
250    pc: usize,
251    fp: usize,
252    trampoline_fp: usize,
253    mut f: impl FnMut(Frame) -> ControlFlow<R>,
254) -> ControlFlow<R> {
255    let iter = unsafe { frame_iterator(unwind, pc, fp, trampoline_fp) };
256    for frame in iter {
257        f(frame)?;
258    }
259
260    ControlFlow::Continue(())
261}