gsym-rs 0.1.3

Pure-Rust reader, writer, and Linux ELF/DWARF converter for LLVM GSYM
Documentation
use crate::endian::Encoder;
use crate::format::function::EncodedFunction;
use crate::format::{self, align_up};
use crate::{Endian, Error, Result};

use super::WriterOptions;

pub(super) struct EncodedImage {
    pub(super) prefix: Vec<u8>,
    pub(super) function_info: Vec<u8>,
}

impl EncodedImage {
    pub(super) fn into_bytes(mut self) -> Vec<u8> {
        self.prefix.extend_from_slice(&self.function_info);
        self.prefix
    }
}

const AVERAGE_FUNCTION_LEN: usize = 24;

pub(super) fn address_offset_width(maximum: u64) -> u8 {
    if u8::try_from(maximum).is_ok() {
        1
    } else if u16::try_from(maximum).is_ok() {
        2
    } else if u32::try_from(maximum).is_ok() {
        4
    } else {
        8
    }
}

pub(super) fn encode_v1(
    options: &WriterOptions,
    base: u64,
    width: u8,
    files: &[(u64, u64)],
    strings: &[u8],
    functions: &[EncodedFunction],
) -> Result<EncodedImage> {
    if options.build_id.len() > format::v1::MAX_UUID_SIZE {
        return Err(Error::V1BuildIdTooLong {
            size: options.build_id.len(),
        });
    }
    let count = function_count(functions)?;
    let address_end = format::v1::HEADER_SIZE
        .checked_add(checked_table_size(
            functions.len(),
            usize::from(width),
            "v1 address",
        )?)
        .ok_or(Error::Overflow("v1 address table end"))?;
    let info_start = align_up(address_end, 4).ok_or(Error::Overflow("v1 info alignment"))?;
    let info_end = info_start
        .checked_add(checked_table_size(functions.len(), 4, "v1 address-info")?)
        .ok_or(Error::Overflow("v1 address-info table end"))?;
    let file_start = align_up(info_end, 4).ok_or(Error::Overflow("v1 file alignment"))?;
    let file_size = 4_usize
        .checked_add(checked_table_size(files.len(), 8, "v1 file")?)
        .ok_or(Error::Overflow("v1 file table size"))?;
    let string_start = file_start
        .checked_add(file_size)
        .ok_or(Error::Overflow("v1 string offset"))?;
    let function_base = string_start
        .checked_add(strings.len())
        .ok_or(Error::Overflow("v1 FunctionInfo base"))?;
    let (function_offsets, function_bytes) =
        encode_function_section(functions, options.endian, 4, function_base)?;
    let header = format::v1::Header {
        address_offset_size: width,
        base_address: base,
        address_count: count,
        string_table_offset: narrow_v1(string_start, "string-table offset")?,
        string_table_size: narrow_v1(strings.len(), "string-table size")?,
        uuid: format::v1::FixedUuid::new(&options.build_id)?,
    };

    let mut output = Encoder::with_capacity(
        options.endian,
        function_base.saturating_add(function_bytes.len()),
    );
    output.write_bytes(&header.encode(options.endian)?);
    write_address_offsets(&mut output, functions, base, width)?;
    output.align_to(4)?;
    for offset in function_offsets {
        output.write_u32(narrow_v1(offset, "FunctionInfo offset")?);
    }
    output.align_to(4)?;
    write_file_table(&mut output, files, 4)?;
    debug_assert_eq!(output.len(), string_start);
    output.write_bytes(strings);
    debug_assert_eq!(output.len(), function_base);
    Ok(EncodedImage {
        prefix: output.into_inner(),
        function_info: function_bytes,
    })
}

