Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/unwinder/src/stackwalk.rs
3067 views
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.
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)]
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.
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.
124
pub 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.
248
pub 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
}
262
263