gsym-rs 0.1.6

Pure-Rust reader, writer, and Linux ELF/DWARF converter for LLVM GSYM
Documentation
use crate::endian::{Encoder, Endian};
use crate::error::Error;
use crate::format::{GSYM_MAGIC, v1, v2};

use super::{ENDIANS, patch_byte, patch_bytes};

#[test]
fn v1_headers_match_reference_fields_endianness_and_errors() {
    let header = v1::Header {
        address_offset_size: 4,
        base_address: 0x1000,
        address_count: 1,
        string_table_offset: 0x2000,
        string_table_size: 0x1000,
        uuid: v1::FixedUuid::new(&(0_u8..16).collect::<Vec<_>>()).unwrap(),
    };
    for endian in ENDIANS {
        let bytes = header.encode(endian).unwrap();
        assert_eq!(bytes.len(), v1::HEADER_SIZE);
        assert_eq!(
            bytes.split_at(4).0,
            if endian == Endian::Little {
                b"MYSG"
            } else {
                b"GSYM"
            }
        );
        assert_eq!(
            v1::Header::decode(&bytes).unwrap(),
            (header.clone(), endian)
        );
    }

    let mut invalid = header.clone();
    invalid.address_offset_size = 3;
    assert!(matches!(
        invalid.encode(Endian::Little),
        Err(Error::InvalidAddressOffsetSize {
            version: 1,
            size: 3
        })
    ));
    assert!(matches!(
        v1::FixedUuid::new(&[0; v1::MAX_UUID_SIZE + 1]),
        Err(Error::V1BuildIdTooLong { size: 21 })
    ));

    for length in 0..v1::HEADER_SIZE {
        let encoded = header.encode(Endian::Little).unwrap();
        assert!(v1::Header::decode(encoded.split_at(length).0).is_err());
    }
    let mut bad_version = header.encode(Endian::Little).unwrap();
    patch_bytes(&mut bad_version, 4, &12_u16.to_le_bytes());
    assert!(matches!(
        v1::Header::decode(&bad_version),
        Err(Error::UnsupportedVersion(12))
    ));

    let mut bad_magic = header.encode(Endian::Little).unwrap();
    patch_bytes(&mut bad_magic, 0, &12_u32.to_le_bytes());
    assert!(matches!(
        v1::Header::decode(&bad_magic),
        Err(Error::InvalidMagic(12))
    ));

    let mut bad_width = header.encode(Endian::Little).unwrap();
    patch_byte(&mut bad_width, 6, 12);
    assert!(matches!(
        v1::Header::decode(&bad_width),
        Err(Error::InvalidAddressOffsetSize {
            version: 1,
            size: 12
        })
    ));

    let mut bad_uuid = header.encode(Endian::Little).unwrap();
    patch_byte(&mut bad_uuid, 7, 128);
    assert!(matches!(
        v1::Header::decode(&bad_uuid),
        Err(Error::InvalidUuidSize(128))
    ));
}

#[test]
fn v1_layout_matches_reference_tables_and_rejects_bad_string_ranges() {
    let header = v1::Header {
        address_offset_size: 2,
        base_address: 0,
        address_count: 11,
        string_table_offset: 136,
        string_table_size: 167,
        uuid: v1::FixedUuid::default(),
    };
    let layout = header.layout(400).unwrap();
    assert_eq!(layout.address_offsets, 48..70);
    assert_eq!(layout.address_info_offsets, 72..116);
    assert_eq!(layout.file_table_offset, 116);
    assert_eq!(layout.string_table, 136..303);
    assert_eq!(layout.function_info_offset, 303);

    let mut overlapping = header.clone();
    overlapping.string_table_offset = 100;
    assert!(overlapping.layout(400).is_err());
    let mut truncated = header;
    truncated.string_table_offset = 390;
    assert!(truncated.layout(400).is_err());
}