pub(super) fn encode_v2(
    options: &WriterOptions,
    base: u64,
    width: u8,
    files: &[(u64, u64)],
    strings: &[u8],
    functions: &[EncodedFunction],
) -> Result<EncodedImage> {
    let (relative_offsets, function_bytes) =
        encode_function_section(functions, options.endian, 8, 0)?;
    let header = format::v2::Header {
        address_offset_size: width,
        string_table_encoding: format::v2::STRING_TABLE_ENCODING_DEFAULT,
        base_address: base,
        address_count: function_count(functions)?,
    };
    let layout = header.write_layout(
        options.build_id.len(),
        u32::try_from(files.len()).map_err(|_| Error::Limit {
            context: "file count",
            value: files.len() as u64,
            limit: u64::from(u32::MAX),
        })?,
        strings.len(),
        function_bytes.len(),
    )?;

    let mut output = Encoder::with_capacity(
        options.endian,
        layout
            .function_info
            .start
            .saturating_add(function_bytes.len()),
    );
    output.write_bytes(&header.encode(options.endian)?);
    for entry in &layout.directory {
        entry.encode_into(&mut output);
    }
    format::v2::GlobalData {
        section_type: format::v2::GLOBAL_END,
        file_offset: 0,
        file_size: 0,
    }
    .encode_into(&mut output);
    debug_assert_eq!(output.len(), layout.directory_end);
    if let Some(uuid) = &layout.uuid {
        debug_assert_eq!(output.len(), uuid.start);
        output.write_bytes(&options.build_id);
    }
    output.align_to(usize::from(width))?;
    debug_assert_eq!(output.len(), layout.address_offsets.start);
    write_address_offsets(&mut output, functions, base, width)?;
    output.align_to(8)?;
    debug_assert_eq!(output.len(), layout.address_info_offsets.start);
    for offset in relative_offsets {
        output.write_u64(offset as u64);
    }
    output.align_to(4)?;
    debug_assert_eq!(output.len(), layout.file_table.start);
    write_file_table(&mut output, files, 8)?;
    debug_assert_eq!(output.len(), layout.string_table.start);
    output.write_bytes(strings);
    output.align_to(4)?;
    debug_assert_eq!(output.len(), layout.function_info.start);
    debug_assert_eq!(
        output.len().saturating_add(function_bytes.len()),
        layout.file_size
    );
    Ok(EncodedImage {
        prefix: output.into_inner(),
        function_info: function_bytes,
    })
}

fn function_count(functions: &[EncodedFunction]) -> Result<u32> {
    u32::try_from(functions.len()).map_err(|_| Error::Limit {
        context: "function count",
        value: functions.len() as u64,
        limit: u64::from(u32::MAX),
    })
}

fn checked_table_size(count: usize, width: usize, context: &'static str) -> Result<usize> {
    count.checked_mul(width).ok_or(Error::Overflow(context))
}

fn narrow_v1(value: usize, field: &'static str) -> Result<u32> {
    u32::try_from(value).map_err(|_| Error::V1LimitExceeded {
        field,
        value: value as u64,
    })
}

fn encode_function_section(
    functions: &[EncodedFunction],
    endian: Endian,
    string_width: u8,
    absolute_base: usize,
) -> Result<(Vec<usize>, Vec<u8>)> {
    let mut output =
        Encoder::with_capacity(endian, functions.len().saturating_mul(AVERAGE_FUNCTION_LEN));
    let mut offsets = Vec::with_capacity(functions.len());
    for function in functions {
        let absolute = absolute_base
            .checked_add(output.len())
            .ok_or(Error::Overflow("FunctionInfo offset"))?;
        let aligned = align_up(absolute, 4).ok_or(Error::Overflow("FunctionInfo alignment"))?;
        let padding = aligned
            .checked_sub(absolute)
            .ok_or(Error::Overflow("FunctionInfo alignment padding"))?;
        output.write_bytes(
            [0; 3]
                .get(..padding)
                .ok_or(Error::Overflow("FunctionInfo alignment padding"))?,
        );
        offsets.push(aligned);
        format::function::encode_into(function, &mut output, string_width, false)?;
    }
    Ok((offsets, output.into_inner()))
}

fn write_address_offsets(
    output: &mut Encoder,
    functions: &[EncodedFunction],
    base: u64,
    width: u8,
) -> Result<()> {
    output.align_to(usize::from(width))?;
    for function in functions {
        output.write_uint(
            function
                .range
                .start
                .checked_sub(base)
                .ok_or(Error::InvalidModel(
                    "function address precedes the image base",
                ))?,
            width,
        )?;
    }
    Ok(())
}

fn write_file_table(output: &mut Encoder, files: &[(u64, u64)], string_width: u8) -> Result<()> {
    output.write_u32(u32::try_from(files.len()).map_err(|_| Error::Limit {
        context: "file count",
        value: files.len() as u64,
        limit: u64::from(u32::MAX),
    })?);
    for (directory, basename) in files {
        output.write_uint(*directory, string_width)?;
        output.write_uint(*basename, string_width)?;
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::model::AddressRange;

    #[test]
    fn function_records_align_in_absolute_file_space() {
        let functions = [
            EncodedFunction {
                range: AddressRange::new(0x1000, 0x1010),
                name: 1,
                ..EncodedFunction::default()
            },
            EncodedFunction {
                range: AddressRange::new(0x1020, 0x1030),
                name: 1,
                ..EncodedFunction::default()
            },
        ];

        for absolute_base in 0..8 {
            let (offsets, _) =
                encode_function_section(&functions, Endian::Little, 4, absolute_base).unwrap();
            assert_eq!(offsets.len(), functions.len());
            assert!(offsets.iter().all(|offset| offset % 4 == 0));
            assert_eq!(offsets.first().copied(), align_up(absolute_base, 4));
        }
    }
}