Compare commits

...

13 Commits

Author SHA1 Message Date
Aetias
a5a6a3928e Fix read error on objects with no .text section (#67)
* Fix read error on objects with no .text section

* Fix read error on DWARF 1.1 objects

* Revert DWARF 1 changes

---------

Co-authored-by: Luke Street <luke@street.dev>
2024-06-03 19:47:38 -06:00
fc54e93681 API updates for ARM backend 2024-06-03 19:37:48 -06:00
c9b11db2fa Update README.md 2024-06-03 19:09:35 -06:00
Aetias
b991960080 ARMv5TE (DS) support (#68)
* Initial ARM support

* Disassemble const pool reloc

* Disasm ARM/Thumb/data based on mapping symbols

* Fallback to mapping symbol `$a`

* Support multiple DWARF sequences

* Update line info

* Rework DWARF line info parsing

- Properly handles multiple sections
  in DWARF 1
- line_info moved into ObjSection
- DWARF 2 parser no longer errors with
  no .text section
- Both parsers properly skip empty
  sections

* Simplify line_info (no Option)

* Get line info from section; output formatted ins string

* Unwrap code section in `arm.rs`

* Handle reloc `R_ARM_SBREL32`

* Update ARM disassembler

* Update README.md

* Format

* Revert "Update README.md"

This reverts commit 8bbfcc6f45.

* Update README.md

---------

Co-authored-by: Luke Street <luke.street@encounterpc.com>
2024-06-03 19:08:49 -06:00
425dc8546b More descriptive message for build failure
Resolves #64
2024-06-03 19:06:19 -06:00
9e04357d9f Use solid scrollbar in egui
Resolves #69
2024-06-03 19:03:33 -06:00
6037c12ad0 Disable lto to workaround crash
See #66
2024-06-03 18:58:25 -06:00
b15f643713 Bump version to 2.0.0-alpha.3 2024-06-03 18:54:46 -06:00
3f82c1a50f objdiff-core API adjustments
- Allows using process_code without
  constructing an ObjInfo
- Allows creating an arch without
  having to provide an object

Used in decomp-toolkit
2024-06-03 18:52:32 -06:00
0ea6242669 Bump rabbitizer version (fixes crash) 2024-06-03 18:50:22 -06:00
0c20a0d9cd Update README.md 2024-05-21 18:12:58 -06:00
f30b3cfae2 Default "Space between args" -> true 2024-05-21 18:09:46 -06:00
9e57a66a05 Auto-detect MIPS ABI/category & add config
Under Diff Options -> Arch Settings, one
can override the ABI/instruction category
2024-05-21 18:06:14 -06:00
19 changed files with 609 additions and 117 deletions

19
Cargo.lock generated
View File

@@ -3081,7 +3081,7 @@ dependencies = [
[[package]]
name = "objdiff-cli"
version = "2.0.0-alpha.1"
version = "2.0.0-alpha.3"
dependencies = [
"anyhow",
"argp",
@@ -3100,7 +3100,7 @@ dependencies = [
[[package]]
name = "objdiff-core"
version = "2.0.0-alpha.1"
version = "2.0.0-alpha.3"
dependencies = [
"anyhow",
"byteorder",
@@ -3123,11 +3123,13 @@ dependencies = [
"serde_json",
"serde_yaml",
"similar",
"strum",
"unarm",
]
[[package]]
name = "objdiff-gui"
version = "2.0.0-alpha.1"
version = "2.0.0-alpha.3"
dependencies = [
"anyhow",
"bytes",
@@ -3157,6 +3159,7 @@ dependencies = [
"serde",
"serde_json",
"shell-escape",
"strum",
"tempfile",
"time",
"tracing-subscriber",
@@ -3543,9 +3546,9 @@ dependencies = [
[[package]]
name = "rabbitizer"
version = "1.10.0"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f570a07717dcb6edf85e6652d92671c49e8728014c6ec1577a81b4d949725e27"
checksum = "305e6fb948a8a884ba996ac4efb1d7b0ee44d0bbfcd86b9c0f0fc0aa0aa0fc21"
dependencies = [
"cc",
"glob",
@@ -4693,6 +4696,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "unarm"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c13fc9a9c95348bf7565e5c30688fc288239962958cac0ccdc7cd009141d850"
[[package]]
name = "unicase"
version = "2.7.0"

View File

@@ -8,5 +8,7 @@ resolver = "2"
[profile.release-lto]
inherits = "release"
lto = "thin"
# Temporarily disabled to fix notify crash
# See https://github.com/encounter/objdiff/issues/66
#lto = "thin"
strip = "debuginfo"

View File

@@ -14,9 +14,10 @@ Features:
- Click to highlight all instances of values and registers.
Supports:
- PowerPC 750CL (GameCube & Wii)
- MIPS (Nintendo 64 & PS2)
- PowerPC 750CL (GameCube, Wii)
- MIPS (N64, PS1, PS2, PSP)
- x86 (COFF only at the moment)
- ARMv5 (DS)
See [Usage](#usage) for more information.

View File

@@ -1,6 +1,6 @@
[package]
name = "objdiff-cli"
version = "2.0.0-alpha.1"
version = "2.0.0-alpha.3"
edition = "2021"
rust-version = "1.70"
authors = ["Luke Street <luke@street.dev>"]

View File

@@ -821,8 +821,10 @@ impl FunctionDiffUi {
.transpose()?;
let config = diff::DiffObjConfig {
relax_reloc_diffs: self.relax_reloc_diffs,
space_between_args: true, // TODO
x86_formatter: Default::default(), // TODO
space_between_args: true, // TODO
x86_formatter: Default::default(), // TODO
mips_abi: Default::default(), // TODO
mips_instr_category: Default::default(), // TODO
};
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), prev.as_ref())?;

View File

@@ -1,6 +1,6 @@
[package]
name = "objdiff-core"
version = "2.0.0-alpha.1"
version = "2.0.0-alpha.3"
edition = "2021"
rust-version = "1.70"
authors = ["Luke Street <luke@street.dev>"]
@@ -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", "unarm"]
[dependencies]
anyhow = "1.0.82"
@@ -31,6 +32,7 @@ num-traits = "0.2.18"
object = { version = "0.35.0", features = ["read_core", "std", "elf", "pe"], default-features = false }
serde = { version = "1", features = ["derive"] }
similar = { version = "2.5.0", default-features = false }
strum = { version = "0.26.2", features = ["derive"] }
# config
globset = { version = "0.4.14", features = ["serde1"], optional = true }
@@ -46,9 +48,12 @@ cwdemangle = { version = "1.0.0", optional = true }
ppc750cl = { git = "https://github.com/encounter/ppc750cl", rev = "6cbd7d888c7082c2c860f66cbb9848d633f753ed", optional = true }
# mips
rabbitizer = { version = "1.10.0", optional = true }
rabbitizer = { version = "1.11.0", optional = true }
# x86
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
unarm = { version = "1.0.0", optional = true }

View File

@@ -0,0 +1,296 @@
use std::{
borrow::Cow,
collections::{BTreeMap, HashMap},
};
use anyhow::{bail, Result};
use object::{
elf, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, SectionIndex,
SectionKind, Symbol,
};
use unarm::{
args::{Argument, OffsetImm, OffsetReg, Register},
parse::{ArmVersion, ParseMode, Parser},
ParsedIns,
};
use crate::{
arch::{ObjArch, ProcessCodeResult},
diff::DiffObjConfig,
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
};
pub struct ObjArchArm {
/// Maps section index, to list of disasm modes (arm, thumb or data) sorted by address
disasm_modes: HashMap<SectionIndex, Vec<DisasmMode>>,
}
impl ObjArchArm {
pub fn new(file: &File) -> Result<Self> {
match file {
File::Elf32(_) => {
let disasm_modes: HashMap<_, _> = file
.sections()
.filter(|s| s.kind() == SectionKind::Text)
.map(|s| {
let index = s.index();
let mut mapping_symbols: Vec<_> = file
.symbols()
.filter(|s| s.section_index().map(|i| i == index).unwrap_or(false))
.filter_map(|s| DisasmMode::from_symbol(&s))
.collect();
mapping_symbols.sort_unstable_by_key(|x| x.address);
(s.index(), mapping_symbols)
})
.collect();
Ok(Self { disasm_modes })
}
_ => bail!("Unsupported file format {:?}", file.format()),
}
}
}
impl ObjArch for ObjArchArm {
fn process_code(
&self,
address: u64,
code: &[u8],
section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u64>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
let start_addr = address as u32;
let end_addr = start_addr + code.len() as u32;
// Mapping symbols decide what kind of data comes after it. $a for ARM code, $t for Thumb code and $d for data.
let fallback_mappings = [DisasmMode { address: start_addr, mapping: ParseMode::Arm }];
let mapping_symbols = self
.disasm_modes
.get(&SectionIndex(section_index))
.map(|x| x.as_slice())
.unwrap_or(&fallback_mappings);
let first_mapping_idx =
match mapping_symbols.binary_search_by_key(&start_addr, |x| x.address) {
Ok(idx) => idx,
Err(idx) => idx - 1,
};
let first_mapping = mapping_symbols[first_mapping_idx].mapping;
let mut mappings_iter =
mapping_symbols.iter().skip(first_mapping_idx + 1).take_while(|x| x.address < end_addr);
let mut next_mapping = mappings_iter.next();
let ins_count = code.len() / first_mapping.instruction_size();
let mut ops = Vec::<u16>::with_capacity(ins_count);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
let mut parser = Parser::new(ArmVersion::V5Te, first_mapping, start_addr, code);
while let Some((address, op, ins)) = parser.next() {
if let Some(next) = next_mapping {
let next_address = parser.address;
if next_address >= next.address {
// Change mapping
parser.mode = next.mapping;
next_mapping = mappings_iter.next();
}
}
let line = line_info.range(..=address as u64).last().map(|(_, &b)| b);
let reloc = relocations.iter().find(|r| (r.address as u32 & !1) == address).cloned();
let mut reloc_arg = None;
if let Some(reloc) = &reloc {
match reloc.flags {
RelocationFlags::Elf { r_type: elf::R_ARM_THM_XPC22 }
| RelocationFlags::Elf { r_type: elf::R_ARM_PC24 } => {
reloc_arg =
ins.args.iter().rposition(|a| matches!(a, Argument::BranchDest(_)));
}
_ => (),
}
};
let (args, branch_dest) = if reloc.is_some() && parser.mode == ParseMode::Data {
(vec![ObjInsArg::Reloc], None)
} else {
push_args(&ins, config, reloc_arg, address)?
};
ops.push(op.id());
insts.push(ObjIns {
address: address as u64,
size: (parser.address - address) as u8,
op: op.id(),
mnemonic: ins.mnemonic.to_string(),
args,
reloc,
branch_dest,
line,
formatted: ins.to_string(),
orig: None,
});
}
Ok(ProcessCodeResult { ops, insts })
}
fn implcit_addend(
&self,
_section: &ObjSection,
address: u64,
reloc: &Relocation,
) -> anyhow::Result<i64> {
bail!("Unsupported ARM implicit relocation {:#x}{:?}", address, reloc.flags())
}
fn demangle(&self, name: &str) -> Option<String> {
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:?}>"))
}
}
#[derive(Clone, Copy, Debug)]
struct DisasmMode {
address: u32,
mapping: ParseMode,
}
impl DisasmMode {
fn from_symbol<'a>(sym: &Symbol<'a, '_, &'a [u8]>) -> Option<Self> {
if let Ok(name) = sym.name() {
ParseMode::from_mapping_symbol(name)
.map(|mapping| DisasmMode { address: sym.address() as u32, mapping })
} else {
None
}
}
}
fn push_args(
parsed_ins: &ParsedIns,
config: &DiffObjConfig,
reloc_arg: Option<usize>,
cur_addr: u32,
) -> Result<(Vec<ObjInsArg>, Option<u64>)> {
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 {
Argument::OffsetImm(OffsetImm { post_indexed: true, value: _ })
| Argument::OffsetReg(OffsetReg { add: _, post_indexed: true, reg: _ })
| 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) {
args.push(ObjInsArg::Reloc);
} else {
match arg {
Argument::Reg(reg) => {
if reg.deref {
deref = true;
args.push(ObjInsArg::PlainText("[".into()));
}
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(reg.reg.to_string().into())));
if reg.writeback {
if reg.deref {
writeback = true;
} else {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
}
}
}
Argument::RegList(reg_list) => {
args.push(ObjInsArg::PlainText("{".into()));
let mut first = true;
for i in 0..16 {
if (reg_list.regs & (1 << i)) != 0 {
if !first {
args.push(ObjInsArg::PlainText(config.separator().into()));
}
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
Register::parse(i).to_string().into(),
)));
first = false;
}
}
args.push(ObjInsArg::PlainText("}".into()));
if reg_list.user_mode {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("^".to_string().into())));
}
}
Argument::UImm(value) | Argument::CoOpcode(value) => {
args.push(ObjInsArg::PlainText("#".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64)));
}
Argument::SImm(value)
| Argument::OffsetImm(OffsetImm { post_indexed: _, value }) => {
args.push(ObjInsArg::PlainText("#".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(*value as i64)));
}
Argument::BranchDest(value) => {
let dest = cur_addr.wrapping_add_signed(*value) as u64;
args.push(ObjInsArg::BranchDest(dest));
branch_dest = Some(dest);
}
Argument::CoOption(value) => {
args.push(ObjInsArg::PlainText("{".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64)));
args.push(ObjInsArg::PlainText("}".into()));
}
Argument::CoprocNum(value) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(format!("p{}", value).into())));
}
Argument::ShiftImm(shift) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into())));
args.push(ObjInsArg::PlainText(" #".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(shift.imm as u64)));
}
Argument::ShiftReg(shift) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into())));
args.push(ObjInsArg::PlainText(" ".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.reg.to_string().into())));
}
Argument::OffsetReg(offset) => {
if !offset.add {
args.push(ObjInsArg::PlainText("-".into()));
}
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
offset.reg.to_string().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())));
}
}
Ok((args, branch_dest))
}

