use std::fmt;
use std::io::{self, BufRead, Write};
use std::num;
use clap::{Parser, Subcommand, ValueEnum};
#[cfg(feature = "aarch32")]
use exarmo_aarch32::{ItState, a32, t32};
use exarmo_core::decode::{Decoded, UNPREDICTABLE_MARK};
#[cfg(not(any(feature = "aarch64", feature = "aarch32")))]
compile_error!("exarmo-cli needs at least one of the aarch64 and aarch32 features");
const ABOUT: &str = if cfg!(all(feature = "aarch64", feature = "aarch32")) {
"Disassemble AArch64, A32 and T32 instructions"
} else if cfg!(feature = "aarch64") {
"Disassemble AArch64 instructions"
} else {
"Disassemble A32 and T32 instructions"
};
#[derive(Parser)]
#[command(name = "exarmo", about = ABOUT)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Decode {
set: Set,
word: String,
#[arg(long)]
byteswap: bool,
#[arg(long)]
debug: bool,
#[arg(long)]
intrinsics: bool,
},
}
#[derive(Clone, Copy, ValueEnum)]
enum Set {
#[cfg(feature = "aarch64")]
A64,
#[cfg(feature = "aarch32")]
A32,
#[cfg(feature = "aarch32")]
T32,
}
const ERROR: &str = "error: ";
#[derive(Clone, Copy)]
struct Show {
debug: bool,
intrinsics: bool,
}
fn marked_text(instruction: &dyn Decoded) -> Result<String, fmt::Error> {
let mut text = String::new();
instruction.write_tokens_at(0, &mut text)?;
if instruction.unpredictable() {
text.push_str(UNPREDICTABLE_MARK);
}
Ok(text)
}
fn rendered<I: Decoded, E: fmt::Debug>(
decoded: Result<I, E>,
show: Show,
intrinsic: impl FnOnce(&I) -> Option<&'static str>,
) -> String {
match decoded {
Ok(instruction) => match marked_text(&instruction) {
Err(_) => "-".to_string(),
Ok(mut text) => {
if show.intrinsics {
let intrinsic = intrinsic(&instruction).unwrap_or("-");
text = format!("{text}\t{intrinsic}");
}
if show.debug {
text = format!("{text}\t{instruction:?}");
}
text
}
},
Err(why) => format!("{ERROR}{why:?}"),
}
}
fn decoded(set: Set, bits: u32, show: Show) -> String {
match set {
#[cfg(feature = "aarch64")]
Set::A64 => rendered(exarmo_aarch64::decode(bits), show, |instruction| {
let intrinsics = instruction.intrinsics();
intrinsics.first().map(|intrinsic| intrinsic.name())
}),
#[cfg(feature = "aarch32")]
Set::A32 => rendered(a32::decode(bits), show, |_| None),
#[cfg(feature = "aarch32")]
Set::T32 => rendered(t32::decode(bits, ItState::Outside), show, |_| None),
}
}
fn has_intrinsics(set: Set) -> bool {
match set {
#[cfg(feature = "aarch64")]
Set::A64 => true,
#[cfg(feature = "aarch32")]
Set::A32 | Set::T32 => false,
}
}
fn word(set: Set, hex: &str, byteswap: bool) -> Result<u32, num::ParseIntError> {
let bits = u32::from_str_radix(hex, 16)?;
if !byteswap {
return Ok(bits);
}
Ok(match set {
#[cfg(feature = "aarch64")]
Set::A64 => bits.swap_bytes(),
#[cfg(feature = "aarch32")]
Set::A32 => bits.swap_bytes(),
#[cfg(feature = "aarch32")]
Set::T32 if hex.len() <= 4 => u32::from((bits as u16).swap_bytes()) << 16,
#[cfg(feature = "aarch32")]
Set::T32 => {
u32::from(((bits >> 16) as u16).swap_bytes()) << 16
| u32::from((bits as u16).swap_bytes())
}
})
}
fn decode(set: Set, hex: &str, byteswap: bool, show: Show) {
if hex == "-" {
let input = io::stdin().lock();
let mut out = io::BufWriter::new(io::stdout().lock());
for line in input.lines() {
let line = line.expect("the input is readable");
let hex = line.trim();
if hex.is_empty() {
continue;
}
match word(set, hex, byteswap) {
Ok(bits) => writeln!(out, "{}", decoded(set, bits, show)),
Err(_) => writeln!(out, "not a hexadecimal word"),
}
.expect("the output is writable");
}
return;
}
let bits = word(set, hex, byteswap).unwrap_or_else(|e| {
eprintln!("not a hexadecimal word: {hex}: {e}");
std::process::exit(1);
});
println!("{}", decoded(set, bits, show));
}
fn main() {
match Cli::parse().command {
Command::Decode {
set,
word,
byteswap,
debug,
intrinsics,
} => {
if intrinsics && !has_intrinsics(set) {
eprintln!("--intrinsics is for a64 alone");
std::process::exit(2);
}
decode(set, &word, byteswap, Show { debug, intrinsics })
}
}
}