binparse 0.1.0

A colorful, user-friendly alternative to readelf for analyzing ELF binaries
//! Program header (segment) parsing and display.
//!
//! Displays program headers with human-readable segment types,
//! flags, and memory layout information.

use colored::Colorize;
use goblin::elf::program_header::{PF_R, PF_W, PF_X};
use goblin::elf::Elf;

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

/// Display all program headers
pub fn display_program_headers(elf: &Elf) {
    print_title("Program Headers (Segments)");

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

    println!(
        "  {} program headers at offset {:#X}\n",
        elf.program_headers.len().to_string().cyan(),
        elf.header.e_phoff
    );

    for (i, ph) in elf.program_headers.iter().enumerate() {
        display_program_header(i, ph);
    }
}

/// Display a single program header
fn display_program_header(index: usize, ph: &goblin::elf::ProgramHeader) {
    let type_str = segment_type_to_string(ph.p_type);
    print_indexed_item(index, type_str);

    // Type with raw value
    print_field_with_raw("Type", type_str, &format!("0x{:08X}", ph.p_type));

    // Flags
    let flags_str = format_segment_flags(ph.p_flags);
    print_field_with_raw("Flags", &flags_str, &format!("0x{:X}", ph.p_flags));

    // Offset in file
    print_field("Offset", &format!("{:#018X}", ph.p_offset));

    // Virtual address
    print_field("Virtual Address", &format!("{:#018X}", ph.p_vaddr));

    // Physical address
    print_field("Physical Address", &format!("{:#018X}", ph.p_paddr));

    // File size
    print_field(
        "File Size",
        &format!("{:#X} ({} bytes)", ph.p_filesz, ph.p_filesz),
    );

    // Memory size
    print_field(
        "Memory Size",
        &format!("{:#X} ({} bytes)", ph.p_memsz, ph.p_memsz),
    );

    // Alignment
    print_field("Alignment", &format!("{:#X}", ph.p_align));

    println!();
}

/// Convert segment type to human-readable string
fn segment_type_to_string(p_type: u32) -> &'static str {
    match p_type {
        0 => "PT_NULL (Unused)",
        1 => "PT_LOAD (Loadable segment)",
        2 => "PT_DYNAMIC (Dynamic linking info)",
        3 => "PT_INTERP (Interpreter path)",
        4 => "PT_NOTE (Auxiliary info)",
        5 => "PT_SHLIB (Reserved)",
        6 => "PT_PHDR (Program header table)",
        7 => "PT_TLS (Thread-local storage)",
        0x6474E550 => "PT_GNU_EH_FRAME (Exception handling)",
        0x6474E551 => "PT_GNU_STACK (Stack executability)",
        0x6474E552 => "PT_GNU_RELRO (Read-only after reloc)",
        0x6474E553 => "PT_GNU_PROPERTY (GNU property)",
        0x6FFFFFFA => "PT_SUNWBSS (Sun-specific BSS)",
        0x6FFFFFFB => "PT_SUNWSTACK (Sun stack segment)",
        pt if (0x60000000..=0x6FFFFFFF).contains(&pt) => "PT_LOOS..PT_HIOS (OS-specific)",
        pt if (0x70000000..=0x7FFFFFFF).contains(&pt) => "PT_LOPROC..PT_HIPROC (Processor-specific)",
        _ => "Unknown",
    }
}

/// Format segment flags as human-readable string
fn format_segment_flags(flags: u32) -> String {
    let r = flags & PF_R != 0;
    let w = flags & PF_W != 0;
    let x = flags & PF_X != 0;

    let rwx = format_flags(r, w, x);

    let mut parts = vec![rwx];

    // Check for additional flags
    let extra = flags & !(PF_R | PF_W | PF_X);
    if extra != 0 {
        parts.push(format!("+0x{:X}", extra));
    }

    parts.join(" ")
}

/// Get a short type name for table display
pub fn segment_type_short(p_type: u32) -> &'static str {
    match p_type {
        0 => "NULL",
        1 => "LOAD",
        2 => "DYNAMIC",
        3 => "INTERP",
        4 => "NOTE",
        5 => "SHLIB",
        6 => "PHDR",
        7 => "TLS",
        0x6474E550 => "GNU_EH_FRAME",
        0x6474E551 => "GNU_STACK",
        0x6474E552 => "GNU_RELRO",
        0x6474E553 => "GNU_PROPERTY",
        _ => "UNKNOWN",
    }
}