#[test]
fn v2_headers_accept_supported_widths_and_reject_invalid_fields() {
    for endian in ENDIANS {
        for width in 1..=8 {
            let header = v2::Header {
                address_offset_size: width,
                string_table_encoding: 0,
                base_address: 0x1000,
                address_count: 1,
            };
            let bytes = header.encode(endian).unwrap();
            assert_eq!(bytes.len(), v2::HEADER_SIZE);
            assert_eq!(v2::Header::decode(&bytes).unwrap(), (header, endian));
        }
    }

    let invalid_width = v2::Header {
        address_offset_size: 0,
        string_table_encoding: 0,
        base_address: 0,
        address_count: 1,
    };
    assert!(matches!(
        invalid_width.encode(Endian::Little),
        Err(Error::InvalidAddressOffsetSize {
            version: 2,
            size: 0
        })
    ));
    let invalid_encoding = v2::Header {
        address_offset_size: 4,
        string_table_encoding: 12,
        base_address: 0,
        address_count: 1,
    };
    assert!(matches!(
        invalid_encoding.encode(Endian::Little),
        Err(Error::UnsupportedStringTableEncoding(12))
    ));

    for length in 0..v2::HEADER_SIZE {
        assert!(v2::Header::decode([0; v2::HEADER_SIZE].get(..length).unwrap()).is_err());
    }

    let valid = v2::Header {
        address_offset_size: 4,
        string_table_encoding: 0,
        base_address: 0x1000,
        address_count: 1,
    };
    let mut bad_magic = valid.encode(Endian::Little).unwrap();
    patch_bytes(&mut bad_magic, 0, &12_u32.to_le_bytes());
    assert!(matches!(
        v2::Header::decode(&bad_magic),
        Err(Error::InvalidMagic(12))
    ));

    let mut bad_version = valid.encode(Endian::Little).unwrap();
    patch_bytes(&mut bad_version, 4, &12_u16.to_le_bytes());
    assert!(matches!(
        v2::Header::decode(&bad_version),
        Err(Error::UnsupportedVersion(12))
    ));

    let mut bad_width = valid.encode(Endian::Little).unwrap();
    patch_byte(&mut bad_width, 6, 12);
    assert!(matches!(
        v2::Header::decode(&bad_width),
        Err(Error::InvalidAddressOffsetSize {
            version: 2,
            size: 12
        })
    ));

    let mut bad_encoding = valid.encode(Endian::Little).unwrap();
    patch_byte(&mut bad_encoding, 7, 12);
    assert!(matches!(
        v2::Header::decode(&bad_encoding),
        Err(Error::UnsupportedStringTableEncoding(12))
    ));
}

fn encoded_directory(
    endian: Endian,
    header: &v2::Header,
    uuid_size: usize,
    file_count: u32,
    string_size: usize,
    function_size: usize,
) -> (v2::WriteLayout, Vec<u8>) {
    let layout = header
        .write_layout(uuid_size, file_count, string_size, function_size)
        .unwrap();
    let mut output = Encoder::new(endian);
    output.write_bytes(&header.encode(endian).unwrap());
    for entry in &layout.directory {
        entry.encode_into(&mut output);
    }
    v2::GlobalData {
        section_type: v2::GLOBAL_END,
        file_offset: 0,
        file_size: 0,
    }
    .encode_into(&mut output);
    let mut bytes = output.into_inner();
    bytes.resize(layout.file_size, 0);
    patch_byte(&mut bytes, layout.string_table.start, 0);
    (layout, bytes)
}

#[test]
fn v2_directory_layout_matches_reference_with_and_without_uuid() {
    for endian in ENDIANS {
        for uuid_size in [0, 13] {
            let header = v2::Header {
                address_offset_size: 8,
                string_table_encoding: 0,
                base_address: 0x1000,
                address_count: 3,
            };
            let (layout, bytes) = encoded_directory(endian, &header, uuid_size, 2, 17, 91);
            let directory = v2::parse_directory(&bytes, endian, &header).unwrap();
            assert_eq!(directory.end_offset, layout.directory_end);
            assert_eq!(directory.entries, layout.directory);
            assert_eq!(layout.address_offsets.start % 8, 0);
            assert_eq!(layout.address_info_offsets.start % 8, 0);
            assert_eq!(layout.file_table.start % 4, 0);
            assert_eq!(layout.function_info.start % 4, 0);
            assert_eq!(layout.uuid.is_some(), uuid_size != 0);
            for section in &layout.directory {
                assert!(section.file_offset + section.file_size <= bytes.len() as u64);
            }
        }
    }
}

