r2fas 0.2.1

radare2 core plugin that loads FASM -s symbolic dumps for named labels, source lines, and comments
//! Symbol table records (FAS.TXT table 2).

use crate::error::{FasError, Result};
use crate::fas::bytes;
use crate::fas::header::Header;

/// Size of one symbol record in bytes.
pub const SYMBOL_SIZE: usize = 32;

/// Table 2.1 — symbol was defined (not just mentioned in a skipped `if`).
pub const SYM_DEFINED: u16 = 1;
/// Table 2.1 — assembly-time variable (`=` / `equ` style), not a memory label.
pub const SYM_VARIABLE: u16 = 2;
/// Table 2.1 — special marker with no value.
pub const SYM_MARKER: u16 = 0x400;
/// High bit of the name field: name lives in the strings table, not Pascal.
pub const NAME_IN_STRINGS: u32 = 0x8000_0000;
/// High bit of the reloc field: relative to an external symbol.
pub const RELOC_EXTERNAL: u32 = 0x8000_0000;

/// One 32-byte symbol record.
#[derive(Debug, Clone)]
pub struct Symbol {
    /// Stable zero-based record index.
    pub id: usize,
    /// Numeric value (address or constant).
    pub value: u64,
    /// Flag bits (table 2.1).
    pub flags: u16,
    /// Size attached to the label, or 0 for a plain label.
    pub size: u8,
    /// Value type (table 2.2). 0 = absolute.
    pub val_type: u8,
    /// Extended SIB associated with the value.
    pub extended_sib: u32,
    /// Last assembly pass that defined this symbol.
    pub defined_pass: u16,
    /// Last assembly pass that used this symbol.
    pub used_pass: u16,
    /// Section/external reloc info (table 2 field +20).
    pub reloc: u32,
    /// Name field (table 2 field +24).
    pub name_field: u32,
    /// Preprocessed-source offset of the defining line.
    pub def_line_off: u32,
}

impl Symbol {
    /// Parse one record at the start of `rec` (must be 32 bytes).
    pub fn parse(rec: &[u8]) -> Result<Self> {
        Self::parse_with_id(0, rec)
    }

    /// Parse one record and assign its stable table index.
    pub fn parse_with_id(id: usize, rec: &[u8]) -> Result<Self> {
        if rec.len() < SYMBOL_SIZE {
            return Err(FasError::Truncated("symbol"));
        }
        Ok(Self {
            id,
            value: bytes::u64_at(rec, 0)?,
            flags: bytes::u16_at(rec, 8)?,
            size: bytes::u8_at(rec, 10)?,
            val_type: bytes::u8_at(rec, 11)?,
            extended_sib: bytes::u32_at(rec, 12)?,
            defined_pass: bytes::u16_at(rec, 16)?,
            used_pass: bytes::u16_at(rec, 18)?,
            reloc: bytes::u32_at(rec, 20)?,
            name_field: bytes::u32_at(rec, 24)?,
            def_line_off: bytes::u32_at(rec, 28)?,
        })
    }

    /// Bit 0: the symbol was actually defined.
    pub fn is_defined(&self) -> bool {
        self.flags & SYM_DEFINED != 0
    }

    /// Bit 1: assembly-time variable (`foo = 16`), not a load-time address.
    pub fn is_variable(&self) -> bool {
        self.flags & SYM_VARIABLE != 0
    }

    /// Bit 10: FASM internal marker.
    pub fn is_marker(&self) -> bool {
        self.flags & SYM_MARKER != 0
    }

    /// Field +20 high bit: `extrn`.
    pub fn is_external(&self) -> bool {
        self.reloc & RELOC_EXTERNAL != 0
    }

    /// One-based section index when this is section-relative.
    pub fn section_index(&self) -> Option<u32> {
        (self.val_type != 0 && !self.is_external()).then_some(self.reloc & !RELOC_EXTERNAL)
    }

    /// Strings-table offset of the external symbol when external-relative.
    pub fn external_name_offset(&self) -> Option<u32> {
        self.is_external().then_some(self.reloc & !RELOC_EXTERNAL)
    }

    /// Resolve the external symbol name, when present.
    pub fn external_name<'a>(&self, header: &Header, data: &'a [u8]) -> Result<Option<&'a str>> {
        self.external_name_offset()
            .map(|offset| header.string_at(data, offset))
            .transpose()
    }

    /// Anonymous (no name).
    pub fn is_anonymous(&self) -> bool {
        self.name_field == 0
    }

    /// Resolve the symbol name from either the strings table or a Pascal
    /// string in the preprocessed source.
    pub fn name<'a>(&self, header: &Header, data: &'a [u8]) -> Result<Option<&'a str>> {
        if self.is_anonymous() {
            return Ok(None);
        }
        if self.name_field & NAME_IN_STRINGS != 0 {
            let off = self.name_field & !NAME_IN_STRINGS;
            return header.string_at(data, off).map(Some);
        }
        let prep = header.preprocessed(data)?;
        let off = (self.name_field & !NAME_IN_STRINGS) as usize;
        bytes::pascal_at(prep, off).map(Some)
    }
}

/// Parse the whole symbol table.
pub fn parse_symbols(header: &Header, data: &[u8]) -> Result<Vec<Symbol>> {
    let raw = header.symbols(data)?;
    if raw.len() % SYMBOL_SIZE != 0 {
        return Err(FasError::Geometry("symbols not a multiple of 32"));
    }
    raw.chunks_exact(SYMBOL_SIZE)
        .enumerate()
        .map(|(id, record)| Symbol::parse_with_id(id, record))
        .collect()
}