use crate::{
color,
demangle::{self, demangled},
opts::{Format, NameDisplay, OutputStyle, ToDump},
pick_dump_item, safeprintln, Item,
};
use ar::Archive;
use capstone::{Capstone, Insn};
use object::{
Architecture, Object, ObjectSection, ObjectSymbol, Relocation, RelocationTarget, SectionIndex,
SymbolKind,
};
use owo_colors::OwoColorize;
use std::{
collections::{BTreeMap, BTreeSet},
path::Path,
};
#[derive(Copy, Clone)]
struct Reference<'a> {
name: &'a str,
name_display: NameDisplay,
}
impl std::fmt::Display for Reference<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", demangle::contents(self.name, self.name_display))
}
}
struct HexDump<'a> {
max_width: usize,
bytes: &'a [u8],
}
impl std::fmt::Display for HexDump<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.bytes.is_empty() {
return Ok(());
}
for byte in self.bytes {
write!(f, "{byte:02x} ")?;
}
for _ in 0..(1 + self.max_width - self.bytes.len()) {
f.write_str(" ")?;
}
Ok(())
}
}
pub fn dump_disasm(
goal: ToDump,
file: &Path,
fmt: &Format,
syntax: OutputStyle,
) -> anyhow::Result<()> {
if file.extension().is_some_and(|e| e == "rlib") {
let mut slices = Vec::new();
let mut archive = Archive::new(std::fs::File::open(file)?);
while let Some(entry) = archive.next_entry() {
let mut entry = entry?;
let name = std::str::from_utf8(entry.header().identifier())?;
if !name.ends_with(".o") {
continue;
}
let mut bytes = Vec::new();
std::io::Read::read_to_end(&mut entry, &mut bytes)?;
slices.push(bytes);
}
dump_slices(goal, slices.as_slice(), fmt, syntax)
} else {
let binary_data = std::fs::read(file)?;
dump_slices(goal, &[binary_data][..], fmt, syntax)
}
}
fn pick_item<'a>(
goal: ToDump,
files: &'a [object::File],
fmt: &Format,
) -> anyhow::Result<(&'a object::File<'a>, SectionIndex, usize, usize)> {
let mut items = BTreeMap::new();
for file in files {
let mut addresses: Vec<_> = file
.symbols()
.filter(|s| s.is_definition() && s.kind() == SymbolKind::Text)
.map(|s| s.address() as usize)
.collect();
addresses.sort_unstable();
for (index, symbol) in file
.symbols()
.filter(|s| s.is_definition() && s.kind() == SymbolKind::Text)
.enumerate()
{
let raw_name = symbol.name()?;
let (name, hashed) = match demangled(raw_name) {
Some(dem) => (format!("{dem:#?}"), format!("{dem:?}")),
None => (raw_name.to_owned(), raw_name.to_owned()),
};
let Some(section_index) = symbol.section_index() else {
continue;
};
let addr = symbol.address() as usize;
let mut len = symbol.size() as usize; if len == 0 {
let (Ok(idx) | Err(idx)) = addresses.binary_search(&addr);
let next_address = match addresses[idx..].iter().copied().find(|&a| a > addr) {
Some(addr) => addr,
None => {
let section = file.section_by_index(section_index)?;
(section.address() + section.size()) as usize
}
};
len = next_address - addr;
}
let item = Item {
name,
hashed,
index,
len,
non_blank_len: len,
mangled_name: raw_name.to_owned(),
};
items.insert(item, (file, section_index, addr, len));
}
}
pick_dump_item(goal, fmt, &items)
.ok_or_else(|| anyhow::anyhow!("no can do --everything with --disasm"))
}
fn reloc_info<'a>(
file: &'a object::File,
reloc_map: &'a BTreeMap<u64, Relocation>,
insn: &Insn,
fmt: &Format,
) -> Option<Reference<'a>> {
let addr = insn.address();
let range = addr..addr + insn.len() as u64;
let (_range, relocation) = reloc_map.range(range).next()?;
let name = match relocation.target() {
RelocationTarget::Symbol(sym) => file.symbol_by_index(sym).ok()?.name().ok(),
RelocationTarget::Section(sec) => file.section_by_index(sec).ok()?.name().ok(),
RelocationTarget::Absolute => None,
_ => None,
}?;
Some(Reference {
name,
name_display: fmt.name_display,
})
}
fn dump_slices(
goal: ToDump,
binary_data: &[Vec<u8>],
fmt: &Format,
syntax: OutputStyle,
) -> anyhow::Result<()> {
let files = binary_data
.iter()
.map(|data| object::File::parse(data.as_slice()))
.collect::<Result<Vec<_>, _>>()?;
let (file, section_index, addr, len) = pick_item(goal, &files, fmt)?;
let mut opcode_cache = BTreeMap::new();
let section = file.section_by_index(section_index)?;
let reloc_map = section.relocations().collect::<BTreeMap<_, _>>();
let symbol_names = if reloc_map.is_empty() {
files
.iter()
.flat_map(|f| f.symbols())
.map(|s| {
let name = s.name().unwrap();
let name = name.split_once('$').map_or(name, |(p, _)| p);
let reloc = Reference {
name,
name_display: fmt.name_display,
};
(s.address(), reloc)
})
.collect::<BTreeMap<_, _>>()
} else {
BTreeMap::new()
};
let is_thumb = addr & 1 == 1;
let addr = addr & !1;
let start = addr - section.address() as usize;
let cs = make_capstone(file, syntax, is_thumb)?;
let code = §ion.data()?[start..start + len];
if fmt.verbosity >= 2 {
if reloc_map.is_empty() {
safeprintln!("There is no relocation table");
} else {
safeprintln!("reloc_map {:#?}", reloc_map);
}
}
let insns = cs.disasm_all(code, addr as u64)?;
if insns.is_empty() {
if fmt.verbosity > 0 {
safeprintln!("No instructions - empty code block?");
}
return Ok(());
}
let max_width = insns.iter().map(|i| i.len()).max().unwrap_or(1);
let addrs = insns
.iter()
.map(|insn| {
if *opcode_cache.entry(insn.op_str()).or_insert_with(|| {
cs.insn_detail(insn)
.expect("Can't get instruction info")
.groups()
.iter()
.any(|g| matches!(cs.group_name(*g).as_deref(), Some("call" | "jump")))
}) {
let r = get_reference(&cs, insn)?;
(r != insn.address() + insn.len() as u64).then_some(r)
} else {
None
}
})
.collect::<Vec<_>>();
let local_range = insns[0].address()..insns.last().unwrap().address();
let local_labels = addrs
.iter()
.copied()
.flatten()
.filter(|addr| local_range.contains(addr))
.collect::<BTreeSet<_>>();
let local_labels = local_labels
.into_iter()
.enumerate()
.map(|n| (n.1, n.0))
.collect::<BTreeMap<_, _>>();
let mut buf = String::new();
for (insn, &maddr) in insns.iter().zip(addrs.iter()) {
let hex = HexDump {
max_width,
bytes: if fmt.simplify { &[] } else { insn.bytes() },
};
let addr = insn.address();
let mut refn = reloc_info(file, &reloc_map, insn, fmt)
.or_else(|| maddr.and_then(|addr| symbol_names.get(&addr).copied()));
if let Some(id) = local_labels.get(&addr) {
use owo_colors::OwoColorize;
safeprintln!(
"{}{}:",
crate::color!(".L", OwoColorize::bright_yellow),
crate::color!(id, OwoColorize::bright_yellow),
);
}
let i = crate::asm::Instruction {
op: insn.mnemonic().unwrap_or("???"),
args: insn.op_str(),
};
if let Some(id) = maddr.and_then(|a| local_labels.get(&a)) {
buf.clear();
use std::fmt::Write;
write!(
buf,
"{}{}",
color!(".L", OwoColorize::bright_yellow),
color!(id, OwoColorize::bright_yellow)
)
.unwrap();
refn = Some(Reference {
name: buf.as_str(),
name_display: fmt.name_display,
});
}
if let Some(reloc) = refn {
safeprintln!("{addr:8x}: {hex}{i} # {reloc}");
} else {
safeprintln!("{addr:8x}: {hex}{i}");
}
}
Ok(())
}
fn get_reference(cs: &Capstone, insn: &Insn) -> Option<u64> {
use capstone::arch::{
arm64::Arm64OperandType, x86::X86OperandType, ArchDetail, DetailsArchInsn,
};
let details = cs.insn_detail(insn).unwrap();
match details.arch_detail() {
ArchDetail::X86Detail(x86) => match x86.operands().next()?.op_type {
X86OperandType::Imm(rel) => Some(rel.try_into().unwrap()),
X86OperandType::Mem(mem) => {
assert_eq!(mem.scale(), 1);
if mem.disp() == 0 {
(insn.address() + insn.len() as u64).checked_add_signed(mem.disp())
} else {
None
}
}
_ => None, },
ArchDetail::Arm64Detail(arm) => match arm.operands().next()?.op_type {
Arm64OperandType::Imm(rel) => Some(rel.try_into().unwrap()),
Arm64OperandType::Mem(mem) => {
if mem.disp() == 0 {
(insn.address() + insn.len() as u64).checked_add_signed(mem.disp() as i64)
} else {
None
}
}
_ => None, },
_ => None,
}
}
impl From<OutputStyle> for capstone::arch::x86::ArchSyntax {
fn from(value: OutputStyle) -> Self {
match value {
OutputStyle::Intel => Self::Intel,
OutputStyle::Att => Self::Att,
}
}
}
fn make_capstone(
file: &object::File,
syntax: OutputStyle,
is_thumb: bool,
) -> anyhow::Result<Capstone> {
use capstone::{
arch::{self, BuildsCapstone, BuildsCapstoneExtraMode, BuildsCapstoneSyntax},
Endian,
};
let endianness = match file.endianness() {
object::Endianness::Little => Endian::Little,
object::Endianness::Big => Endian::Big,
};
let mut capstone = match file.architecture() {
Architecture::Arm if is_thumb => Capstone::new()
.arm()
.mode(arch::arm::ArchMode::Thumb)
.build()?,
Architecture::Arm => Capstone::new()
.arm()
.mode(arch::arm::ArchMode::Arm)
.build()?,
Architecture::Aarch64 => Capstone::new()
.arm64()
.mode(arch::arm64::ArchMode::Arm)
.build()?,
Architecture::I386 => Capstone::new()
.x86()
.mode(arch::x86::ArchMode::Mode32)
.syntax(syntax.into())
.build()?,
Architecture::X86_64_X32 | Architecture::X86_64 => Capstone::new()
.x86()
.mode(arch::x86::ArchMode::Mode64)
.syntax(syntax.into())
.build()?,
Architecture::M68k => Capstone::new()
.m68k()
.mode(arch::m68k::ArchMode::M68k040)
.build()?,
Architecture::Riscv32 => Capstone::new()
.riscv()
.mode(arch::riscv::ArchMode::RiscV32)
.extra_mode([arch::riscv::ArchExtraMode::RiscVC].into_iter())
.build()?,
Architecture::Riscv64 => Capstone::new()
.riscv()
.mode(arch::riscv::ArchMode::RiscV64)
.extra_mode([arch::riscv::ArchExtraMode::RiscVC].into_iter())
.build()?,
unknown => anyhow::bail!("Dunno how to decompile {unknown:?}"),
};
capstone.set_detail(true)?;
capstone.set_endian(endianness)?;
Ok(capstone)
}