binparse 0.1.0

A colorful, user-friendly alternative to readelf for analyzing ELF binaries
//! Symbol table parsing and display.
//!
//! Displays both static (.symtab) and dynamic (.dynsym) symbol tables
//! with human-readable type, binding, and visibility information.

use colored::Colorize;
use goblin::elf::sym::{
    STB_GLOBAL, STB_LOCAL, STB_WEAK, STT_FILE, STT_FUNC, STT_NOTYPE, STT_OBJECT, STT_SECTION,
};
use goblin::elf::Elf;

use crate::display::{
    print_field, print_field_with_raw, print_indexed_item, print_section, print_title,
};

/// Display all symbol tables
pub fn display_symbols(elf: &Elf) {
    print_title("Symbol Tables");

    let has_syms = !elf.syms.is_empty();
    let has_dynsyms = !elf.dynsyms.is_empty();

    if !has_syms && !has_dynsyms {
        println!("  {}", "No symbol tables".dimmed());
        return;
    }

    // Static symbols (.symtab)
    if has_syms {
        display_symbol_table(".symtab (Static Symbols)", &elf.syms, &elf.strtab);
    }

    // Dynamic symbols (.dynsym)
    if has_dynsyms {
        display_symbol_table(".dynsym (Dynamic Symbols)", &elf.dynsyms, &elf.dynstrtab);
    }
}

/// Display a specific symbol table
fn display_symbol_table(
    name: &str,
    symbols: &goblin::elf::Symtab,
    strtab: &goblin::strtab::Strtab,
) {
    print_section(name);
    println!("    {} symbols\n", symbols.len().to_string().cyan());

    for (i, sym) in symbols.iter().enumerate() {
        display_symbol(i, &sym, strtab);
    }
}

/// Display a single symbol
fn display_symbol(index: usize, sym: &goblin::elf::Sym, strtab: &goblin::strtab::Strtab) {
    // Get symbol name
    let name = strtab.get_at(sym.st_name).unwrap_or("<no name>");

    // Skip null symbols with no name for cleaner output
    if sym.st_name == 0 && sym.st_value == 0 && sym.st_size == 0 && index == 0 {
        return;
    }

    let display_name = if name.is_empty() {
        match sym.st_type() {
            STT_SECTION => "(section)",
            STT_FILE => "(file)",
            _ => "(unnamed)",
        }
    } else {
        name
    };

    print_indexed_item(index, display_name);

    // Name
    if !name.is_empty() {
        print_field("Name", name);
    }

    // Value (address)
    print_field("Value", &format!("{:#018X}", sym.st_value));

    // Size
    if sym.st_size > 0 {
        print_field("Size", &format!("{} bytes", sym.st_size));
    }

    // Type
    let type_str = symbol_type_to_string(sym.st_type());
    print_field_with_raw("Type", type_str, &format!("{}", sym.st_type()));

    // Binding
    let bind_str = symbol_binding_to_string(sym.st_bind());
    print_field_with_raw("Binding", bind_str, &format!("{}", sym.st_bind()));

    // Visibility
    let vis_str = symbol_visibility_to_string(sym.st_visibility());
    print_field_with_raw("Visibility", vis_str, &format!("{}", sym.st_visibility()));

    // Section index
    let shndx_str = section_index_to_string(sym.st_shndx);
    print_field_with_raw("Section Index", &shndx_str, &format!("{}", sym.st_shndx));

    println!();
}

/// Convert symbol type to human-readable string
fn symbol_type_to_string(st_type: u8) -> &'static str {
    match st_type {
        STT_NOTYPE => "NOTYPE (Unspecified)",
        STT_OBJECT => "OBJECT (Data object)",
        STT_FUNC => "FUNC (Function)",
        3 => "SECTION (Section)",
        STT_FILE => "FILE (Source file name)",
        5 => "COMMON (Common data object)",
        6 => "TLS (Thread-local storage)",
        10 => "GNU_IFUNC (Indirect function)",
        _ => "Unknown",
    }
}

/// Convert symbol binding to human-readable string
fn symbol_binding_to_string(st_bind: u8) -> &'static str {
    match st_bind {
        STB_LOCAL => "LOCAL (Not visible outside object)",
        STB_GLOBAL => "GLOBAL (Visible to all objects)",
        STB_WEAK => "WEAK (Like global, but lower precedence)",
        10 => "GNU_UNIQUE (Unique symbol)",
        _ => "Unknown",
    }
}

/// Convert symbol visibility to human-readable string
fn symbol_visibility_to_string(st_vis: u8) -> &'static str {
    match st_vis {
        0 => "DEFAULT",
        1 => "INTERNAL",
        2 => "HIDDEN",
        3 => "PROTECTED",
        _ => "Unknown",
    }
}

/// Convert section index to human-readable string
fn section_index_to_string(shndx: usize) -> String {
    match shndx {
        0 => "UNDEF (Undefined)".to_string(),
        0xFFF1 => "ABS (Absolute)".to_string(),
        0xFFF2 => "COMMON (Common)".to_string(),
        0xFFFF => "XINDEX (Extended)".to_string(),
        n if (0xFF00..=0xFFFF).contains(&n) => format!("RESERVED ({})", n),
        n => n.to_string(),
    }
}