simple-frame-rs 0.3.0

Parse SFrame (Simple Frame) stack trace information
Documentation
//! Crate to parse SFrame stack trace information.
//!
//! Usage: Use [SFrameSection::from] to load sframe section content and access
//! its content.
//!
//! Spec: <https://sourceware.org/binutils/docs/sframe-spec.html>

use thiserror::Error;

pub mod v1;
pub mod v2;
pub mod v3;

#[macro_export]
macro_rules! read_binary {
    ($data: expr, $le: expr, $ty: ident, $offset: expr) => {{
        let data_offset = $offset;
        let mut data_bytes: [u8; core::mem::size_of::<$ty>()] = [0; core::mem::size_of::<$ty>()];
        data_bytes.copy_from_slice(&$data[data_offset..data_offset + core::mem::size_of::<$ty>()]);
        if $le {
            $ty::from_le_bytes(data_bytes)
        } else {
            $ty::from_be_bytes(data_bytes)
        }
    }};
}

#[macro_export]
macro_rules! read_struct {
    ($struct: ident, $data: expr, $le: expr, $x: ident, $ty: ident) => {{ read_binary!($data, $le, $ty, core::mem::offset_of!($struct, $x)) }};
}

/// Result type for the crate
pub type SFrameResult<T> = core::result::Result<T, SFrameError>;

