diff --git a/Cargo.lock b/Cargo.lock index 1970460..af0b7bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,6 +215,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "armv5te" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7603974675228909093b94530f81c2ebaeafb7dc05968e48f422e453970dc80" + [[package]] name = "arrayref" version = "0.3.7" @@ -3103,6 +3109,7 @@ name = "objdiff-core" version = "1.0.0" dependencies = [ "anyhow", + "armv5te", "byteorder", "cpp_demangle", "cwdemangle", diff --git a/objdiff-core/Cargo.toml b/objdiff-core/Cargo.toml index 16a0a94..b490a4b 100644 --- a/objdiff-core/Cargo.toml +++ b/objdiff-core/Cargo.toml @@ -12,13 +12,14 @@ A local diffing tool for decompilation projects. """ [features] -all = ["config", "dwarf", "mips", "ppc", "x86"] +all = ["config", "dwarf", "mips", "ppc", "x86", "arm"] any-arch = [] # Implicit, used to check if any arch is enabled config = ["globset", "semver", "serde_json", "serde_yaml"] dwarf = ["gimli"] mips = ["any-arch", "rabbitizer"] ppc = ["any-arch", "cwdemangle", "ppc750cl"] x86 = ["any-arch", "cpp_demangle", "iced-x86", "msvc-demangler"] +arm = ["any-arch", "cpp_demangle", "armv5te"] [dependencies] anyhow = "1.0.82" @@ -52,3 +53,6 @@ rabbitizer = { version = "1.10.0", optional = true } cpp_demangle = { version = "0.4.3", optional = true } iced-x86 = { version = "1.21.0", default-features = false, features = ["std", "decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums"], optional = true } msvc-demangler = { version = "0.10.0", optional = true } + +# arm +armv5te = { version = "0.1.0", optional = true } diff --git a/objdiff-core/src/arch/arm.rs b/objdiff-core/src/arch/arm.rs new file mode 100644 index 0000000..492fbfe --- /dev/null +++ b/objdiff-core/src/arch/arm.rs @@ -0,0 +1,224 @@ +use std::borrow::Cow; + +use anyhow::{bail, Result}; +use armv5te::arm; +use object::{elf, File, Relocation, RelocationFlags}; + +use crate::{ + arch::{ObjArch, ProcessCodeResult}, + diff::DiffObjConfig, + obj::{ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection, SymbolRef}, +}; + +pub struct ObjArchArm {} + +impl ObjArchArm { + pub fn new(_file: &File) -> Result { + Ok(Self {}) + } +} + +impl ObjArch for ObjArchArm { + fn process_code( + &self, + obj: &ObjInfo, + symbol_ref: SymbolRef, + config: &DiffObjConfig, + ) -> Result { + let (section, symbol) = obj.section_symbol(symbol_ref); + let code = §ion.data + [symbol.section_address as usize..(symbol.section_address + symbol.size) as usize]; + + let ins_count = code.len() / 4; + let mut ops = Vec::::with_capacity(ins_count); + let mut insts = Vec::::with_capacity(ins_count); + for (cur_addr, mut ins) in arm::InsIter::new(code, symbol.address as u32) { + let reloc = section.relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr); + if let Some(reloc) = reloc { + ins.code = match reloc.flags { + RelocationFlags::Elf { r_type: elf::R_ARM_PC24 } => ins.code & !0xffffff, + _ => bail!("Unhandled relocation flags {:?}", reloc.flags), + }; + } + + let parsed_ins = arm::ParsedIns::parse(ins); + + let mut reloc_arg = None; + if let Some(reloc) = reloc { + if let RelocationFlags::Elf { r_type: elf::R_ARM_PC24 } = reloc.flags { + reloc_arg = parsed_ins + .args + .iter() + .rposition(|a| matches!(a, arm::Argument::BranchDest(_))); + } + } + + let mut args = vec![]; + let mut branch_dest = None; + let mut writeback = false; + let mut deref = false; + for (i, arg) in parsed_ins.args_iter().enumerate() { + // Emit punctuation before separator + if deref { + match arg { + arm::Argument::PostOffset(_) + | arm::Argument::RegPostOffset(_) + | arm::Argument::CoOption(_) => { + deref = false; + args.push(ObjInsArg::PlainText("]".into())); + if writeback { + writeback = false; + args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into()))); + } + } + _ => {} + } + } + + if i > 0 { + args.push(ObjInsArg::PlainText(config.separator().into())); + } + + if reloc_arg == Some(i) { + let reloc = reloc.unwrap(); + push_reloc(&mut args, reloc)?; + } else { + match arg { + arm::Argument::RegWb(reg) => { + args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque( + reg.to_string().into(), + ))); + args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into()))); + } + arm::Argument::RegDeref(reg) => { + deref = true; + args.push(ObjInsArg::PlainText("[".into())); + args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque( + reg.to_string().into(), + ))); + } + arm::Argument::RegDerefWb(reg) => { + deref = true; + writeback = true; + args.push(ObjInsArg::PlainText("[".into())); + args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque( + reg.to_string().into(), + ))); + } + arm::Argument::RegList(reg_list) => { + push_reg_list(reg_list, &mut args, config); + } + arm::Argument::RegListC(reg_list) => { + push_reg_list(reg_list, &mut args, config); + args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque( + "^".to_string().into(), + ))); + } + arm::Argument::UImm(value) | arm::Argument::CoOpcode(value) => { + args.push(ObjInsArg::PlainText("#".into())); + args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64))); + } + arm::Argument::SImm((value, _)) + | arm::Argument::Offset((value, _)) + | arm::Argument::PostOffset((value, _)) => { + args.push(ObjInsArg::PlainText("#".into())); + args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(*value as i64))); + } + arm::Argument::BranchDest((value, _)) => { + let dest = cur_addr.wrapping_add_signed(*value) as u64; + args.push(ObjInsArg::BranchDest(dest)); + branch_dest = Some(dest); + } + arm::Argument::CoOption(value) => { + args.push(ObjInsArg::PlainText("{".into())); + args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64))); + args.push(ObjInsArg::PlainText("}".into())); + } + arm::Argument::CoprocNum(value) => { + args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque( + format!("p{}", value).into(), + ))); + } + _ => args + .push(ObjInsArg::Arg(ObjInsArgValue::Opaque(arg.to_string().into()))), + } + } + } + if deref { + args.push(ObjInsArg::PlainText("]".into())); + if writeback { + args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into()))); + } + } + + ops.push(ins.op as u16); + let line = obj + .line_info + .as_ref() + .and_then(|map| map.range(..=cur_addr as u64).last().map(|(_, &b)| b)); + insts.push(ObjIns { + address: cur_addr as u64, + size: 4, + op: ins.op as u16, + mnemonic: parsed_ins.mnemonic.to_string(), + args, + reloc: reloc.cloned(), + branch_dest, + line, + orig: None, + }) + } + + Ok(ProcessCodeResult { ops, insts }) + } + + fn implcit_addend( + &self, + _section: &ObjSection, + address: u64, + reloc: &Relocation, + ) -> anyhow::Result { + bail!("Unsupported ARM implicit relocation {:#x}{:?}", address, reloc.flags()) + } + + fn demangle(&self, name: &str) -> Option { + cpp_demangle::Symbol::new(name) + .ok() + .and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok()) + } + + fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> { + Cow::Owned(format!("<{flags:?}>")) + } +} + +fn push_reg_list(reg_list: &u32, args: &mut Vec, config: &DiffObjConfig) { + args.push(ObjInsArg::PlainText("{".into())); + let mut first = true; + for i in 0..16 { + if (reg_list & (1 << i)) != 0 { + if !first { + args.push(ObjInsArg::PlainText(config.separator().into())); + } + args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque( + arm::Reg::parse(i).to_string().into(), + ))); + first = false; + } + } + args.push(ObjInsArg::PlainText("}".into())); +} + +fn push_reloc(args: &mut Vec, reloc: &ObjReloc) -> Result<()> { + match reloc.flags { + RelocationFlags::Elf { r_type } => match r_type { + elf::R_ARM_PC24 => { + args.push(ObjInsArg::Reloc); + args.push(ObjInsArg::PlainText("@pc24".into())); + } + _ => bail!("Unsupported ELF ARM relocation type {r_type}"), + }, + flags => bail!("Unsupported ARM relocation kind: {flags:?}"), + } + Ok(()) +} diff --git a/objdiff-core/src/arch/mod.rs b/objdiff-core/src/arch/mod.rs index 6e5057c..225d7ce 100644 --- a/objdiff-core/src/arch/mod.rs +++ b/objdiff-core/src/arch/mod.rs @@ -14,6 +14,8 @@ mod mips; mod ppc; #[cfg(feature = "x86")] mod x86; +#[cfg(feature = "arm")] +mod arm; pub trait ObjArch: Send + Sync { fn process_code( @@ -44,6 +46,8 @@ pub fn new_arch(object: &object::File) -> Result> { Architecture::Mips => Box::new(mips::ObjArchMips::new(object)?), #[cfg(feature = "x86")] Architecture::I386 | Architecture::X86_64 => Box::new(x86::ObjArchX86::new(object)?), + #[cfg(feature = "arm")] + Architecture::Arm => Box::new(arm::ObjArchArm::new(object)?), arch => bail!("Unsupported architecture: {arch:?}"), }) }