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