/// SFrame section
///
/// Ref: <https://sourceware.org/binutils/docs/sframe-spec.html#SFrame-Section>
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub enum SFrameSection<'a> {
    V1(v1::SFrameSection<'a>),
    V2(v2::SFrameSection<'a>),
    V3(v3::SFrameSection<'a>),
}

/// The magic number for SFrame section: 0xdee2
const SFRAME_MAGIC: u16 = 0xdee2;

/// Raw SFrame Header
///
/// Ref: <https://sourceware.org/binutils/docs/sframe-spec.html#SFrame-Preamble>
#[repr(C, packed)]
struct RawSFramePreamble {
    magic: u16,
    version: u8,
    flags: u8,
}

impl<'a> SFrameSection<'a> {
    /// Print the section in string in the same way as objdump
    pub fn to_string(&self) -> SFrameResult<String> {
        match self {
            SFrameSection::V1(sframe_section) => sframe_section.to_string(),
            SFrameSection::V2(sframe_section) => sframe_section.to_string(),
            SFrameSection::V3(sframe_section) => sframe_section.to_string(),
        }
    }
    /// Parse SFrame section from data
    pub fn from(data: &'a [u8], section_base: u64) -> SFrameResult<SFrameSection<'a>> {
        // parse sframe_header
        if data.len() < core::mem::size_of::<RawSFramePreamble>() {
            return Err(SFrameError::UnexpectedEndOfData);
        }

        // probe magic
        let magic_offset = core::mem::offset_of!(RawSFramePreamble, magic);
        let mut magic_bytes: [u8; 2] = [0; 2];
        magic_bytes.copy_from_slice(&data[magic_offset..magic_offset + 2]);
        let magic_le = u16::from_le_bytes(magic_bytes);
        if magic_le != SFRAME_MAGIC {
            let magic_be = u16::from_be_bytes(magic_bytes);
            if magic_be != SFRAME_MAGIC {
                return Err(SFrameError::InvalidMagic);
            }
        }

        // probe version
        let version_offset = core::mem::offset_of!(RawSFramePreamble, version);
        let version = data[version_offset];
        match version {
            1 => Ok(SFrameSection::V1(v1::SFrameSection::from(
                data,
                section_base,
            )?)),
            2 => Ok(SFrameSection::V2(v2::SFrameSection::from(
                data,
                section_base,
            )?)),
            3 => Ok(SFrameSection::V3(v3::SFrameSection::from(
                data,
                section_base,
            )?)),
            _ => Err(SFrameError::UnsupportedVersion),
        }
    }

    pub fn find_fde(&self, pc: u64) -> SFrameResult<Option<SFrameFDE>> {
        match self {
            SFrameSection::V1(sframe_section) => {
                Ok(sframe_section.find_fde(pc)?.map(SFrameFDE::V1))
            }
            SFrameSection::V2(sframe_section) => {
                Ok(sframe_section.find_fde(pc)?.map(SFrameFDE::V2))
            }
            SFrameSection::V3(sframe_section) => {
                Ok(sframe_section.find_fde(pc)?.map(SFrameFDE::V3))
            }
        }
    }
}

/// SFrame FDE
///
/// Ref: <https://sourceware.org/binutils/docs/sframe-spec.html#SFrame-Function-Descriptor-Entries>
#[derive(Debug, Clone, Copy)]
#[allow(dead_code)]
pub enum SFrameFDE {
    V1(v1::SFrameFDE),
    V2(v2::SFrameFDE),
    V3(v3::SFrameFDE),
}

impl SFrameFDE {
    /// Find FRE entry by pc
    pub fn find_fre(
        &self,
        section: &SFrameSection<'_>,
        pc: u64,
    ) -> SFrameResult<Option<SFrameFRE>> {
        match (self, section) {
            (SFrameFDE::V1(sframe_fde), SFrameSection::V1(sframe_section)) => {
                Ok(sframe_fde.find_fre(sframe_section, pc)?.map(SFrameFRE::V1))
            }
            (SFrameFDE::V2(sframe_fde), SFrameSection::V2(sframe_section)) => {
                Ok(sframe_fde.find_fre(sframe_section, pc)?.map(SFrameFRE::V2))
            }
            (SFrameFDE::V3(sframe_fde), SFrameSection::V3(sframe_section)) => {
                Ok(sframe_fde.find_fre(sframe_section, pc)?.map(SFrameFRE::V3))
            }
            _ => Err(SFrameError::UnsupportedVersion),
        }
    }
}

/// SFrame FRE
///
/// Ref: <https://sourceware.org/binutils/docs/sframe-spec.html#SFrame-Frame-Row-Entries>
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum SFrameFRE {
    V1(v1::SFrameFRE),
    V2(v2::SFrameFRE),
    V3(v3::SFrameFRE),
}

impl SFrameFRE {
    /// Distinguish between SP or FP based CFA recovery.
    pub fn get_cfa_base_reg_id(&self) -> u8 {
        match self {
            SFrameFRE::V1(sframe_fre) => sframe_fre.info.get_cfa_base_reg_id(),
            SFrameFRE::V2(sframe_fre) => sframe_fre.info.get_cfa_base_reg_id(),
            SFrameFRE::V3(sframe_fre) => sframe_fre.info.get_cfa_base_reg_id(),
        }
    }

    /// Get CFA offset against base reg
    pub fn get_cfa_offset(&self, section: &SFrameSection<'_>) -> SFrameResult<Option<i32>> {
        match (self, section) {
            (SFrameFRE::V1(sframe_fre), SFrameSection::V1(sframe_section)) => {
                Ok(sframe_fre.get_cfa_offset(sframe_section))
            }
            (SFrameFRE::V2(sframe_fre), SFrameSection::V2(sframe_section)) => {
                Ok(sframe_fre.get_cfa_offset(sframe_section))
            }
            (SFrameFRE::V3(sframe_fre), SFrameSection::V3(sframe_section)) => {
                Ok(sframe_fre.get_cfa_offset(sframe_section))
            }
            _ => Err(SFrameError::UnsupportedVersion),
        }
    }

    /// Get RA offset against CFA
    pub fn get_ra_offset(&self, section: &SFrameSection<'_>) -> SFrameResult<Option<i32>> {
        match (self, section) {
            (SFrameFRE::V1(sframe_fre), SFrameSection::V1(sframe_section)) => {
                Ok(sframe_fre.get_ra_offset(sframe_section))
            }
            (SFrameFRE::V2(sframe_fre), SFrameSection::V2(sframe_section)) => {
                Ok(sframe_fre.get_ra_offset(sframe_section))
            }
            (SFrameFRE::V3(sframe_fre), SFrameSection::V3(sframe_section)) => {
                Ok(sframe_fre.get_ra_offset(sframe_section))
            }
            _ => Err(SFrameError::UnsupportedVersion),
        }
    }

    /// Get FP offset against CFA
    pub fn get_fp_offset(&self, section: &SFrameSection<'_>) -> SFrameResult<Option<i32>> {
        match (self, section) {
            (SFrameFRE::V1(sframe_fre), SFrameSection::V1(sframe_section)) => {
                Ok(sframe_fre.get_fp_offset(sframe_section))
            }
            (SFrameFRE::V2(sframe_fre), SFrameSection::V2(sframe_section)) => {
                Ok(sframe_fre.get_fp_offset(sframe_section))
            }
            (SFrameFRE::V3(sframe_fre), SFrameSection::V3(sframe_section)) => {
                Ok(sframe_fre.get_fp_offset(sframe_section))
            }
            _ => Err(SFrameError::UnsupportedVersion),
        }
    }
}

/// Error types for the crate
#[derive(Error, Debug)]
pub enum SFrameError {
    /// Propagate core::fmt::Error
    #[error("format error")]
    Fmt(#[from] core::fmt::Error),
    /// Unexpected end of data
    #[error("unexpected end of data")]
    UnexpectedEndOfData,
    /// Invalid magic number
    #[error("invalid magic number")]
    InvalidMagic,
    /// Unsupported version
    #[error("unsupported version")]
    UnsupportedVersion,
    /// Unsupported flags
    #[error("unsupported flags")]
    UnsupportedFlags,
    /// Unsupported ABI
    #[error("unsupported abi")]
    UnsupportedABI,
    /// Unsupported FDE type
    #[error("unsupported fde type")]
    UnsupportedFDEType,
    /// Unsupported FRE type
    #[error("unsupported fre type")]
    UnsupportedFREType,
    /// Unsupported FRE stack offset size
    #[error("unsupported fre stack offset size")]
    UnsupportedFREStackOffsetSize,
    /// Unsupported FRE data word size
    #[error("unsupported fre data word size")]
    UnsupportedFREDataWordSize,
}