binparse 0.1.0

A colorful, user-friendly alternative to readelf for analyzing ELF binaries
//! binparse - A colorful, user-friendly alternative to readelf
//!
//! A modern tool for analyzing ELF binaries with beautiful colored output.

mod display;
mod elf;
mod error;

use std::fs;
use std::path::PathBuf;
use std::process::ExitCode;

use clap::Parser;
use colored::Colorize;

use crate::elf::ElfFile;
use crate::error::{BinparseError, Result};

/// binparse - A colorful ELF binary analyzer
///
/// A user-friendly alternative to readelf with beautiful colored output.
/// Supports 32-bit and 64-bit ELF files with little and big endian.
#[derive(Parser, Debug)]
#[command(
    name = "binparse",
    author,
    version,
    about = "A colorful, user-friendly alternative to readelf",
    long_about = "binparse is a modern tool for analyzing ELF binaries.\n\n\
                  It provides beautiful colored output with human-readable descriptions\n\
                  of all ELF structures including headers, segments, sections, and symbols."
)]
struct Args {
    /// Path to the binary file to analyze
    #[arg(value_name = "FILE")]
    file: PathBuf,

    /// Display the ELF file header
    #[arg(short = 'H', long = "header", help = "Display ELF header")]
    header: bool,

    /// Display program headers (segments)
    #[arg(
        short = 'p',
        long = "segments",
        alias = "program-headers",
        help = "Display program headers (segments)"
    )]
    segments: bool,

    /// Display section headers
    #[arg(
        short = 's',
        long = "sections",
        alias = "section-headers",
        help = "Display section headers"
    )]
    sections: bool,

    /// Display symbol tables
    #[arg(long = "symbols", alias = "syms", help = "Display symbol tables")]
    symbols: bool,

    /// Display all information
    #[arg(short = 'a', long = "all", help = "Display all information")]
    all: bool,
}

impl Args {
    /// Check if any display option was explicitly specified
    fn any_specified(&self) -> bool {
        self.header || self.segments || self.sections || self.symbols || self.all
    }

    /// Get the effective display options
    fn effective_options(&self) -> DisplayOptions {
        if self.all {
            DisplayOptions {
                header: true,
                segments: true,
                sections: true,
                symbols: true,
            }
        } else if self.any_specified() {
            DisplayOptions {
                header: self.header,
                segments: self.segments,
                sections: self.sections,
                symbols: self.symbols,
            }
        } else {
            // Default: show header only
            DisplayOptions {
                header: true,
                segments: false,
                sections: false,
                symbols: false,
            }
        }
    }
}

/// Which parts of the binary to display
struct DisplayOptions {
    header: bool,
    segments: bool,
    sections: bool,
    symbols: bool,
}

fn main() -> ExitCode {
    let args = Args::parse();

    match run(&args) {
        Ok(()) => ExitCode::SUCCESS,
        Err(e) => {
            eprintln!("{}: {}", "Error".red().bold(), e);
            ExitCode::FAILURE
        }
    }
}

fn run(args: &Args) -> Result<()> {
    // Read the file
    let data = fs::read(&args.file).map_err(|e| BinparseError::FileRead {
        path: args.file.clone(),
        source: e,
    })?;

    // Parse and analyze
    analyze_file(&data, &args.file, args.effective_options())
}

fn analyze_file(data: &[u8], path: &PathBuf, options: DisplayOptions) -> Result<()> {
    // Check minimum size
    if data.len() < 16 {
        return Err(BinparseError::FileTooSmall { size: data.len() });
    }

    // Detect file type by magic bytes
    match detect_format(data) {
        Format::Elf => analyze_elf(data, path, options),
        Format::Unknown => Err(BinparseError::UnsupportedFormat(
            "Unknown binary format. Only ELF is currently supported.".to_string(),
        )),
    }
}

fn analyze_elf(data: &[u8], path: &PathBuf, options: DisplayOptions) -> Result<()> {
    let elf = ElfFile::parse(data, path)?;

    // Print file info header
    elf.print_file_info();

    // Display requested sections
    if options.header {
        elf.display_header();
    }

    if options.segments {
        elf.display_program_headers();
    }

    if options.sections {
        elf.display_section_headers();
    }

    if options.symbols {
        elf.display_symbols();
    }

    println!();
    Ok(())
}

/// Detected binary format
enum Format {
    Elf,
    Unknown,
}

/// Detect the binary format from magic bytes
fn detect_format(data: &[u8]) -> Format {
    if data.len() >= 4 && &data[0..4] == b"\x7FELF" {
        Format::Elf
    } else {
        Format::Unknown
    }
}