use hereticus::address::Contents;
use hereticus::matching::contents_of;
use hereticus::obj::{Binary, FoundBy, Function, Isa};
use tabled::settings::object::Columns;
use tabled::settings::style::HorizontalLine;
use tabled::settings::{Alignment, Style};
use tabled::{Table, Tabled};
use super::Format;
use crate::formats::json_string;
struct Listed<'a> {
function: &'a Function,
isa: Isa,
contents: Contents,
}
impl<'a> Listed<'a> {
fn of(binary: &'a Binary) -> impl Iterator<Item = Self> + 'a {
binary.functions.iter().map(move |function| Self {
function,
isa: binary.isa_of(function),
contents: contents_of(binary, function),
})
}
}
#[derive(Tabled)]
struct Row {
#[tabled(rename = "ADDRESS")]
address: String,
#[tabled(rename = "SIZE")]
size: usize,
#[tabled(rename = "ISA")]
isa: &'static str,
#[tabled(rename = "HOLDS")]
holds: &'static str,
#[tabled(rename = "FUNCTION")]
function: String,
}
impl From<&Listed<'_>> for Row {
fn from(listed: &Listed<'_>) -> Self {
Self {
address: format!("{:#x}", listed.function.address),
size: listed.function.code.len(),
isa: name_of_isa(listed.isa),
holds: name_of_contents(listed.contents),
function: listed.function.label(),
}
}
}
const SIZE_COLUMN: usize = 1;
pub(crate) fn print_functions(binary: &Binary, format: Format) {
let listed = Listed::of(binary);
match format {
Format::Text => {
let mut table = Table::new(listed.map(|listed| Row::from(&listed)));
table
.with(
Style::rounded()
.horizontals([(1, HorizontalLine::inherit(Style::modern_rounded()))]),
)
.with(Alignment::left())
.modify(Columns::one(SIZE_COLUMN), Alignment::right());
println!("{table}");
}
Format::Compact => {
for listed in listed {
let row = Row::from(&listed);
println!(
"{}\t{}\t{}\t{}\t{}",
row.address, row.size, row.isa, row.holds, row.function
);
}
}
Format::Markdown => {
let mut table = Table::new(listed.map(|listed| Row::from(&listed)));
table.with(Style::markdown());
println!("{table}");
}
Format::Json => println!("{}", json_functions(binary)),
}
}
fn json_functions(binary: &Binary) -> String {
let functions: Vec<String> = Listed::of(binary)
.map(|listed| {
format!(
"{{\"name\":{},\"address\":{},\"size\":{},\"isa\":\"{}\",\"holds\":\"{}\"}}",
json_string(&listed.function.label()),
listed.function.address,
listed.function.code.len(),
name_of_isa(listed.isa),
name_of_contents(listed.contents),
)
})
.collect();
let found_by = match binary.found_by {
FoundBy::Symbols => "symbols",
FoundBy::Scanning => "scanning",
};
format!(
"{{\"isa\":\"{}\",\"found_by\":\"{found_by}\",\"functions\":[{}]}}",
name_of_isa(binary.isa),
functions.join(",")
)
}
fn name_of_isa(isa: Isa) -> &'static str {
match isa {
Isa::Arm32 => "arm32",
Isa::Thumb2 => "thumb2",
Isa::Aarch64 => "aarch64",
Isa::Mips32 => "mips32",
Isa::Mips32Be => "mips32-be",
Isa::Mips16 => "mips16",
Isa::Mips16Be => "mips16-be",
Isa::X86_64 => "x86-64",
Isa::X86 => "x86",
}
}
fn name_of_contents(contents: Contents) -> &'static str {
match contents {
Contents::Code => "code",
Contents::Data => "data",
}
}
#[cfg(test)]
mod tests {
use clap::ValueEnum;
use super::*;
use crate::ArchName;
#[test]
fn each_isa_takes_the_name_arch_takes() {
for &arch in ArchName::value_variants() {
let wanted = arch.to_possible_value().expect("each architecture has a name");
assert_eq!(name_of_isa(arch.into()), wanted.get_name());
}
}
}