Path: blob/main/crates/environ/src/frame_table.rs
3054 views
//! Frame-table parser and lookup logic.1//!2//! This module contains utilities to interpret the `.wasmtime.frame`3//! section in a compiled artifact as produced by4//! [`crate::compile::FrameTableBuilder`].56use crate::FuncKey;7use alloc::vec::Vec;8use object::{Bytes, LittleEndian, U32Bytes};910/// An index into the table of stack shapes.11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]12pub struct FrameStackShape(pub(crate) u32);13impl FrameStackShape {14pub(crate) fn index(self) -> usize {15usize::try_from(self.0).unwrap()16}1718/// Get the raw stack-shape index suitable for serializing into19/// metadata.20pub fn raw(self) -> u32 {21self.022}2324/// Wrap a raw stack shape index (e.g. from debug tags) into a FrameStackShape.25pub fn from_raw(index: u32) -> FrameStackShape {26FrameStackShape(index)27}28}2930/// An index to a frame descriptor that can be referenced from a31/// program point descriptor.32#[derive(Clone, Copy, Debug)]33pub struct FrameTableDescriptorIndex(pub(crate) u32);34impl FrameTableDescriptorIndex {35fn index(self) -> usize {36usize::try_from(self.0).unwrap()37}38}3940/// A parser for a frame-table section.41///42/// This parser holds slices to the in-memory section data, and is43/// cheap to construct: it reads some header fields but does not44/// interpret or validate content data until queried.45pub struct FrameTable<'a> {46frame_descriptor_ranges: &'a [U32Bytes<LittleEndian>],47frame_descriptor_data: &'a [u8],4849frame_descriptor_fp_offsets: &'a [U32Bytes<LittleEndian>],5051progpoint_pcs: &'a [U32Bytes<LittleEndian>],52progpoint_descriptor_offsets: &'a [U32Bytes<LittleEndian>],53progpoint_descriptor_data: &'a [U32Bytes<LittleEndian>],5455breakpoint_pcs: &'a [U32Bytes<LittleEndian>],56breakpoint_patch_offsets: &'a [U32Bytes<LittleEndian>],57breakpoint_patch_data_ends: &'a [U32Bytes<LittleEndian>],58breakpoint_patch_data: &'a [u8],5960original_text: &'a [u8],61}6263impl<'a> FrameTable<'a> {64/// Parse a frame table section from a byte-slice as produced by65/// [`crate::compile::FrameTableBuilder`].66pub fn parse(data: &'a [u8], original_text: &'a [u8]) -> anyhow::Result<FrameTable<'a>> {67let mut data = Bytes(data);68let num_frame_descriptors = data69.read::<U32Bytes<LittleEndian>>()70.map_err(|_| anyhow::anyhow!("Unable to read frame descriptor count prefix"))?;71let num_frame_descriptors = usize::try_from(num_frame_descriptors.get(LittleEndian))?;72let num_progpoint_descriptors = data73.read::<U32Bytes<LittleEndian>>()74.map_err(|_| anyhow::anyhow!("Unable to read progpoint descriptor count prefix"))?;75let num_progpoint_descriptors =76usize::try_from(num_progpoint_descriptors.get(LittleEndian))?;77let num_breakpoints = data78.read::<U32Bytes<LittleEndian>>()79.map_err(|_| anyhow::anyhow!("Unable to read breakpoint count prefix"))?;80let num_breakpoints = usize::try_from(num_breakpoints.get(LittleEndian))?;8182let frame_descriptor_pool_length = data83.read::<U32Bytes<LittleEndian>>()84.map_err(|_| anyhow::anyhow!("Unable to read frame descriptor pool length"))?;85let frame_descriptor_pool_length =86usize::try_from(frame_descriptor_pool_length.get(LittleEndian))?;87let progpoint_descriptor_pool_length = data88.read::<U32Bytes<LittleEndian>>()89.map_err(|_| anyhow::anyhow!("Unable to read progpoint descriptor pool length"))?;90let progpoint_descriptor_pool_length =91usize::try_from(progpoint_descriptor_pool_length.get(LittleEndian))?;92let breakpoint_patch_pool_length = data93.read::<U32Bytes<LittleEndian>>()94.map_err(|_| anyhow::anyhow!("Unable to read breakpoint patch pool length"))?;95let breakpoint_patch_pool_length =96usize::try_from(breakpoint_patch_pool_length.get(LittleEndian))?;9798let (frame_descriptor_ranges, data) =99object::slice_from_bytes::<U32Bytes<LittleEndian>>(data.0, 2 * num_frame_descriptors)100.map_err(|_| anyhow::anyhow!("Unable to read frame descriptor ranges slice"))?;101let (frame_descriptor_fp_offsets, data) =102object::slice_from_bytes::<U32Bytes<LittleEndian>>(data, num_frame_descriptors)103.map_err(|_| anyhow::anyhow!("Unable to read frame descriptor FP offset slice"))?;104105let (progpoint_pcs, data) =106object::slice_from_bytes::<U32Bytes<LittleEndian>>(data, num_progpoint_descriptors)107.map_err(|_| anyhow::anyhow!("Unable to read progpoint PC slice"))?;108let (progpoint_descriptor_offsets, data) =109object::slice_from_bytes::<U32Bytes<LittleEndian>>(data, num_progpoint_descriptors)110.map_err(|_| anyhow::anyhow!("Unable to read progpoint descriptor offset slice"))?;111let (breakpoint_pcs, data) =112object::slice_from_bytes::<U32Bytes<LittleEndian>>(data, num_breakpoints)113.map_err(|_| anyhow::anyhow!("Unable to read breakpoint PC slice"))?;114let (breakpoint_patch_offsets, data) =115object::slice_from_bytes::<U32Bytes<LittleEndian>>(data, num_breakpoints)116.map_err(|_| anyhow::anyhow!("Unable to read breakpoint patch offsets slice"))?;117let (breakpoint_patch_data_ends, data) =118object::slice_from_bytes::<U32Bytes<LittleEndian>>(data, num_breakpoints)119.map_err(|_| anyhow::anyhow!("Unable to read breakpoint patch data ends slice"))?;120121let (frame_descriptor_data, data) = data122.split_at_checked(frame_descriptor_pool_length)123.ok_or_else(|| anyhow::anyhow!("Unable to read frame descriptor pool"))?;124125let (progpoint_descriptor_data, data) = object::slice_from_bytes::<U32Bytes<LittleEndian>>(126data,127progpoint_descriptor_pool_length,128)129.map_err(|_| anyhow::anyhow!("Unable to read progpoint descriptor pool"))?;130131let (breakpoint_patch_data, _) = data132.split_at_checked(breakpoint_patch_pool_length)133.ok_or_else(|| anyhow::anyhow!("Unable to read breakpoint patch pool"))?;134135Ok(FrameTable {136frame_descriptor_ranges,137frame_descriptor_data,138frame_descriptor_fp_offsets,139progpoint_pcs,140progpoint_descriptor_offsets,141progpoint_descriptor_data,142breakpoint_pcs,143breakpoint_patch_offsets,144breakpoint_patch_data_ends,145breakpoint_patch_data,146original_text,147})148}149150/// Get raw frame descriptor data and slot-to-FP-offset for a151/// given frame descriptor.152pub fn frame_descriptor(153&self,154frame_descriptor: FrameTableDescriptorIndex,155) -> Option<(&'a [u8], u32)> {156let range_start = self157.frame_descriptor_ranges158.get(frame_descriptor.index() * 2)?159.get(LittleEndian);160let range_end = self161.frame_descriptor_ranges162.get(frame_descriptor.index() * 2 + 1)?163.get(LittleEndian);164let range_start = usize::try_from(range_start).unwrap();165let range_end = usize::try_from(range_end).unwrap();166if range_end < range_start || range_end > self.frame_descriptor_data.len() {167return None;168}169let descriptor = &self.frame_descriptor_data[range_start..range_end];170let slot_to_fp_offset = self171.frame_descriptor_fp_offsets172.get(frame_descriptor.index())?173.get(LittleEndian);174Some((descriptor, slot_to_fp_offset))175}176177/// Get frames for the program point at the PC upper-bounded by a178/// given search PC (offset in text section).179pub fn find_program_point(180&self,181search_pc: u32,182search_pos: FrameInstPos,183) -> Option<impl Iterator<Item = (u32, FrameTableDescriptorIndex, FrameStackShape)>> {184let key = FrameInstPos::encode(search_pc, search_pos);185let index = match self186.progpoint_pcs187.binary_search_by_key(&key, |entry| entry.get(LittleEndian))188{189Ok(idx) => idx,190Err(idx) if idx > 0 => idx - 1,191Err(_) => return None,192};193194Some(self.program_point_frame_iter(index))195}196197/// Get all program point records with iterators over198/// corresponding frames for each.199pub fn into_program_points(200self,201) -> impl Iterator<202Item = (203u32,204FrameInstPos,205Vec<(u32, FrameTableDescriptorIndex, FrameStackShape)>,206),207> + 'a {208self.progpoint_pcs.iter().enumerate().map(move |(i, pc)| {209let pc_and_pos = pc.get(LittleEndian);210let (pc, pos) = FrameInstPos::decode(pc_and_pos);211(212pc,213pos,214self.program_point_frame_iter(i).collect::<Vec<_>>(),215)216})217}218219fn program_point_frame_iter(220&self,221index: usize,222) -> impl Iterator<Item = (u32, FrameTableDescriptorIndex, FrameStackShape)> {223let offset =224usize::try_from(self.progpoint_descriptor_offsets[index].get(LittleEndian)).unwrap();225let mut data = &self.progpoint_descriptor_data[offset..];226227core::iter::from_fn(move || {228if data.len() < 3 {229return None;230}231let wasm_pc = data[0].get(LittleEndian);232let frame_descriptor = FrameTableDescriptorIndex(data[1].get(LittleEndian));233let stack_shape = FrameStackShape(data[2].get(LittleEndian));234data = &data[3..];235let not_last = wasm_pc & 0x8000_0000 != 0;236let wasm_pc = wasm_pc & 0x7fff_ffff;237if !not_last {238data = &[];239}240Some((wasm_pc, frame_descriptor, stack_shape))241})242}243244/// For a given breakpoint index, return the patch offset in text,245/// the patch data, and the original data.246fn breakpoint_patch(&self, i: usize) -> FrameTableBreakpointData<'_> {247let patch_pool_start = if i == 0 {2480249} else {250self.breakpoint_patch_data_ends[i - 1].get(LittleEndian)251};252let patch_pool_end = self.breakpoint_patch_data_ends[i].get(LittleEndian);253let patch_pool_start = usize::try_from(patch_pool_start).unwrap();254let patch_pool_end = usize::try_from(patch_pool_end).unwrap();255let len = patch_pool_end - patch_pool_start;256let offset = self.breakpoint_patch_offsets[i].get(LittleEndian);257let offset = usize::try_from(offset).unwrap();258let original_data = &self.original_text[offset..offset + len];259FrameTableBreakpointData {260offset,261enable: &self.breakpoint_patch_data[patch_pool_start..patch_pool_end],262disable: original_data,263}264}265266/// Find a list of breakpoint patches for a given Wasm PC.267pub fn lookup_breakpoint_patches_by_pc(268&self,269pc: u32,270) -> impl Iterator<Item = FrameTableBreakpointData<'_>> + '_ {271// Find *some* entry with a matching Wasm PC. Note that there272// may be multiple entries for one PC.273let range = match self274.breakpoint_pcs275.binary_search_by_key(&pc, |p| p.get(LittleEndian))276{277Ok(mut i) => {278// Scan backward to first index with this PC.279while i > 0 && self.breakpoint_pcs[i - 1].get(LittleEndian) == pc {280i -= 1;281}282283// Scan forward to find the end of the range.284let mut end = i;285while end < self.breakpoint_pcs.len()286&& self.breakpoint_pcs[end].get(LittleEndian) == pc287{288end += 1;289}290291i..end292}293Err(_) => 0..0,294};295296range.map(|i| self.breakpoint_patch(i))297}298299/// Return an iterator over all breakpoint patches.300///301/// Returned tuples are (Wasm PC, breakpoint data).302pub fn breakpoint_patches(303&self,304) -> impl Iterator<Item = (u32, FrameTableBreakpointData<'_>)> + '_ {305self.breakpoint_pcs.iter().enumerate().map(|(i, wasm_pc)| {306let wasm_pc = wasm_pc.get(LittleEndian);307let data = self.breakpoint_patch(i);308(wasm_pc, data)309})310}311}312313/// Data describing how to patch code to enable or disable one314/// breakpoint.315pub struct FrameTableBreakpointData<'a> {316/// Offset in the code image's text section.317pub offset: usize,318/// Code bytes to patch in to enable the breakpoint.319pub enable: &'a [u8],320/// Code bytes to patch in to disable the breakpoint.321pub disable: &'a [u8],322}323324/// An instruction position for a program point.325///326/// We attach debug metadata to a *position* on an offset in the text327/// (code) section, either "post" or "pre". The "post" position328/// logically comes first, and is associated with the instruction that329/// ends at this offset (i.e., the previous instruction). The "pre"330/// position comes next, and is associated with the instruction that331/// begins at this offset (i.e., the next instruction).332///333/// We make this distinction because metadata lookups sometimes occur334/// with a PC that is after the instruction (e.g., the return address335/// after a call instruction), and sometimes at the instruction (e.g.,336/// a trapping PC address). The lookup context will know which one to337/// use -- e.g., when walking the stack, "pre" for a trapping PC and338/// "post" for every frame after that -- so we simply encode it as339/// part of the position and allow searching on it.340///341/// The need for this distinction can be understood by way of an342/// example; say we have:343///344/// ```plain345/// call ...346/// trapping_store ...347/// ```348///349/// where both instructions have debug metadata. We might look up the350/// PC of `trapping_store` once as we walk the stack from within the351/// call (we will get this PC because it is the return address) and352/// once when `trapping_store` itself traps; and we want different353/// metadata in each case.354///355/// An alternative is to universally attach tags to the end offset of356/// an instruction, which allows us to handle return addresses357/// naturally but requires traps to adjust their PC. However, this358/// requires trap handlers to know the length of the trapping359/// instruction, which is not always easy -- in the most general case,360/// on variable-length instruction sets, it requires a full361/// instruction decoder.362#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]363pub enum FrameInstPos {364/// The "post" position at an offset attaches to the instruction365/// that ends at this offset, i.e., came previously.366Post,367/// The "pre" position at an offset attaches to the instruction368/// that begins at this offset, i.e., comes next.369Pre,370}371372impl FrameInstPos {373pub(crate) fn encode(pc: u32, pos: FrameInstPos) -> u32 {374let lsb = match pos {375Self::Post => 0,376Self::Pre => 1,377};378debug_assert!(pc < 0x8000_0000);379(pc << 1) | lsb380}381pub(crate) fn decode(bits: u32) -> (u32, FrameInstPos) {382let pos = match bits & 1 {3830 => Self::Post,3841 => Self::Pre,385_ => unreachable!(),386};387let pc = bits >> 1;388(pc, pos)389}390}391392/// An offset into the state slot.393#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]394pub struct FrameStateSlotOffset(pub(crate) u32);395impl FrameStateSlotOffset {396#[cfg(feature = "compile")]397pub(crate) fn add(self, offset: u32) -> FrameStateSlotOffset {398FrameStateSlotOffset(self.0 + offset)399}400401/// Get the offset into the state stackslot, suitable for use in a402/// `stack_store`/`stack_load` instruction.403pub fn offset(self) -> i32 {404i32::try_from(self.0).unwrap()405}406}407408/// A type stored in a frame.409#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]410#[allow(missing_docs, reason = "self-describing variants")]411pub enum FrameValType {412I32,413I64,414F32,415F64,416V128,417AnyRef,418FuncRef,419ExternRef,420ExnRef,421ContRef,422}423424impl FrameValType {425#[cfg(feature = "compile")]426pub(crate) fn storage_size(&self, pointer_size: u32) -> u32 {427match self {428FrameValType::I32 => 4,429FrameValType::I64 => 8,430FrameValType::F32 => 4,431FrameValType::F64 => 8,432FrameValType::V128 => 16,433FrameValType::AnyRef | FrameValType::ExternRef | FrameValType::ExnRef => 4,434FrameValType::FuncRef => pointer_size,435FrameValType::ContRef => 2 * pointer_size,436}437}438}439440impl From<FrameValType> for u8 {441fn from(value: FrameValType) -> u8 {442match value {443FrameValType::I32 => 0,444FrameValType::I64 => 1,445FrameValType::F32 => 2,446FrameValType::F64 => 3,447FrameValType::V128 => 4,448FrameValType::AnyRef => 5,449FrameValType::FuncRef => 6,450FrameValType::ExternRef => 7,451FrameValType::ExnRef => 8,452FrameValType::ContRef => 9,453}454}455}456457impl TryFrom<u8> for FrameValType {458type Error = anyhow::Error;459fn try_from(value: u8) -> anyhow::Result<Self> {460match value {4610 => Ok(Self::I32),4621 => Ok(Self::I64),4632 => Ok(Self::F32),4643 => Ok(Self::F64),4654 => Ok(Self::V128),4665 => Ok(Self::AnyRef),4676 => Ok(Self::FuncRef),4687 => Ok(Self::ExternRef),4698 => Ok(Self::ExnRef),4709 => Ok(Self::ContRef),471_ => Err(anyhow::anyhow!("Invalid type")),472}473}474}475476/// Parser for a frame state slot descriptor.477///478/// This provides the ability to extract offsets and types for locals479/// and for the stack given a stack shape.480pub struct FrameStateSlot<'a> {481func_key: FuncKey,482local_offsets: &'a [U32Bytes<LittleEndian>],483stack_shape_parents: &'a [U32Bytes<LittleEndian>],484stack_shape_offsets: &'a [U32Bytes<LittleEndian>],485local_types: &'a [u8],486stack_shape_types: &'a [u8],487}488489impl<'a> FrameStateSlot<'a> {490/// Parse a slot descriptor.491///492/// This parses the descriptor bytes as provided by493/// [`FrameTable::frame_descriptor`].494pub fn parse(descriptor: &'a [u8]) -> anyhow::Result<FrameStateSlot<'a>> {495let mut data = Bytes(descriptor);496let func_key_namespace = data497.read::<U32Bytes<LittleEndian>>()498.map_err(|_| anyhow::anyhow!("Unable to read func key namespace"))?499.get(LittleEndian);500let func_key_index = data501.read::<U32Bytes<LittleEndian>>()502.map_err(|_| anyhow::anyhow!("Unable to read func key index"))?503.get(LittleEndian);504let func_key = FuncKey::from_raw_parts(func_key_namespace, func_key_index);505506let num_locals = data507.read::<U32Bytes<LittleEndian>>()508.map_err(|_| anyhow::anyhow!("Unable to read num_locals"))?509.get(LittleEndian);510let num_locals = usize::try_from(num_locals)?;511let num_stack_shapes = data512.read::<U32Bytes<LittleEndian>>()513.map_err(|_| anyhow::anyhow!("Unable to read num_stack_shapes"))?514.get(LittleEndian);515let num_stack_shapes = usize::try_from(num_stack_shapes)?;516517let (local_offsets, data) =518object::slice_from_bytes::<U32Bytes<LittleEndian>>(data.0, num_locals)519.map_err(|_| anyhow::anyhow!("Unable to read local_offsets slice"))?;520let (stack_shape_parents, data) =521object::slice_from_bytes::<U32Bytes<LittleEndian>>(data, num_stack_shapes)522.map_err(|_| anyhow::anyhow!("Unable to read stack_shape_parents slice"))?;523let (stack_shape_offsets, data) =524object::slice_from_bytes::<U32Bytes<LittleEndian>>(data, num_stack_shapes)525.map_err(|_| anyhow::anyhow!("Unable to read stack_shape_offsets slice"))?;526let (local_types, data) = data527.split_at_checked(num_locals)528.ok_or_else(|| anyhow::anyhow!("Unable to read local_types slice"))?;529let (stack_shape_types, _) = data530.split_at_checked(num_stack_shapes)531.ok_or_else(|| anyhow::anyhow!("Unable to read stack_shape_types slice"))?;532533Ok(FrameStateSlot {534func_key,535local_offsets,536stack_shape_parents,537stack_shape_offsets,538local_types,539stack_shape_types,540})541}542543/// Get the FuncKey for the function that produced this frame544/// slot.545pub fn func_key(&self) -> FuncKey {546self.func_key547}548549/// Get the local offsets and types.550pub fn locals(&self) -> impl Iterator<Item = (FrameStateSlotOffset, FrameValType)> {551(0..self.num_locals()).map(|i| self.local(i).unwrap())552}553554/// Get the type and offset for a given local.555pub fn local(&self, index: usize) -> Option<(FrameStateSlotOffset, FrameValType)> {556let offset = FrameStateSlotOffset(self.local_offsets.get(index)?.get(LittleEndian));557let ty = FrameValType::try_from(*self.local_types.get(index)?).expect("Invalid type");558Some((offset, ty))559}560561/// Get the number of locals in the frame.562pub fn num_locals(&self) -> usize {563self.local_offsets.len()564}565566/// Get the offsets and types for operand stack values, from top567/// of stack (most recently pushed) down.568pub fn stack(569&self,570shape: FrameStackShape,571) -> impl Iterator<Item = (FrameStateSlotOffset, FrameValType)> {572fn unpack_option_shape(shape: FrameStackShape) -> Option<FrameStackShape> {573if shape.0 == u32::MAX {574None575} else {576Some(shape)577}578}579580let mut shape = unpack_option_shape(shape);581core::iter::from_fn(move || {582shape.map(|s| {583let parent = FrameStackShape(self.stack_shape_parents[s.index()].get(LittleEndian));584let parent = unpack_option_shape(parent);585let offset =586FrameStateSlotOffset(self.stack_shape_offsets[s.index()].get(LittleEndian));587let ty = FrameValType::try_from(self.stack_shape_types[s.index()])588.expect("Invalid type");589shape = parent;590(offset, ty)591})592})593}594595/// Returns an iterator over all storage in this frame.596pub fn stack_and_locals(597&self,598shape: FrameStackShape,599) -> impl Iterator<Item = (FrameStateSlotOffset, FrameValType)> + '_ {600self.locals().chain(self.stack(shape))601}602}603604605