#![doc = include_str!("../README.md")]
#![warn(unsafe_op_in_unsafe_fn)]
#![warn(
clippy::all,
clippy::pedantic,
clippy::alloc_instead_of_core,
clippy::std_instead_of_core,
clippy::std_instead_of_alloc
)]
#![allow(
clippy::upper_case_acronyms,
clippy::unnecessary_wraps,
clippy::missing_docs_in_private_items,
clippy::print_stderr,
clippy::print_stdout,
clippy::implicit_return,
clippy::separated_literal_suffix,
clippy::question_mark_used,
clippy::mod_module_files,
clippy::expect_used,
clippy::module_name_repetitions,
clippy::unwrap_in_result,
clippy::min_ident_chars,
clippy::single_char_lifetime_names,
clippy::single_call_fn,
clippy::absolute_paths,
clippy::similar_names
)]
#[cfg(target_os = "linux")]
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
mod archive;
mod cmdline;
mod elf;
mod errors;
mod options;
mod parser;
mod pe;
mod ui;
mod utils;
extern crate alloc;
use core::iter;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::ExitCode;
use clap::Parser;
use flexi_logger::{FlexiLoggerError, LoggerHandle};
use log::{debug, error, trace};
use rayon::prelude::*;
use crate::cmdline::UseColor;
use crate::errors::{Error, Result};
use crate::parser::BinaryParser;
use crate::ui::ColorBuffer;
fn main() -> ExitCode {
let options = cmdline::Options::parse();
let _log_handle = match init_logger(&options) {
Ok(h) => h,
Err(err) => {
eprintln!("Error: {}", format_error(&err));
return ExitCode::FAILURE;
}
};
trace!("{:?}", &options);
let mut exit_code = 0_u8;
match run(options) {
Ok((successes, errors)) => {
for (path, color_buffer) in successes {
print!("{}: ", path.display());
if color_buffer.print().is_err() {
exit_code = 1;
break;
}
}
if exit_code == 0 {
for (path, error) in errors {
exit_code = 1;
error!("{}: {}", path.display(), format_error(&error));
}
}
}
Err(error) => {
exit_code = 1;
error!("{}", format_error(&error));
}
}
ExitCode::from(exit_code)
}
type SuccessResults = Vec<(PathBuf, ColorBuffer)>;
type ErrorResults = Vec<(PathBuf, Error)>;
fn run(mut options: cmdline::Options) -> Result<(SuccessResults, ErrorResults)> {
use rayon::iter::Either;
let icb_stdout = ColorBuffer::for_stdout(options.color);
let input_files = core::mem::take(&mut options.input_files);
let result: (Vec<_>, Vec<_>) = input_files
.into_iter()
.zip(iter::repeat(icb_stdout))
.collect::<Vec<_>>()
.into_par_iter()
.map(|(path, mut out)| {
let r = process_file(&path, &mut out.color_buffer, &options);
(path, out, r)
})
.partition_map(|(path, out, result)| match result {
Ok(()) => Either::Left((path, out)),
Err(r) => Either::Right((path, r)),
});
Ok(result)
}
fn format_error(mut r: &dyn core::error::Error) -> String {
use core::fmt::Write;
let mut text = format!("{r}.");
while let Some(source) = r.source() {
let _ignored = write!(&mut text, " {source}.");
r = source;
}
text
}
fn init_logger(options: &cmdline::Options) -> core::result::Result<LoggerHandle, FlexiLoggerError> {
use flexi_logger::{
AdaptiveFormat, LogSpecification, Logger, colored_default_format, default_format,
};
let log_spec = LogSpecification::builder()
.default(if options.verbose {
log::LevelFilter::Trace
} else {
log::LevelFilter::Info
})
.build();
let logger = Logger::with(log_spec).use_utc();
let logger = match options.color {
UseColor::Auto => logger.adaptive_format_for_stderr(AdaptiveFormat::Default),
UseColor::Always => logger.format_for_stderr(colored_default_format),
UseColor::Never => logger.format_for_stderr(default_format),
};
logger.start()
}
fn process_file(
path: &Path,
color_buffer: &mut termcolor::Buffer,
options: &cmdline::Options,
) -> Result<()> {
use goblin::Object;
let parser = BinaryParser::open(path)?;
let results = match parser.object() {
Object::Elf(_elf) => {
debug!("Binary file format is 'ELF'.");
elf::analyze_binary(&parser, options)
}
Object::PE(_pe) => {
debug!("Binary file format is 'PE'.");
pe::analyze_binary(&parser, options)
}
Object::Mach(_mach) => {
debug!("Binary file format is 'MACH'.");
Err(Error::UnsupportedBinaryFormat {
format: "MACH".into(),
path: path.into(),
})
}
Object::Archive(_archive) => {
debug!("Binary file format is 'Archive'.");
archive::analyze_binary(&parser, options)
}
Object::Unknown(_magic) => Err(Error::UnknownBinaryFormat(path.into())),
_ => Err(Error::UnknownBinaryFormat(path.into())),
}?;
let mut iter = results.into_iter();
if let Some(first) = iter.next() {
first.as_ref().display_in_color_term(color_buffer)?;
for opt in iter {
write!(color_buffer, " ")
.map_err(|r| Error::from_io1(r, "write", "standard output stream"))?;
opt.as_ref().display_in_color_term(color_buffer)?;
}
}
writeln!(color_buffer)
.map_err(|r| Error::from_io1(r, "write line", "standard output stream"))?;
Ok(())
}