1 //! Builder for the `ELF_WASMTIME_FRAME_TABLE` ("frame table") section 2 //! in compiled executables. 3 //! 4 //! This section is present only if debug instrumentation is 5 //! enabled. It describes functions, stackslots that carry Wasm state, 6 //! and allows looking up active Wasm frames (including multiple 7 //! frames in one function due to inlining), Wasm local types and Wasm 8 //! operand stack depth in each frame by PC, with offsets to read 9 //! those values off of the state in the stack frame. 10 11 use crate::{ 12 FrameInstPos, FrameStackShape, FrameStateSlotOffset, FrameTableDescriptorIndex, FrameValType, 13 FuncKey, ModulePC, WasmHeapTopType, WasmValType, prelude::*, 14 }; 15 use object::{LittleEndian, U32}; 16 use std::collections::{HashMap, hash_map::Entry}; 17 18 /// Builder for a stackslot descriptor. 19 pub struct FrameStateSlotBuilder { 20 /// Function identifier for this state slot. 21 func_key: FuncKey, 22 23 /// Pointer size for target. 24 pointer_size: u32, 25 26 /// Local types and offsets. 27 locals: Vec<(FrameValType, FrameStateSlotOffset)>, 28 29 /// Stack nodes: (parent, type, offset) tuples. 30 stacks: Vec<(Option<FrameStackShape>, FrameValType, FrameStateSlotOffset)>, 31 32 /// Hashconsing for stack-type nodes. 33 stacks_dedup: 34 HashMap<(Option<FrameStackShape>, FrameValType, FrameStateSlotOffset), FrameStackShape>, 35 36 /// Size of vmctx (one pointer). 37 vmctx_size: u32, 38 39 /// Size of all locals. 40 locals_size: u32, 41 42 /// Maximum size of whole state slot. 43 slot_size: u32, 44 } 45 46 impl From<WasmValType> for FrameValType { from(ty: WasmValType) -> FrameValType47 fn from(ty: WasmValType) -> FrameValType { 48 match ty { 49 WasmValType::I32 => FrameValType::I32, 50 WasmValType::I64 => FrameValType::I64, 51 WasmValType::F32 => FrameValType::F32, 52 WasmValType::F64 => FrameValType::F64, 53 WasmValType::V128 => FrameValType::V128, 54 WasmValType::Ref(r) => match r.heap_type.top() { 55 WasmHeapTopType::Any => FrameValType::AnyRef, 56 WasmHeapTopType::Extern => FrameValType::ExternRef, 57 WasmHeapTopType::Func => FrameValType::FuncRef, 58 WasmHeapTopType::Exn => FrameValType::ExnRef, 59 WasmHeapTopType::Cont => FrameValType::ContRef, 60 }, 61 } 62 } 63 } 64 65 impl FrameStateSlotBuilder { 66 /// Create a new state-slot builder. new(func_key: FuncKey, pointer_size: u32) -> FrameStateSlotBuilder67 pub fn new(func_key: FuncKey, pointer_size: u32) -> FrameStateSlotBuilder { 68 FrameStateSlotBuilder { 69 func_key, 70 pointer_size, 71 locals: vec![], 72 stacks: vec![], 73 stacks_dedup: HashMap::new(), 74 vmctx_size: pointer_size, 75 locals_size: 0, 76 slot_size: pointer_size, 77 } 78 } 79 80 /// Add a local to the state-slot. 81 /// 82 /// Locals must be added in local index order, and must be added 83 /// before any stack shapes are defined. The offset in the state 84 /// slot is returned. add_local(&mut self, ty: FrameValType) -> FrameStateSlotOffset85 pub fn add_local(&mut self, ty: FrameValType) -> FrameStateSlotOffset { 86 // N.B.: the vmctx pointer is always at offset 0, so we add 87 // its size here. 88 let offset = FrameStateSlotOffset(self.vmctx_size + self.locals_size); 89 let size = ty.storage_size(self.pointer_size); 90 self.locals_size += size; 91 self.slot_size += size; 92 self.locals.push((ty, offset)); 93 offset 94 } 95 96 /// Get a local's offset in the state-slot. local_offset(&self, local: u32) -> FrameStateSlotOffset97 pub fn local_offset(&self, local: u32) -> FrameStateSlotOffset { 98 let index = usize::try_from(local).unwrap(); 99 self.locals[index].1 100 } 101 102 /// Push a stack entry. Returns the stack-shape descriptor and the 103 /// offset at which to write the pushed value. push_stack( &mut self, parent: Option<FrameStackShape>, ty: FrameValType, ) -> (FrameStackShape, FrameStateSlotOffset)104 pub fn push_stack( 105 &mut self, 106 parent: Option<FrameStackShape>, 107 ty: FrameValType, 108 ) -> (FrameStackShape, FrameStateSlotOffset) { 109 let offset = parent 110 .map(|parent| { 111 let (_, ty, offset) = self.stacks[parent.index()]; 112 offset.add(ty.storage_size(self.pointer_size)) 113 }) 114 // N.B.: the stack starts at vmctx_size + locals_size, 115 // because the layout puts vmctx first, then locals, then 116 // stack. 117 .unwrap_or(FrameStateSlotOffset(self.vmctx_size + self.locals_size)); 118 119 self.slot_size = core::cmp::max( 120 self.slot_size, 121 offset.0 + ty.storage_size(self.pointer_size), 122 ); 123 124 let shape = match self.stacks_dedup.entry((parent, ty, offset)) { 125 Entry::Occupied(o) => *o.get(), 126 Entry::Vacant(v) => { 127 let shape = FrameStackShape(u32::try_from(self.stacks.len()).unwrap()); 128 self.stacks.push((parent, ty, offset)); 129 *v.insert(shape) 130 } 131 }; 132 133 (shape, offset) 134 } 135 136 /// Get the offset for the top slot in a given stack shape. stack_last_offset(&self, shape: FrameStackShape) -> FrameStateSlotOffset137 pub fn stack_last_offset(&self, shape: FrameStackShape) -> FrameStateSlotOffset { 138 self.stacks[shape.index()].2 139 } 140 141 /// Serialize the frame-slot descriptor so it can be included as 142 /// metadata. serialize(&self) -> Vec<u8>143 pub fn serialize(&self) -> Vec<u8> { 144 // Format (all little-endian): 145 // - func_key: (u32, u32) 146 // - num_locals: u32 147 // - num_stack_shapes: u32 148 // - local_offsets: num_locals times: 149 // - offset: u32 (offset from start of state slot) 150 // - stack_shape_parents: num_stack_shapes times: 151 // - parent_shape: u32 (or u32::MAX for none) 152 // - stack_shape_offsets: num_stack_shapes times: 153 // - offset: u32 (offset from start of state slot for top-of-stack value) 154 // - local_types: num_locals times: 155 // - type: u8 156 // - stack_shape_types: num_stack_shapes times: 157 // - type: u8 (type of top-of-stack value) 158 159 let mut buffer = vec![]; 160 let (func_key_namespace, func_key_index) = self.func_key.into_parts(); 161 buffer.extend_from_slice(&u32::to_le_bytes(func_key_namespace.into_raw())); 162 buffer.extend_from_slice(&u32::to_le_bytes(func_key_index.into_raw())); 163 164 buffer.extend_from_slice(&u32::to_le_bytes(u32::try_from(self.locals.len()).unwrap())); 165 buffer.extend_from_slice(&u32::to_le_bytes(u32::try_from(self.stacks.len()).unwrap())); 166 167 for (_, offset) in &self.locals { 168 buffer.extend_from_slice(&u32::to_le_bytes(offset.0)); 169 } 170 for (parent, _, _) in &self.stacks { 171 let parent = parent.map(|p| p.0).unwrap_or(u32::MAX); 172 buffer.extend_from_slice(&u32::to_le_bytes(parent)); 173 } 174 for (_, _, offset) in &self.stacks { 175 buffer.extend_from_slice(&u32::to_le_bytes(offset.0)); 176 } 177 for (ty, _) in &self.locals { 178 buffer.push(*ty as u8); 179 } 180 for (_, ty, _) in &self.stacks { 181 buffer.push(*ty as u8); 182 } 183 184 buffer 185 } 186 187 /// The total size required for all locals/stack storage. size(&self) -> u32188 pub fn size(&self) -> u32 { 189 self.slot_size 190 } 191 } 192 193 /// Builder for the Frame Table. 194 /// 195 /// Format: 196 /// 197 /// - `num_slot_descriptors`: u32 198 /// - `num_progpoints`: u32 199 /// - `num_breakpoints`: u32 200 /// - `frame_descriptor_pool_length`: u32 201 /// - `progpoint_descriptor_pool_length`: u32 202 /// - `breakpoint_patch_pool_length`: u32 203 /// - `num_slot_descriptors` times: 204 /// - frame descriptor offset: u32 205 /// - length: u32 206 /// - `num_slot_descriptors` times: 207 /// - offset from frame up to FP: u32 208 /// - `num_progpoints` times: 209 /// - PC, from start of text section, position (post/pre): u32 210 /// - encoded as (pc << 1) | post_pre_bit 211 /// - `num_progpoints` times: 212 /// - progpoint descriptor offset: u32 213 /// - `num_breakpoints` times: 214 /// - Wasm PC: u32 (sorted order; may repeat) 215 /// - `num_breakpoints` times: 216 /// - patch offset in text: u32 217 /// - `num_breakpoints` times: 218 /// - end of breakpoint patch data in pool: u32 219 /// (find the start by end of previous; patches are in the 220 /// pool in order and this saves storing redundant start/end values) 221 /// - frame descriptors (format described above; `frame_descriptor_pool_length` bytes) 222 /// - progpoint descriptors (`progpoint_descriptor_pool_length` bytes) 223 /// - each descriptor: sequence of frames 224 /// - Wasm PC: u32 (high bit set to indicate a parent frame) 225 /// - slot descriptor index: u32 226 /// - stack shape index: u32 (or u32::MAX for none) 227 /// - breakpoint patch pool (`breakpoint_patch_pool_length` bytes) 228 /// - freeform slices of machine-code bytes to patch in 229 #[derive(Default)] 230 pub struct FrameTableBuilder { 231 /// (offset, length) pairs into `frame_descriptor_data`, indexed 232 /// by frame descriptor number. 233 frame_descriptor_ranges: Vec<U32<LittleEndian>>, 234 frame_descriptor_data: Vec<u8>, 235 236 /// Offset from frame slot up to FP for each frame descriptor. 237 frame_descriptor_fp_offsets: Vec<U32<LittleEndian>>, 238 239 progpoint_pcs: Vec<U32<LittleEndian>>, 240 progpoint_descriptor_offsets: Vec<U32<LittleEndian>>, 241 progpoint_descriptor_data: Vec<U32<LittleEndian>>, 242 243 breakpoint_pcs: Vec<U32<LittleEndian>>, 244 breakpoint_patch_offsets: Vec<U32<LittleEndian>>, 245 breakpoint_patch_data_ends: Vec<U32<LittleEndian>>, 246 247 breakpoint_patch_data: Vec<u8>, 248 } 249 250 impl FrameTableBuilder { 251 /// Add one frame descriptor. 252 /// 253 /// Returns the frame descriptor index. add_frame_descriptor( &mut self, slot_to_fp_offset: u32, data: &[u8], ) -> FrameTableDescriptorIndex254 pub fn add_frame_descriptor( 255 &mut self, 256 slot_to_fp_offset: u32, 257 data: &[u8], 258 ) -> FrameTableDescriptorIndex { 259 let start = u32::try_from(self.frame_descriptor_data.len()).unwrap(); 260 self.frame_descriptor_data.extend(data.iter().cloned()); 261 let end = u32::try_from(self.frame_descriptor_data.len()).unwrap(); 262 263 let index = FrameTableDescriptorIndex( 264 u32::try_from(self.frame_descriptor_fp_offsets.len()).unwrap(), 265 ); 266 self.frame_descriptor_fp_offsets 267 .push(U32::new(LittleEndian, slot_to_fp_offset)); 268 self.frame_descriptor_ranges 269 .push(U32::new(LittleEndian, start)); 270 self.frame_descriptor_ranges 271 .push(U32::new(LittleEndian, end)); 272 273 index 274 } 275 276 /// Add one program point. add_program_point( &mut self, native_pc: u32, pos: FrameInstPos, frames: &[(ModulePC, FrameTableDescriptorIndex, FrameStackShape)], )277 pub fn add_program_point( 278 &mut self, 279 native_pc: u32, 280 pos: FrameInstPos, 281 // For each frame: module-relative Wasm PC, frame descriptor, 282 // stack shape within the frame descriptor. 283 frames: &[(ModulePC, FrameTableDescriptorIndex, FrameStackShape)], 284 ) { 285 let pc_and_pos = FrameInstPos::encode(native_pc, pos); 286 // If we already have a program point record at this PC, 287 // overwrite it. 288 while let Some(last) = self.progpoint_pcs.last() 289 && last.get(LittleEndian) == pc_and_pos 290 { 291 self.progpoint_pcs.pop(); 292 self.progpoint_descriptor_offsets.pop(); 293 self.progpoint_descriptor_data 294 .truncate(self.progpoint_descriptor_data.len() - 3); 295 } 296 297 let start = u32::try_from(self.progpoint_descriptor_data.len()).unwrap(); 298 self.progpoint_pcs.push(U32::new(LittleEndian, pc_and_pos)); 299 self.progpoint_descriptor_offsets 300 .push(U32::new(LittleEndian, start)); 301 302 for (i, &(wasm_pc, frame_descriptor, stack_shape)) in frames.iter().enumerate() { 303 let wasm_pc_raw = wasm_pc.raw(); 304 debug_assert!(wasm_pc_raw < 0x8000_0000); 305 let not_last = i < (frames.len() - 1); 306 let wasm_pc_raw = wasm_pc_raw | if not_last { 0x8000_0000 } else { 0 }; 307 self.progpoint_descriptor_data 308 .push(U32::new(LittleEndian, wasm_pc_raw)); 309 self.progpoint_descriptor_data 310 .push(U32::new(LittleEndian, frame_descriptor.0)); 311 self.progpoint_descriptor_data 312 .push(U32::new(LittleEndian, stack_shape.0)); 313 } 314 } 315 316 /// Add one breakpoint patch. add_breakpoint_patch( &mut self, wasm_pc: ModulePC, patch_start_native_pc: u32, patch: &[u8], )317 pub fn add_breakpoint_patch( 318 &mut self, 319 wasm_pc: ModulePC, 320 patch_start_native_pc: u32, 321 patch: &[u8], 322 ) { 323 self.breakpoint_pcs 324 .push(U32::new(LittleEndian, wasm_pc.raw())); 325 self.breakpoint_patch_offsets 326 .push(U32::new(LittleEndian, patch_start_native_pc)); 327 self.breakpoint_patch_data.extend(patch.iter().cloned()); 328 let end = u32::try_from(self.breakpoint_patch_data.len()).unwrap(); 329 self.breakpoint_patch_data_ends 330 .push(U32::new(LittleEndian, end)); 331 } 332 333 /// Serialize the framd-table data section, taking a closure to 334 /// consume slices. serialize<F: FnMut(&[u8])>(&mut self, mut f: F)335 pub fn serialize<F: FnMut(&[u8])>(&mut self, mut f: F) { 336 // Pad `frame_descriptor_data` to a multiple of 4 bytes so 337 // `progpoint_descriptor_data` is aligned as well. 338 while self.frame_descriptor_data.len() & 3 != 0 { 339 self.frame_descriptor_data.push(0); 340 } 341 342 let num_frame_descriptors = u32::try_from(self.frame_descriptor_fp_offsets.len()).unwrap(); 343 f(&num_frame_descriptors.to_le_bytes()); 344 let num_prog_points = u32::try_from(self.progpoint_pcs.len()).unwrap(); 345 f(&num_prog_points.to_le_bytes()); 346 let num_breakpoints = u32::try_from(self.breakpoint_pcs.len()).unwrap(); 347 f(&num_breakpoints.to_le_bytes()); 348 349 let frame_descriptor_pool_length = u32::try_from(self.frame_descriptor_data.len()).unwrap(); 350 f(&frame_descriptor_pool_length.to_le_bytes()); 351 let progpoint_descriptor_pool_length = 352 u32::try_from(self.progpoint_descriptor_data.len()).unwrap(); 353 f(&progpoint_descriptor_pool_length.to_le_bytes()); 354 let breakpoint_patch_pool_length = u32::try_from(self.breakpoint_patch_data.len()).unwrap(); 355 f(&breakpoint_patch_pool_length.to_le_bytes()); 356 357 f(object::bytes_of_slice(&self.frame_descriptor_ranges)); 358 f(object::bytes_of_slice(&self.frame_descriptor_fp_offsets)); 359 f(object::bytes_of_slice(&self.progpoint_pcs)); 360 f(object::bytes_of_slice(&self.progpoint_descriptor_offsets)); 361 f(object::bytes_of_slice(&self.breakpoint_pcs)); 362 f(object::bytes_of_slice(&self.breakpoint_patch_offsets)); 363 f(object::bytes_of_slice(&self.breakpoint_patch_data_ends)); 364 f(&self.frame_descriptor_data); 365 f(object::bytes_of_slice(&self.progpoint_descriptor_data)); 366 f(&self.breakpoint_patch_data); 367 } 368 } 369