binparse 0.1.0

A colorful, user-friendly alternative to readelf for analyzing ELF binaries
//! Section header parsing and display.
//!
//! Displays section headers with resolved names from .shstrtab,
//! human-readable types, and flag descriptions.

use colored::Colorize;
use goblin::elf::section_header::{
    SHF_ALLOC, SHF_COMPRESSED, SHF_EXECINSTR, SHF_GROUP, SHF_INFO_LINK, SHF_LINK_ORDER, SHF_MERGE,
    SHF_OS_NONCONFORMING, SHF_STRINGS, SHF_TLS, SHF_WRITE,
};
use goblin::elf::Elf;

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

/// Display all section headers
pub fn display_section_headers(elf: &Elf) {
    print_title("Section Headers");

    if elf.section_headers.is_empty() {
        println!("  {}", "No section headers".dimmed());
        return;
    }

    println!(
        "  {} section headers at offset {:#X}\n",
        elf.section_headers.len().to_string().cyan(),
        elf.header.e_shoff
    );

    for (i, sh) in elf.section_headers.iter().enumerate() {
        display_section_header(i, sh, elf);
    }
}

/// Display a single section header
fn display_section_header(index: usize, sh: &goblin::elf::SectionHeader, elf: &Elf) {
    // Get section name from string table
    let name = elf.shdr_strtab.get_at(sh.sh_name).unwrap_or("<no name>");

    let type_str = section_type_to_string(sh.sh_type);
    print_indexed_item(index, if name.is_empty() { "(empty)" } else { name });

    // Section name
    print_field("Name", if name.is_empty() { "(empty)" } else { name });

    // Section type
    print_field_with_raw("Type", type_str, &format!("0x{:08X}", sh.sh_type));

    // Flags
    let flags_str = format_section_flags(sh.sh_flags);
    print_field_with_raw("Flags", &flags_str, &format!("0x{:X}", sh.sh_flags));

    // Address
    print_field("Address", &format!("{:#018X}", sh.sh_addr));

    // Offset
    print_field("Offset", &format!("{:#018X}", sh.sh_offset));

    // Size
    print_field("Size", &format!("{:#X} ({} bytes)", sh.sh_size, sh.sh_size));

    // Link and Info
    if sh.sh_link != 0 || sh.sh_info != 0 {
        print_field("Link", &format!("{}", sh.sh_link));
        print_field("Info", &format!("{}", sh.sh_info));
    }

    // Alignment
    if sh.sh_addralign != 0 {
        print_field("Alignment", &format!("{:#X}", sh.sh_addralign));
    }

    // Entry size (for tables)
    if sh.sh_entsize != 0 {
        print_field("Entry Size", &format!("{} bytes", sh.sh_entsize));
    }

    println!();
}

/// Convert section type to human-readable string
fn section_type_to_string(sh_type: u32) -> &'static str {
    match sh_type {
        0 => "SHT_NULL (Inactive)",
        1 => "SHT_PROGBITS (Program data)",
        2 => "SHT_SYMTAB (Symbol table)",
        3 => "SHT_STRTAB (String table)",
        4 => "SHT_RELA (Relocation entries with addends)",
        5 => "SHT_HASH (Symbol hash table)",
        6 => "SHT_DYNAMIC (Dynamic linking info)",
        7 => "SHT_NOTE (Notes)",
        8 => "SHT_NOBITS (BSS, no file space)",
        9 => "SHT_REL (Relocation entries)",
        10 => "SHT_SHLIB (Reserved)",
        11 => "SHT_DYNSYM (Dynamic linker symbol table)",
        14 => "SHT_INIT_ARRAY (Constructors)",
        15 => "SHT_FINI_ARRAY (Destructors)",
        16 => "SHT_PREINIT_ARRAY (Pre-constructors)",
        17 => "SHT_GROUP (Section group)",
        18 => "SHT_SYMTAB_SHNDX (Extended section indices)",
        0x6FFFFFF5 => "SHT_GNU_ATTRIBUTES (GNU attributes)",
        0x6FFFFFF6 => "SHT_GNU_HASH (GNU hash table)",
        0x6FFFFFF7 => "SHT_GNU_LIBLIST (Prelink library list)",
        0x6FFFFFF8 => "SHT_CHECKSUM (Checksum for DSO)",
        0x6FFFFFFD => "SHT_GNU_VERDEF (Version definition)",
        0x6FFFFFFE => "SHT_GNU_VERNEED (Version requirements)",
        0x6FFFFFFF => "SHT_GNU_VERSYM (Version symbol table)",
        st if (0x60000000..=0x6FFFFFFF).contains(&st) => "SHT_LOOS..SHT_HIOS (OS-specific)",
        st if (0x70000000..=0x7FFFFFFF).contains(&st) => "SHT_LOPROC..SHT_HIPROC (Processor-specific)",
        st if st >= 0x80000000 => "SHT_LOUSER..SHT_HIUSER (User-specific)",
        _ => "Unknown",
    }
}

/// Format section flags as human-readable string
fn format_section_flags(flags: u64) -> String {
    if flags == 0 {
        return String::from("none");
    }

    let mut parts = Vec::new();

    if flags & (SHF_WRITE as u64) != 0 {
        parts.push("WRITE");
    }
    if flags & (SHF_ALLOC as u64) != 0 {
        parts.push("ALLOC");
    }
    if flags & (SHF_EXECINSTR as u64) != 0 {
        parts.push("EXECINSTR");
    }
    if flags & (SHF_MERGE as u64) != 0 {
        parts.push("MERGE");
    }
    if flags & (SHF_STRINGS as u64) != 0 {
        parts.push("STRINGS");
    }
    if flags & (SHF_INFO_LINK as u64) != 0 {
        parts.push("INFO_LINK");
    }
    if flags & (SHF_LINK_ORDER as u64) != 0 {
        parts.push("LINK_ORDER");
    }
    if flags & (SHF_OS_NONCONFORMING as u64) != 0 {
        parts.push("OS_NONCONFORMING");
    }
    if flags & (SHF_GROUP as u64) != 0 {
        parts.push("GROUP");
    }
    if flags & (SHF_TLS as u64) != 0 {
        parts.push("TLS");
    }
    if flags & (SHF_COMPRESSED as u64) != 0 {
        parts.push("COMPRESSED");
    }

    if parts.is_empty() {
        format!("0x{:X}", flags)
    } else {
        parts.join(" | ")
    }
}

/// Get a short type name for table display
pub fn section_type_short(sh_type: u32) -> &'static str {
    match sh_type {
        0 => "NULL",
        1 => "PROGBITS",
        2 => "SYMTAB",
        3 => "STRTAB",
        4 => "RELA",
        5 => "HASH",
        6 => "DYNAMIC",
        7 => "NOTE",
        8 => "NOBITS",
        9 => "REL",
        10 => "SHLIB",
        11 => "DYNSYM",
        14 => "INIT_ARRAY",
        15 => "FINI_ARRAY",
        16 => "PREINIT_ARRAY",
        17 => "GROUP",
        18 => "SYMTAB_SHNDX",
        0x6FFFFFF6 => "GNU_HASH",
        0x6FFFFFFD => "VERDEF",
        0x6FFFFFFE => "VERNEED",
        0x6FFFFFFF => "VERSYM",
        _ => "UNKNOWN",
    }
}