View File

@@ -1,54 +1,95 @@
use std::borrow::Cow;
use std::{borrow::Cow, collections::BTreeMap, sync::Mutex};
use anyhow::{anyhow, bail, Result};
use object::{elf, Endian, Endianness, File, Object, Relocation, RelocationFlags};
use object::{elf, Endian, Endianness, File, FileFlags, Object, Relocation, RelocationFlags};
use rabbitizer::{config, Abi, InstrCategory, Instruction, OperandType};
use crate::{
arch::{ObjArch, ProcessCodeResult},
diff::DiffObjConfig,
obj::{ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection, SymbolRef},
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
};
fn configure_rabbitizer() {
static RABBITIZER_MUTEX: Mutex<()> = Mutex::new(());
fn configure_rabbitizer(abi: Abi) {
unsafe {
config::RabbitizerConfig_Cfg.reg_names.fpr_abi_names = Abi::O32;
config::RabbitizerConfig_Cfg.reg_names.fpr_abi_names = abi;
}
}
pub struct ObjArchMips {
pub endianness: Endianness,
pub abi: Abi,
pub instr_category: InstrCategory,
}
const EF_MIPS_ABI: u32 = 0x0000F000;
const EF_MIPS_MACH: u32 = 0x00FF0000;
const E_MIPS_MACH_ALLEGREX: u32 = 0x00840000;
const E_MIPS_MACH_5900: u32 = 0x00920000;
impl ObjArchMips {
pub fn new(object: &File) -> Result<Self> {
configure_rabbitizer();
Ok(Self { endianness: object.endianness() })
let mut abi = Abi::NUMERIC;
let mut instr_category = InstrCategory::CPU;
match object.flags() {
FileFlags::None => {}
FileFlags::Elf { e_flags, .. } => {
abi = match e_flags & EF_MIPS_ABI {
elf::EF_MIPS_ABI_O32 => Abi::O32,
elf::EF_MIPS_ABI_EABI32 | elf::EF_MIPS_ABI_EABI64 => Abi::N32,
_ => Abi::NUMERIC,
};
instr_category = match e_flags & EF_MIPS_MACH {
E_MIPS_MACH_ALLEGREX => InstrCategory::R4000ALLEGREX,
E_MIPS_MACH_5900 => InstrCategory::R5900,
_ => InstrCategory::CPU,
};
}
_ => bail!("Unsupported MIPS file flags"),
}
Ok(Self { endianness: object.endianness(), abi, instr_category })
}
}
impl ObjArch for ObjArchMips {
fn process_code(
&self,
obj: &ObjInfo,
symbol_ref: SymbolRef,
address: u64,
code: &[u8],
_section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u64>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
let (section, symbol) = obj.section_symbol(symbol_ref);
let section = section.ok_or_else(|| anyhow!("Code symbol section not found"))?;
let code = &section.data
[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
let _guard = RABBITIZER_MUTEX.lock().map_err(|e| anyhow!("Failed to lock mutex: {e}"))?;
configure_rabbitizer(match config.mips_abi {
MipsAbi::Auto => self.abi,
MipsAbi::O32 => Abi::O32,
MipsAbi::N32 => Abi::N32,
MipsAbi::N64 => Abi::N64,
});
let instr_category = match config.mips_instr_category {
MipsInstrCategory::Auto => self.instr_category,
MipsInstrCategory::Cpu => InstrCategory::CPU,
MipsInstrCategory::Rsp => InstrCategory::RSP,
MipsInstrCategory::R3000Gte => InstrCategory::R3000GTE,
MipsInstrCategory::R4000Allegrex => InstrCategory::R4000ALLEGREX,
MipsInstrCategory::R5900 => InstrCategory::R5900,
};
let start_address = symbol.address;
let end_address = symbol.address + symbol.size;
let start_address = address;
let end_address = address + code.len() as u64;
let ins_count = code.len() / 4;
let mut ops = Vec::<u16>::with_capacity(ins_count);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
let mut cur_addr = start_address as u32;
for chunk in code.chunks_exact(4) {
let reloc = section.relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
let code = self.endianness.read_u32_bytes(chunk.try_into()?);
let instruction = Instruction::new(code, cur_addr, InstrCategory::CPU);
let instruction = Instruction::new(code, cur_addr, instr_category);
let formatted = instruction.disassemble(None, 0);
let op = instruction.unique_id as u16;
@@ -112,7 +153,7 @@ impl ObjArch for ObjArchMips {
}
}
}
let line = section.line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
let line = line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
insts.push(ObjIns {
address: cur_addr as u64,
size: 4,

View File

@@ -1,25 +1,30 @@
use std::borrow::Cow;
use std::{borrow::Cow, collections::BTreeMap};
use anyhow::{bail, Result};
use object::{Architecture, Object, Relocation, RelocationFlags};
use crate::{
diff::DiffObjConfig,
obj::{ObjInfo, ObjIns, ObjSection, SymbolRef},
obj::{ObjIns, ObjReloc, ObjSection},
};
#[cfg(feature = "arm")]
mod arm;
#[cfg(feature = "mips")]
mod mips;
pub mod mips;
#[cfg(feature = "ppc")]
mod ppc;
pub mod ppc;
#[cfg(feature = "x86")]
mod x86;
pub mod x86;
pub trait ObjArch: Send + Sync {
fn process_code(
&self,
obj: &ObjInfo,
symbol_ref: SymbolRef,
address: u64,
code: &[u8],
section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u64>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult>;
@@ -44,6 +49,8 @@ pub fn new_arch(object: &object::File) -> Result<Box<dyn ObjArch>> {
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:?}"),
})
}

View File

@@ -1,13 +1,13 @@
use std::borrow::Cow;
use std::{borrow::Cow, collections::BTreeMap};
use anyhow::{anyhow, bail, Result};
use anyhow::{bail, Result};
use object::{elf, File, Relocation, RelocationFlags};
use ppc750cl::{Argument, InsIter, GPR};
use crate::{
arch::{ObjArch, ProcessCodeResult},
diff::DiffObjConfig,
obj::{ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection, SymbolRef},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
};
// Relative relocation, can be Simm, Offset or BranchDest
@@ -31,20 +31,18 @@ impl ObjArchPpc {
impl ObjArch for ObjArchPpc {
fn process_code(
&self,
obj: &ObjInfo,
symbol_ref: SymbolRef,
address: u64,
code: &[u8],
_section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u64>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
let (section, symbol) = obj.section_symbol(symbol_ref);
let section = section.ok_or_else(|| anyhow!("Code symbol section not found"))?;
let code = &section.data
[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
let ins_count = code.len() / 4;
let mut ops = Vec::<u16>::with_capacity(ins_count);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
for (cur_addr, mut ins) in InsIter::new(code, symbol.address as u32) {
let reloc = section.relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
for (cur_addr, mut ins) in InsIter::new(code, address as u32) {
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
if let Some(reloc) = reloc {
// Zero out relocations
ins.code = match reloc.flags {
@@ -133,7 +131,7 @@ impl ObjArch for ObjArchPpc {
}
ops.push(ins.op as u16);
let line = section.line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
let line = line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
insts.push(ObjIns {
address: cur_addr as u64,
size: 4,

View File

@@ -1,4 +1,4 @@
use std::borrow::Cow;
use std::{borrow::Cow, collections::BTreeMap};
use anyhow::{anyhow, bail, ensure, Result};
use iced_x86::{
@@ -11,7 +11,7 @@ use object::{pe, Endian, Endianness, File, Object, Relocation, RelocationFlags};
use crate::{
arch::{ObjArch, ProcessCodeResult},
diff::{DiffObjConfig, X86Formatter},
obj::{ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjSection, SymbolRef},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
};
pub struct ObjArchX86 {
@@ -28,17 +28,15 @@ impl ObjArchX86 {
impl ObjArch for ObjArchX86 {
fn process_code(
&self,
obj: &ObjInfo,
symbol_ref: SymbolRef,
address: u64,
code: &[u8],
_section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u64>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
let (section, symbol) = obj.section_symbol(symbol_ref);
let section = section.ok_or_else(|| anyhow!("Code symbol section not found"))?;
let code = &section.data
[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
let mut result = ProcessCodeResult { ops: Vec::new(), insts: Vec::new() };
let mut decoder = Decoder::with_ip(self.bits, code, symbol.address, DecoderOptions::NONE);
let mut decoder = Decoder::with_ip(self.bits, code, address, DecoderOptions::NONE);
let mut formatter: Box<dyn Formatter> = match config.x86_formatter {
X86Formatter::Intel => Box::new(IntelFormatter::new()),
X86Formatter::Gas => Box::new(GasFormatter::new()),
@@ -70,11 +68,10 @@ impl ObjArch for ObjArchX86 {
let address = instruction.ip();
let op = instruction.mnemonic() as u16;
let reloc = section
.relocations
let reloc = relocations
.iter()
.find(|r| r.address >= address && r.address < address + instruction.len() as u64);
let line = section.line_info.range(..=address).last().map(|(_, &b)| b);
let line = line_info.range(..=address).last().map(|(_, &b)| b);
output.ins = ObjIns {
address,
size: instruction.len() as u8,

View File

@@ -4,7 +4,7 @@ use std::{
time::{Duration, Instant},
};
use anyhow::Result;
use anyhow::{anyhow, Result};
use similar::{capture_diff_slices_deadline, Algorithm};
use crate::{
@@ -16,34 +16,48 @@ use crate::{
obj::{ObjInfo, ObjInsArg, ObjReloc, ObjSymbol, ObjSymbolFlags, SymbolRef},
};
pub fn no_diff_code(
pub fn process_code_symbol(
obj: &ObjInfo,
symbol_ref: SymbolRef,
config: &DiffObjConfig,
) -> Result<ObjSymbolDiff> {
let out = obj.arch.process_code(obj, symbol_ref, config)?;
) -> Result<ProcessCodeResult> {
let (section, symbol) = obj.section_symbol(symbol_ref);
let section = section.ok_or_else(|| anyhow!("Code symbol section not found"))?;
let code = &section.data
[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
obj.arch.process_code(
symbol.address,
code,
section.orig_index,
&section.relocations,
&section.line_info,
config,
)
}
pub fn no_diff_code(out: &ProcessCodeResult, symbol_ref: SymbolRef) -> Result<ObjSymbolDiff> {
let mut diff = Vec::<ObjInsDiff>::new();
for i in out.insts {
diff.push(ObjInsDiff { ins: Some(i), kind: ObjInsDiffKind::None, ..Default::default() });
for i in &out.insts {
diff.push(ObjInsDiff {
ins: Some(i.clone()),
kind: ObjInsDiffKind::None,
..Default::default()
});
}
resolve_branches(&mut diff);
Ok(ObjSymbolDiff { symbol_ref, diff_symbol: None, instructions: diff, match_percent: None })
}
pub fn diff_code(
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left_out: &ProcessCodeResult,
right_out: &ProcessCodeResult,
left_symbol_ref: SymbolRef,
right_symbol_ref: SymbolRef,
config: &DiffObjConfig,
) -> Result<(ObjSymbolDiff, ObjSymbolDiff)> {
let left_out = left_obj.arch.process_code(left_obj, left_symbol_ref, config)?;
let right_out = right_obj.arch.process_code(right_obj, right_symbol_ref, config)?;
let mut left_diff = Vec::<ObjInsDiff>::new();
let mut right_diff = Vec::<ObjInsDiff>::new();
diff_instructions(&mut left_diff, &mut right_diff, &left_out, &right_out)?;
diff_instructions(&mut left_diff, &mut right_diff, left_out, right_out)?;
resolve_branches(&mut left_diff);
resolve_branches(&mut right_diff);

View File

@@ -4,7 +4,7 @@ use anyhow::Result;
use crate::{
diff::{
code::{diff_code, no_diff_code},
code::{diff_code, no_diff_code, process_code_symbol},
data::{
diff_bss_section, diff_bss_symbol, diff_data_section, diff_data_symbol,
diff_text_section, no_diff_symbol,
@@ -13,29 +13,112 @@ use crate::{
obj::{ObjInfo, ObjIns, ObjSection, ObjSectionKind, ObjSymbol, SymbolRef},
};
mod code;
mod data;
pub mod code;
pub mod data;
pub mod display;
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[derive(
Debug,
Copy,
Clone,
Default,
Eq,
PartialEq,
serde::Deserialize,
serde::Serialize,
strum::VariantArray,
strum::EnumMessage,
)]
pub enum X86Formatter {
#[default]
#[strum(message = "Intel (default)")]
Intel,
#[strum(message = "AT&T")]
Gas,
#[strum(message = "NASM")]
Nasm,
#[strum(message = "MASM")]
Masm,
}
#[derive(
Debug,
Copy,
Clone,
Default,
Eq,
PartialEq,
serde::Deserialize,
serde::Serialize,
strum::VariantArray,
strum::EnumMessage,
)]
pub enum MipsAbi {
#[default]
#[strum(message = "Auto (default)")]
Auto,
#[strum(message = "O32")]
O32,
#[strum(message = "N32")]
N32,
#[strum(message = "N64")]
N64,
}
#[derive(
Debug,
Copy,
Clone,
Default,
Eq,
PartialEq,
serde::Deserialize,
serde::Serialize,
strum::VariantArray,
strum::EnumMessage,
)]
pub enum MipsInstrCategory {
#[default]
#[strum(message = "Auto (default)")]
Auto,
#[strum(message = "CPU")]
Cpu,
#[strum(message = "RSP (N64)")]
Rsp,
#[strum(message = "R3000 GTE (PS1)")]
R3000Gte,
#[strum(message = "R4000 ALLEGREX (PSP)")]
R4000Allegrex,
#[strum(message = "R5900 EE (PS2)")]
R5900,
}
#[inline]
const fn default_true() -> bool { true }
#[derive(Debug, Clone, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct DiffObjConfig {
pub relax_reloc_diffs: bool,
#[serde(default = "default_true")]
pub space_between_args: bool,
// x86
pub x86_formatter: X86Formatter,
// MIPS
pub mips_abi: MipsAbi,
pub mips_instr_category: MipsInstrCategory,
}
impl Default for DiffObjConfig {
fn default() -> Self {
Self {
relax_reloc_diffs: false,
space_between_args: true,
x86_formatter: Default::default(),
mips_abi: Default::default(),
mips_instr_category: Default::default(),
}
}
}
impl DiffObjConfig {
@@ -238,9 +321,11 @@ pub fn diff_objs(
let (right_obj, right_out) = right.as_mut().unwrap();
match section_kind {
ObjSectionKind::Code => {
let left_code = process_code_symbol(left_obj, left_symbol_ref, config)?;
let right_code = process_code_symbol(right_obj, right_symbol_ref, config)?;
let (left_diff, right_diff) = diff_code(
left_obj,
right_obj,
&left_code,
&right_code,
left_symbol_ref,
right_symbol_ref,
config,
@@ -250,9 +335,10 @@ pub fn diff_objs(
if let Some(prev_symbol_ref) = prev_symbol_ref {
let (prev_obj, prev_out) = prev.as_mut().unwrap();
let prev_code = process_code_symbol(prev_obj, prev_symbol_ref, config)?;
let (_, prev_diff) = diff_code(
right_obj,
prev_obj,
&right_code,
&prev_code,
right_symbol_ref,
prev_symbol_ref,
config,
@@ -286,8 +372,9 @@ pub fn diff_objs(
let (left_obj, left_out) = left.as_mut().unwrap();
match section_kind {
ObjSectionKind::Code => {
let code = process_code_symbol(left_obj, left_symbol_ref, config)?;
*left_out.symbol_diff_mut(left_symbol_ref) =
no_diff_code(left_obj, left_symbol_ref, config)?;
no_diff_code(&code, left_symbol_ref)?;
}
ObjSectionKind::Data | ObjSectionKind::Bss => {
*left_out.symbol_diff_mut(left_symbol_ref) =
@@ -299,8 +386,9 @@ pub fn diff_objs(
let (right_obj, right_out) = right.as_mut().unwrap();
match section_kind {
ObjSectionKind::Code => {
let code = process_code_symbol(right_obj, right_symbol_ref, config)?;
*right_out.symbol_diff_mut(right_symbol_ref) =
no_diff_code(right_obj, right_symbol_ref, config)?;
no_diff_code(&code, right_symbol_ref)?;
}
ObjSectionKind::Data | ObjSectionKind::Bss => {
*right_out.symbol_diff_mut(right_symbol_ref) =

View File

@@ -328,15 +328,10 @@ fn line_info(obj_file: &File<'_>, sections: &mut [ObjSection]) -> Result<()> {
if let Some(program) = unit.line_program.clone() {
let mut text_sections =
obj_file.sections().filter(|s| s.kind() == SectionKind::Text);
let section_index = text_sections
.next()
.ok_or_else(|| anyhow!("Next text section not found for line info"))?
.index()
.0;
let mut lines = sections
.iter_mut()
.find(|s| s.orig_index == section_index)
.map(|s| &mut s.line_info);
let section_index = text_sections.next().map(|s| s.index().0);
let mut lines = section_index.map(|index| {
&mut sections.iter_mut().find(|s| s.orig_index == index).unwrap().line_info
});
let mut rows = program.rows();
while let Some((_header, row)) = rows.next_row()? {

View File

@@ -1,6 +1,6 @@
[package]
name = "objdiff-gui"
version = "2.0.0-alpha.1"
version = "2.0.0-alpha.3"
edition = "2021"
rust-version = "1.70"
authors = ["Luke Street <luke@street.dev>"]
@@ -46,6 +46,7 @@ ron = "0.8.1"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0.116"
shell-escape = "0.1.5"
strum = { version = "0.26.2", features = ["derive"] }
tempfile = "3.10.1"
time = { version = "0.3.36", features = ["formatting", "local-offset"] }

View File

@@ -30,7 +30,7 @@ use crate::{
views::{
appearance::{appearance_window, Appearance},
config::{
config_ui, diff_config_window, project_window, ConfigViewState, CONFIG_DISABLED_TEXT,
arch_config_window, config_ui, project_window, ConfigViewState, CONFIG_DISABLED_TEXT,
},
data_diff::data_diff_ui,
debug::debug_window,
@@ -52,7 +52,7 @@ pub struct ViewState {
pub show_appearance_config: bool,
pub show_demangle: bool,
pub show_project_config: bool,
pub show_diff_config: bool,
pub show_arch_config: bool,
pub show_debug: bool,
}
@@ -414,7 +414,7 @@ impl eframe::App for App {
show_appearance_config,
show_demangle,
show_project_config,
show_diff_config,
show_arch_config,
show_debug,
} = view_state;
@@ -468,8 +468,8 @@ impl eframe::App for App {
}
});
ui.menu_button("Diff Options", |ui| {
if ui.button("More").clicked() {
*show_diff_config = !*show_diff_config;
if ui.button("Arch Settings").clicked() {
*show_arch_config = !*show_arch_config;
ui.close_menu();
}
let mut config = config.write().unwrap();
@@ -541,7 +541,7 @@ impl eframe::App for App {
project_window(ctx, config, show_project_config, config_state, appearance);
appearance_window(ctx, show_appearance_config, appearance);
demangle_window(ctx, show_demangle, demangle_state, appearance);
diff_config_window(ctx, config, show_diff_config, appearance);
arch_config_window(ctx, config, show_arch_config, appearance);
debug_window(ctx, show_debug, frame_history, appearance);
self.post_update(ctx);

View File

@@ -143,7 +143,7 @@ fn run_make_cmd(config: &BuildConfig, cwd: &Path, arg: &Path) -> Result<BuildSta
cmdline.push(' ');
cmdline.push_str(shell_escape::escape(arg.to_string_lossy()).as_ref());
}
let output = command.output().context("Failed to execute build")?;
let output = command.output().map_err(|e| anyhow!("Failed to execute build: {e}"))?;
let stdout = from_utf8(&output.stdout).context("Failed to process stdout")?;
let stderr = from_utf8(&output.stderr).context("Failed to process stderr")?;
Ok(BuildStatus {

View File

@@ -119,6 +119,8 @@ impl Appearance {
self.delete_color = Color32::from_rgb(200, 40, 41);
}
}
style.spacing.scroll = egui::style::ScrollStyle::solid();
style.spacing.scroll.bar_width = 10.0;
ctx.set_style(style);
}

View File

@@ -16,9 +16,10 @@ use egui::{
use globset::Glob;
use objdiff_core::{
config::{ProjectObject, DEFAULT_WATCH_PATTERNS},
diff::X86Formatter,
diff::{MipsAbi, MipsInstrCategory, X86Formatter},
};
use self_update::cargo_crate_version;
use strum::{EnumMessage, VariantArray};
use crate::{
app::{AppConfig, AppConfigRef, ObjectConfig},
@@ -842,29 +843,28 @@ fn split_obj_config_ui(
});
}
pub fn diff_config_window(
pub fn arch_config_window(
ctx: &egui::Context,
config: &AppConfigRef,
show: &mut bool,
appearance: &Appearance,
) {
let mut config_guard = config.write().unwrap();
egui::Window::new("Diff Config").open(show).show(ctx, |ui| {
diff_config_ui(ui, &mut config_guard, appearance);
egui::Window::new("Arch Settings").open(show).show(ctx, |ui| {
arch_config_ui(ui, &mut config_guard, appearance);
});
}
fn diff_config_ui(ui: &mut egui::Ui, config: &mut AppConfig, _appearance: &Appearance) {
egui::ComboBox::new("x86_formatter", "X86 Format")
.selected_text(format!("{:?}", config.diff_obj_config.x86_formatter))
fn arch_config_ui(ui: &mut egui::Ui, config: &mut AppConfig, _appearance: &Appearance) {
ui.heading("x86");
egui::ComboBox::new("x86_formatter", "Format")
.selected_text(config.diff_obj_config.x86_formatter.get_message().unwrap())
.show_ui(ui, |ui| {
for &formatter in
&[X86Formatter::Intel, X86Formatter::Gas, X86Formatter::Nasm, X86Formatter::Masm]
{
for &formatter in X86Formatter::VARIANTS {
if ui
.selectable_label(
config.diff_obj_config.x86_formatter == formatter,
format!("{:?}", formatter),
formatter.get_message().unwrap(),
)
.clicked()
{
@@ -873,4 +873,38 @@ fn diff_config_ui(ui: &mut egui::Ui, config: &mut AppConfig, _appearance: &Appea
}
}
});
ui.separator();
ui.heading("MIPS");
egui::ComboBox::new("mips_abi", "ABI")
.selected_text(config.diff_obj_config.mips_abi.get_message().unwrap())
.show_ui(ui, |ui| {
for &abi in MipsAbi::VARIANTS {
if ui
.selectable_label(
config.diff_obj_config.mips_abi == abi,
abi.get_message().unwrap(),
)
.clicked()
{
config.diff_obj_config.mips_abi = abi;
config.queue_reload = true;
}
}
});
egui::ComboBox::new("mips_instr_category", "Instruction Category")
.selected_text(config.diff_obj_config.mips_instr_category.get_message().unwrap())
.show_ui(ui, |ui| {
for &category in MipsInstrCategory::VARIANTS {
if ui
.selectable_label(
config.diff_obj_config.mips_instr_category == category,
category.get_message().unwrap(),
)
.clicked()
{
config.diff_obj_config.mips_instr_category = category;
config.queue_reload = true;
}
}
});
}