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 /// 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. 138 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. 157 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. 167 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. 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. 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. 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