gsym-rs 0.1.0

Pure-Rust reader, writer, and Linux ELF/DWARF converter for LLVM GSYM
Documentation
use std::ops::Range;

use crate::endian::{Cursor, Endian};
use crate::error::{Error, Result};
use crate::format::{v1, v2};

#[derive(Clone, Copy, Debug)]
pub(super) enum VersionLayout {
    V1,
    V2,
}

#[derive(Clone, Debug)]
pub(crate) struct ParsedLayout {
    pub(super) version: VersionLayout,
    pub(super) endian: Endian,
    pub(super) address_offset_size: u8,
    pub(super) string_offset_size: u8,
    pub(super) base_address: u64,
    pub(super) address_count: u32,
    pub(super) build_id: Range<usize>,
    pub(super) address_offsets: Range<usize>,
    pub(super) address_info_offsets: Range<usize>,
    pub(super) file_table: Range<usize>,
    pub(super) string_table: Range<usize>,
    pub(super) function_info: Range<usize>,
    pub(super) file_count: u32,
}

pub(super) fn parse(data: &[u8]) -> Result<ParsedLayout> {
    let endian = v1::detect_endian(data)?;
    let mut prefix = Cursor::new(data, endian);
    let _magic = prefix.read_u32()?;
    match prefix.read_u16()? {
        v1::VERSION => parse_v1(data),
        v2::VERSION => parse_v2(data),
        other => Err(Error::UnsupportedVersion(other)),
    }
}

fn parse_v1(data: &[u8]) -> Result<ParsedLayout> {
    let (header, endian) = v1::Header::decode(data)?;
    let layout = header.layout(data.len())?;
    let mut files = Cursor::at(data, endian, layout.file_table_offset)?;
    let file_count = files.read_u32()?;
    let file_size = v1::file_table_size(file_count)?;
    let file_end = layout
        .file_table_offset
        .checked_add(file_size)
        .ok_or(Error::Overflow("v1 file table end"))?;
    if file_end > layout.string_table.start {
        return Err(Error::InvalidFormat("v1 file table overlaps string table"));
    }
    let uuid_size = header.uuid.as_slice().len();
    Ok(ParsedLayout {
        version: VersionLayout::V1,
        endian,
        address_offset_size: header.address_offset_size,
        string_offset_size: v1::STRING_OFFSET_SIZE,
        base_address: header.base_address,
        address_count: header.address_count,
        build_id: 28..28_usize.saturating_add(uuid_size),
        address_offsets: layout.address_offsets,
        address_info_offsets: layout.address_info_offsets,
        file_table: layout.file_table_offset..file_end,
        string_table: layout.string_table,
        function_info: layout.function_info_offset..data.len(),
        file_count,
    })
}

fn parse_v2(data: &[u8]) -> Result<ParsedLayout> {
    let (header, endian) = v2::Header::decode(data)?;
    let directory = v2::parse_directory(data, endian, &header)?;
    let range = |kind| -> Result<Range<usize>> {
        let entry = directory.get(kind).ok_or(Error::MissingSection(kind))?;
        let start =
            usize::try_from(entry.file_offset).map_err(|_| Error::Overflow("section offset"))?;
        let size = usize::try_from(entry.file_size).map_err(|_| Error::Overflow("section size"))?;
        Ok(start
            ..start
                .checked_add(size)
                .ok_or(Error::Overflow("section end"))?)
    };
    let file_table = range(v2::GLOBAL_FILE_TABLE)?;
    let mut file_cursor = Cursor::at(data, endian, file_table.start)?;
    let file_count = file_cursor.read_u32()?;
    if v2::file_table_size(file_count)? != file_table.len() {
        return Err(Error::InvalidFormat(
            "v2 file-table count does not match its size",
        ));
    }
    let build_id = directory
        .get(v2::GLOBAL_UUID)
        .map(|_| range(v2::GLOBAL_UUID))
        .transpose()?
        .unwrap_or(0..0);
    Ok(ParsedLayout {
        version: VersionLayout::V2,
        endian,
        address_offset_size: header.address_offset_size,
        string_offset_size: v2::STRING_OFFSET_SIZE,
        base_address: header.base_address,
        address_count: header.address_count,
        build_id,
        address_offsets: range(v2::GLOBAL_ADDRESS_OFFSETS)?,
        address_info_offsets: range(v2::GLOBAL_ADDRESS_INFO_OFFSETS)?,
        file_table,
        string_table: range(v2::GLOBAL_STRING_TABLE)?,
        function_info: range(v2::GLOBAL_FUNCTION_INFO)?,
        file_count,
    })
}