use capstone::{Arch as CapArch, Capstone, Mode, NO_EXTRA_MODE};
use clap::Parser;
use object::{Architecture as ObjArch, Object, ObjectSection, SectionKind};
use std::{
cmp,
collections::{HashMap, HashSet},
fs::File,
path::PathBuf,
};
fn describe_group_x86(g: &u8) -> Option<&'static str> {
Some(match *g {
128 => "VT-x/AMD-V", 129 => "3DNow",
130 => "AES",
131 => "ADX",
132 => "AVX",
133 => "AVX2",
134 => "AVX512",
135 => "BMI",
136 => "BMI2",
137 => "CMOV",
138 => "F16C", 139 => "FMA",
140 => "FMA4",
141 => "FSGSBASE",
142 => "HLE",
143 => "MMX",
144 => "MODE32",
145 => "MODE64",
146 => "RTM",
147 => "SHA",
148 => "SSE1", 149 => "SSE2",
150 => "SSE3",
151 => "SSE41",
152 => "SSE42",
153 => "SSE4A",
154 => "SSSE3",
155 => "PCLMUL",
156 => "XOP",
157 => "CDI",
158 => "ERI", 159 => "TBM",
160 => "16BITMODE",
161 => "NOT64BITMODE",
162 => "SGX",
163 => "DQI",
164 => "BWI",
165 => "PFI",
166 => "VLX",
167 => "SMAP",
168 => "NOVLX", _ => {
return None;
}
})
}
fn describe_group_aarch64(g: &u8) -> Option<&'static str> {
Some(match *g {
128 => "CRYPTO", 129 => "FPARMV8", 130 => "NEON",
131 => "CRC",
132 => "AES",
133 => "DOTPROD",
134 => "FULLFP16",
135 => "LSE",
136 => "RCPC",
137 => "RDM",
138 => "SHA2",
139 => "SHA3",
140 => "SM4",
141 => "SVE",
142 => "SVE2",
143 => "SVE2AES",
144 => "SVE2BitPerm",
145 => "SVE2SHA3",
146 => "SV#2SM4",
147 => "SME",
148 => "SMEF64",
149 => "SMEI64",
150 => "MatMulFP32",
151 => "MatMulFP64",
152 => "MatMulInt8",
153 => "V8_1A",
154 => "V8_3A",
155 => "V8_4A",
_ => {
return None;
}
})
}
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
path: PathBuf,
}
fn main() {
let cpu_generations: HashMap<&str, u16> = [
("Pentium", 100),
("Pentium Pro", 101),
("Pentium III", 102),
("Pentium 4", 103),
("Pentium M", 104),
("Prescott", 105),
("Intel Core", 106),
("Penryn", 107),
("Nehalem", 108),
("Bonnell", 109),
("Westmere", 110),
("Saltwell", 111),
("Sandy Bridge", 112),
("Ivy Bridge", 113),
("Silvermont", 114),
("Haswell", 115),
("Broadwell", 116),
("Airmont", 117),
("Skylake", 118),
("Goldmont", 119),
("Kaby Lake", 120),
("Coffee Lake", 121),
("Cannon Lake", 122),
("Whiskey Lake", 123),
("Amber Lake", 124),
("Cascade Lake", 125),
("Cooper Lake", 126),
("Ice Lake", 127),
("K6-2", 200),
("Bulldozer", 201),
("K10", 202),
("Piledriver", 203),
("Unknown", 999),
]
.iter()
.cloned()
.collect();
let mut cpu_generations_reverse: HashMap<u16, &str> = HashMap::new();
for (key, val) in &cpu_generations {
cpu_generations_reverse.insert(*val, key);
}
let instrset_to_cpu: HashMap<&str, &str> = [
("VT-x/AMD-V", "Intel Core"), ("3DNow", "K6-2"), ("AES", "Westmere"), ("ADX", "Broadwell"), ("AVX", "Sandy Bridge"), ("AVX2", "Haswell"), ("AVX512", "Unknown"), ("BMI", "Haswell"), ("BMI2", "Haswell"), ("CMOV", "Pentium Pro"), ("F16C", "Ivy Bridge"), ("FMA", "Haswell"), ("FMA4", "Bulldozer"), ("FSGSBASE", "Ivy Bridge"), ("HLE", "Haswell"), ("MMX", "Pentium"), ("MODE32", "Pentium"), ("MODE64", "Intel Core"), ("RTM", "Haswell"), ("SHA", "Goldmont"), ("SSE1", "Pentium III"), ("SSE2", "Pentium 4"), ("SSE3", "Prescott"), ("SSE41", "Penryn"), ("SSE42", "Nehalem"), ("SSE4A", "K10"), ("SSSE3", "Intel Core"), ("PCLMUL", "Intel Core"), ("XOP", "Bulldozer"), ("CDI", "Unknown"), ("ERI", "Unknown"), ("TBM", "Piledriver"), ("16BITMODE", "Unknown"),
("NOT64BITMODE", "Unknown"),
("SGX", "Skylake"), ("DQI", "Cannon Lake"), ("BWI", "Cannon Lake"), ("PFI", "Unknown"), ("VLX", "Cannon Lake"), ("SMAP", "Broadwell"), ("NOVLX", "Unknown"), ]
.iter()
.cloned()
.collect();
let args = Args::parse();
let f = File::open(&args.path).expect("can't open object file");
let buf = unsafe { memmap::Mmap::map(&f).expect("can't memmap object file") };
let obj = object::File::parse(&*buf).expect("can't parse object file");
let obj_arch = obj.architecture();
println!(
"File format and CPU architecture: {:?}, {obj_arch:?}",
obj.format()
);
let (cap_arch, mode, describe_group): (CapArch, Mode, fn(&u8) -> Option<&str>) = match obj_arch
{
ObjArch::X86_64 | ObjArch::X86_64_X32 => {
if obj.is_64() {
(CapArch::X86, Mode::Mode64, describe_group_x86)
} else {
(CapArch::X86, Mode::Mode32, describe_group_x86)
}
}
ObjArch::Aarch64 | ObjArch::Aarch64_Ilp32 => {
(CapArch::ARM64, Mode::Arm, describe_group_aarch64)
}
_ => {
println!(
"CPU architecture `{obj_arch:?}` of input file `{}` is not currently supported for analysis",
args.path.display()
);
return;
}
};
let mut cs =
Capstone::new_raw(cap_arch, mode, NO_EXTRA_MODE, None).expect("can't initialize capstone");
cs.set_detail(true)
.expect("can't enable Capstone detail mode");
cs.set_skipdata(true)
.expect("can't enable Capstone skip data mode");
let mut seen_groups = HashSet::new();
for sect in obj.sections() {
if sect.kind() != SectionKind::Text {
continue;
}
let data = sect.data().expect("couldn't get section data");
let mut offset = 0;
loop {
let rest = &data[offset..];
if rest.is_empty() {
break;
}
let insns = cs
.disasm_count(rest, 0, 1)
.expect("couldn't disassemble section");
for insn in insns.iter() {
offset += insn.bytes().len();
let Ok(detail) = cs.insn_detail(insn) else {
continue;
};
for group_code in detail.groups() {
if seen_groups.insert(group_code.0) {
if let Some(mnemonic) = insn.mnemonic() {
if let Some(desc) = describe_group(&group_code.0) {
println!("{} ({})", desc, mnemonic);
}
}
}
}
}
}
}
let mut proc_features = seen_groups
.iter()
.filter_map(describe_group)
.collect::<Vec<&str>>();
proc_features.sort();
println!(
"Instruction set extensions used: {}",
proc_features.join(", ")
);
if cap_arch == CapArch::X86 {
let mut max_gen_code = 100;
for group_code in seen_groups.iter() {
if let Some(desc) = describe_group(group_code) {
match instrset_to_cpu.get(desc) {
Some(generation) => match cpu_generations.get(generation) {
Some(gen_code) => {
max_gen_code = cmp::max(max_gen_code, *gen_code);
}
None => unimplemented!(),
},
None => unimplemented!(),
}
}
}
match cpu_generations_reverse.get(&max_gen_code) {
Some(generation) => {
println!("CPU Generation: {}", generation);
}
None => unimplemented!(),
}
}
}