#[test]
fn v2_write_layout_matches_reference_offsets() {
    let header = v2::Header {
        address_offset_size: 8,
        string_table_encoding: 0,
        base_address: 0,
        address_count: 3,
    };
    let layout = header.write_layout(20, 2, 17, 91).unwrap();
    assert_eq!(layout.directory_end, 160);
    assert_eq!(layout.uuid, Some(160..180));
    assert_eq!(layout.directory.len(), 6);
}

#[test]
fn v2_directory_rejects_reference_errors_and_structural_corruption() {
    let endian = Endian::Little;
    let header = v2::Header {
        address_offset_size: 1,
        string_table_encoding: 0,
        base_address: 0x1000,
        address_count: 1,
    };
    let (layout, bytes) = encoded_directory(endian, &header, 0, 1, 6, 16);

    for length in 0..layout.directory_end {
        assert!(v2::parse_directory(bytes.split_at(length).0, endian, &header).is_err());
    }

    let mut duplicate = Encoder::new(endian);
    duplicate.write_bytes(&header.encode(endian).unwrap());
    let first = layout.directory.first().unwrap();
    first.encode_into(&mut duplicate);
    first.encode_into(&mut duplicate);
    v2::GlobalData {
        section_type: v2::GLOBAL_END,
        file_offset: 0,
        file_size: 0,
    }
    .encode_into(&mut duplicate);
    let mut duplicate = duplicate.into_inner();
    duplicate.resize(bytes.len(), 0);
    assert!(matches!(
        v2::parse_directory(&duplicate, endian, &header),
        Err(Error::DuplicateSection(_))
    ));

    let mut missing = Encoder::new(endian);
    missing.write_bytes(&header.encode(endian).unwrap());
    for entry in layout.directory.split_at(layout.directory.len() - 1).0 {
        entry.encode_into(&mut missing);
    }
    v2::GlobalData {
        section_type: v2::GLOBAL_END,
        file_offset: 0,
        file_size: 0,
    }
    .encode_into(&mut missing);
    let mut missing = missing.into_inner();
    missing.resize(bytes.len(), 0);
    assert!(matches!(
        v2::parse_directory(&missing, endian, &header),
        Err(Error::MissingSection(v2::GLOBAL_FUNCTION_INFO))
    ));

    let mut zero_sized = bytes.clone();
    let first_size = v2::HEADER_SIZE + 12;
    patch_bytes(&mut zero_sized, first_size, &[0; 8]);
    assert!(matches!(
        v2::parse_directory(&zero_sized, endian, &header),
        Err(Error::ZeroSizedSection { .. })
    ));

    let mut outside = bytes.clone();
    patch_bytes(&mut outside, first_size, &u64::MAX.to_le_bytes());
    assert!(v2::parse_directory(&outside, endian, &header).is_err());

    let mut overlapping = bytes.clone();
    let function_entry = v2::HEADER_SIZE + 4 * v2::GLOBAL_DATA_SIZE;
    patch_bytes(
        &mut overlapping,
        function_entry + 4,
        &u64::try_from(layout.file_table.start)
            .unwrap()
            .to_le_bytes(),
    );
    assert!(v2::parse_directory(&overlapping, endian, &header).is_err());

    let mut bad_terminator = bytes;
    let terminator_offset = layout.directory_end - v2::GLOBAL_DATA_SIZE;
    patch_byte(&mut bad_terminator, terminator_offset + 4, 1);
    assert!(v2::parse_directory(&bad_terminator, endian, &header).is_err());
}

#[test]
fn header_magic_constant_matches_llvm() {
    assert_eq!(GSYM_MAGIC, 0x4753_594d);
    assert_eq!(v1::file_table_size(0).unwrap(), 4);
    assert_eq!(v1::file_table_size(3).unwrap(), 28);
    assert_eq!(v2::file_table_size(0).unwrap(), 4);
    assert_eq!(v2::file_table_size(3).unwrap(), 52);
}