Split into objdiff-core / objdiff-gui; update egui to 0.26.2

This commit is contained in:
Luke Street 2024-02-26 18:43:26 -07:00
parent 0a85c498c5
commit 4eba5f71b0
43 changed files with 1127 additions and 1000 deletions

View File

@ -10,7 +10,7 @@ on:
env:
BUILD_PROFILE: release-lto
CARGO_BIN_NAME: objdiff
CARGO_BIN_NAME: objdiff-gui
CARGO_TARGET_DIR: target
jobs:
@ -25,7 +25,7 @@ jobs:
sudo apt-get update
sudo apt-get -y install libgtk-3-dev
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
@ -42,7 +42,7 @@ jobs:
RUSTFLAGS: -D warnings
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Rust toolchain
# We use nightly options in rustfmt.toml
uses: dtolnay/rust-toolchain@nightly
@ -62,7 +62,7 @@ jobs:
# Prevent new advisories from failing CI
continue-on-error: ${{ matrix.checks == 'advisories' }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v1
with:
command: check ${{ matrix.checks }}
@ -82,7 +82,7 @@ jobs:
sudo apt-get update
sudo apt-get -y install libgtk-3-dev
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cargo test
@ -119,7 +119,7 @@ jobs:
sudo apt-get update
sudo apt-get -y install ${{ matrix.packages }}
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
@ -127,7 +127,7 @@ jobs:
- name: Cargo build
run: cargo build --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }} --bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.name }}
path: |
@ -144,7 +144,7 @@ jobs:
needs: [ build ]
steps:
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Rename artifacts
@ -152,7 +152,7 @@ jobs:
run: |
mkdir ../out
for i in */*/$BUILD_PROFILE/$CARGO_BIN_NAME*; do
mv "$i" "../out/$(sed -E "s/([^/]+)\/[^/]+\/$BUILD_PROFILE\/($CARGO_BIN_NAME)/\2-\1/" <<< "$i")"
mv "$i" "../out/$(sed -E "s/([^/]+)\/[^/]+\/$BUILD_PROFILE\/$CARGO_BIN_NAME/objdiff-\1/" <<< "$i")"
done
ls -R ../out
- name: Release

1233
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,96 +1,11 @@
[package]
name = "objdiff"
version = "1.0.0"
edition = "2021"
rust-version = "1.70"
authors = ["Luke Street <luke@street.dev>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/encounter/objdiff"
readme = "README.md"
description = """
A local diffing tool for decompilation projects.
"""
publish = false
build = "build.rs"
[workspace]
members = [
"objdiff-core",
"objdiff-gui",
]
resolver = "2"
[profile.release-lto]
inherits = "release"
lto = "thin"
strip = "debuginfo"
[features]
default = ["wgpu", "wsl"]
wgpu = ["eframe/wgpu"]
wsl = []
[dependencies]
anyhow = "1.0.79"
byteorder = "1.5.0"
bytes = "1.5.0"
cfg-if = "1.0.0"
const_format = "0.2.32"
cwdemangle = "0.1.6"
dirs = "5.0.1"
eframe = { version = "0.25.0", features = ["persistence"] }
egui = "0.25.0"
egui_extras = "0.25.0"
filetime = "0.2.23"
flagset = "0.4.4"
float-ord = "0.3.2"
font-kit = "0.12.0"
gimli = { version = "0.28.1", default-features = false, features = ["read-all"] }
globset = { version = "0.4.14", features = ["serde1"] }
log = "0.4.20"
memmap2 = "0.9.3"
notify = "6.1.1"
object = { version = "0.32.2", features = ["read_core", "std", "elf"], default-features = false }
png = "0.17.11"
pollster = "0.3.0"
ppc750cl = { git = "https://github.com/encounter/ppc750cl", rev = "4a2bbbc6f84dcb76255ab6f3595a8d4a0ce96618" }
rabbitizer = "1.8.1"
rfd = { version = "0.13.0" } #, default-features = false, features = ['xdg-portal']
ron = "0.8.1"
semver = "1.0.21"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0.111"
serde_yaml = "0.9.30"
shell-escape = "0.1.5"
similar = "2.4.0"
tempfile = "3.9.0"
thiserror = "1.0.56"
time = { version = "0.3.31", features = ["formatting", "local-offset"] }
toml = "0.8.8"
twox-hash = "1.6.3"
# For Linux static binaries, use rustls
[target.'cfg(target_os = "linux")'.dependencies]
reqwest = { version = "0.11.23", default-features = false, features = ["blocking", "json", "multipart", "rustls"] }
self_update = { version = "0.39.0", default-features = false, features = ["rustls"] }
# For all other platforms, use native TLS
[target.'cfg(not(target_os = "linux"))'.dependencies]
reqwest = { version = "0.11.23", default-features = false, features = ["blocking", "json", "multipart", "default-tls"] }
self_update = "0.39.0"
[target.'cfg(windows)'.dependencies]
path-slash = "0.2.1"
winapi = "0.3.9"
[target.'cfg(windows)'.build-dependencies]
winres = "0.1.12"
[target.'cfg(unix)'.dependencies]
exec = "0.3.1"
# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tracing-subscriber = "0.3"
# web:
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.7"
tracing-wasm = "0.2"
[build-dependencies]
anyhow = "1.0.79"
vergen = { version = "8.3.1", features = ["build", "cargo", "git", "gitcl"] }

36
objdiff-core/Cargo.toml Normal file
View File

@ -0,0 +1,36 @@
[package]
name = "objdiff-core"
version = "1.0.0"
edition = "2021"
rust-version = "1.70"
authors = ["Luke Street <luke@street.dev>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/encounter/objdiff"
readme = "../README.md"
description = """
A local diffing tool for decompilation projects.
"""
[features]
all = ["dwarf", "mips", "ppc"]
any-arch = [] # Implicit, used to check if any arch is enabled
dwarf = ["gimli"]
mips = ["any-arch", "rabbitizer"]
ppc = ["any-arch", "cwdemangle", "ppc750cl"]
[dependencies]
anyhow = "1.0.79"
byteorder = "1.5.0"
cwdemangle = { version = "0.1.6", optional = true }
filetime = "0.2.23"
flagset = "0.4.4"
gimli = { version = "0.28.1", default-features = false, features = ["read-all"], optional = true }
log = "0.4.20"
memmap2 = "0.9.3"
num-traits = "0.2.18"
object = { version = "0.32.2", features = ["read_core", "std", "elf"], default-features = false }
ppc750cl = { git = "https://github.com/encounter/ppc750cl", rev = "4a2bbbc6f84dcb76255ab6f3595a8d4a0ce96618", optional = true }
rabbitizer = { version = "1.8.1", optional = true }
serde = { version = "1", features = ["derive"] }
similar = "2.4.0"
twox-hash = "1.6.3"

View File

@ -12,9 +12,10 @@ use crate::{
editops::{editops_find, LevEditType},
DiffAlg, DiffObjConfig, ProcessCodeResult,
},
obj,
obj::{
mips, ppc, ObjArchitecture, ObjInfo, ObjInsArg, ObjInsArgDiff, ObjInsBranchFrom,
ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind, ObjReloc, ObjSymbol, ObjSymbolFlags,
ObjArchitecture, ObjInfo, ObjInsArg, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo,
ObjInsDiff, ObjInsDiffKind, ObjReloc, ObjSymbol, ObjSymbolFlags,
},
};
@ -27,9 +28,13 @@ pub fn no_diff_code(
) -> Result<()> {
let code =
&data[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
let out = match arch {
ObjArchitecture::PowerPc => ppc::process_code(code, symbol.address, relocs, line_info)?,
ObjArchitecture::Mips => mips::process_code(
let out: ProcessCodeResult = match arch {
#[cfg(feature = "ppc")]
ObjArchitecture::PowerPc => {
obj::ppc::process_code(code, symbol.address, relocs, line_info)?
}
#[cfg(feature = "mips")]
ObjArchitecture::Mips => obj::mips::process_code(
code,
symbol.address,
symbol.address + symbol.size,
@ -65,19 +70,26 @@ pub fn diff_code(
let right_code = &right_data[right_symbol.section_address as usize
..(right_symbol.section_address + right_symbol.size) as usize];
let (left_out, right_out) = match arch {
#[cfg(feature = "ppc")]
ObjArchitecture::PowerPc => (
ppc::process_code(left_code, left_symbol.address, left_relocs, left_line_info)?,
ppc::process_code(right_code, right_symbol.address, right_relocs, right_line_info)?,
obj::ppc::process_code(left_code, left_symbol.address, left_relocs, left_line_info)?,
obj::ppc::process_code(
right_code,
right_symbol.address,
right_relocs,
right_line_info,
)?,
),
#[cfg(feature = "mips")]
ObjArchitecture::Mips => (
mips::process_code(
obj::mips::process_code(
left_code,
left_symbol.address,
left_symbol.address + left_symbol.size,
left_relocs,
left_line_info,
)?,
mips::process_code(
obj::mips::process_code(
right_code,
right_symbol.address,
left_symbol.address + left_symbol.size,
@ -235,12 +247,8 @@ fn diff_instructions_lev(
cur_right = right_iter.next();
}
if let (Some(left), Some(right)) = (cur_left, cur_right) {
if (left.address - left_symbol.address as u32) != left_addr {
return Err(anyhow::Error::msg("Instruction address mismatch (left)"));
}
if (right.address - right_symbol.address as u32) != right_addr {
return Err(anyhow::Error::msg("Instruction address mismatch (right)"));
}
debug_assert_eq!(left.address - left_symbol.address as u32, left_addr);
debug_assert_eq!(right.address - right_symbol.address as u32, right_addr);
match op.op_type {
LevEditType::Replace => {
left_diff.push(ObjInsDiff { ins: Some(left.clone()), ..ObjInsDiff::default() });
@ -360,8 +368,8 @@ fn arg_eq(
right_diff: &ObjInsDiff,
) -> bool {
return match left {
ObjInsArg::PpcArg(l) => match right {
ObjInsArg::PpcArg(r) => format!("{l}") == format!("{r}"),
ObjInsArg::Arg(l) | ObjInsArg::ArgWithBase(l) => match right {
ObjInsArg::Arg(r) | ObjInsArg::ArgWithBase(r) => l == r,
_ => false,
},
ObjInsArg::Reloc => {
@ -380,9 +388,6 @@ fn arg_eq(
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
)
}
ObjInsArg::MipsArg(ls) | ObjInsArg::MipsArgWithBase(ls) => {
matches!(right, ObjInsArg::MipsArg(rs) | ObjInsArg::MipsArgWithBase(rs) if ls == rs)
}
ObjInsArg::BranchOffset(_) => {
// Compare dest instruction idx after diffing
left_diff.branch_to.as_ref().map(|b| b.ins_idx)
@ -436,9 +441,8 @@ fn compare_ins(
state.diff_count += 1;
}
let a_str = match a {
ObjInsArg::PpcArg(arg) => format!("{arg}"),
ObjInsArg::Arg(arg) | ObjInsArg::ArgWithBase(arg) => arg.to_string(),
ObjInsArg::Reloc | ObjInsArg::RelocWithBase => String::new(),
ObjInsArg::MipsArg(str) | ObjInsArg::MipsArgWithBase(str) => str.clone(),
ObjInsArg::BranchOffset(arg) => format!("{arg}"),
};
let a_diff = if let Some(idx) = state.left_args_idx.get(&a_str) {
@ -450,9 +454,8 @@ fn compare_ins(
ObjInsArgDiff { idx }
};
let b_str = match b {
ObjInsArg::PpcArg(arg) => format!("{arg}"),
ObjInsArg::Arg(arg) | ObjInsArg::ArgWithBase(arg) => arg.to_string(),
ObjInsArg::Reloc | ObjInsArg::RelocWithBase => String::new(),
ObjInsArg::MipsArg(str) | ObjInsArg::MipsArgWithBase(str) => str.clone(),
ObjInsArg::BranchOffset(arg) => format!("{arg}"),
};
let b_diff = if let Some(idx) = state.right_args_idx.get(&b_str) {

View File

@ -4,7 +4,7 @@ use std::{
time::{Duration, Instant},
};
use anyhow::{bail, Result};
use anyhow::{ensure, Result};
use similar::{capture_diff_slices_deadline, Algorithm};
use crate::{
@ -177,15 +177,14 @@ pub fn diff_data_similar(
pub fn diff_data_lev(left: &mut ObjSection, right: &mut ObjSection) -> Result<()> {
let matrix_size = (left.data.len() as u64).saturating_mul(right.data.len() as u64);
if matrix_size > 1_000_000_000 {
bail!(
"Data section {} too large for Levenshtein diff ({} * {} = {})",
left.name,
left.data.len(),
right.data.len(),
matrix_size
);
}
ensure!(
matrix_size < 1_000_000_000,
"Data section {} too large for Levenshtein diff ({} * {} = {})",
left.name,
left.data.len(),
right.data.len(),
matrix_size
);
let edit_ops = editops_find(&left.data, &right.data);
if edit_ops.is_empty() && !left.data.is_empty() {

6
objdiff-core/src/lib.rs Normal file
View File

@ -0,0 +1,6 @@
pub mod diff;
pub mod obj;
pub mod util;
#[cfg(not(feature = "any-arch"))]
compile_error!("At least one architecture feature must be enabled.");

View File

@ -1,8 +1,7 @@
use std::{borrow::Cow, collections::BTreeMap, fs, io::Cursor, path::Path};
use anyhow::{anyhow, bail, Context, Result};
use anyhow::{anyhow, bail, ensure, Context, Result};
use byteorder::{BigEndian, ReadBytesExt};
use cwdemangle::demangle;
use filetime::FileTime;
use flagset::Flags;
use object::{
@ -53,9 +52,14 @@ fn to_obj_symbol(obj_file: &File<'_>, symbol: &Symbol<'_, '_>, addend: i64) -> R
} else {
symbol.address()
};
let mut demangled_name = None;
#[cfg(feature = "ppc")]
if obj_file.architecture() == Architecture::PowerPc {
demangled_name = cwdemangle::demangle(name, &Default::default());
}
Ok(ObjSymbol {
name: name.to_string(),
demangled_name: demangle(name, &Default::default()),
demangled_name,
address: symbol.address(),
section_address,
size: symbol.size(),
@ -194,16 +198,12 @@ fn relocations_by_section(
RelocationTarget::Symbol(idx) => obj_file
.symbol_by_index(idx)
.context("Failed to locate relocation target symbol")?,
_ => {
return Err(anyhow::Error::msg(format!(
"Unhandled relocation target: {:?}",
reloc.target()
)));
}
_ => bail!("Unhandled relocation target: {:?}", reloc.target()),
};
let kind = match reloc.kind() {
RelocationKind::Absolute => ObjRelocKind::Absolute,
RelocationKind::Elf(kind) => match arch {
#[cfg(feature = "ppc")]
ObjArchitecture::PowerPc => match kind {
elf::R_PPC_ADDR16_LO => ObjRelocKind::PpcAddr16Lo,
elf::R_PPC_ADDR16_HI => ObjRelocKind::PpcAddr16Hi,
@ -211,12 +211,9 @@ fn relocations_by_section(
elf::R_PPC_REL24 => ObjRelocKind::PpcRel24,
elf::R_PPC_REL14 => ObjRelocKind::PpcRel14,
elf::R_PPC_EMB_SDA21 => ObjRelocKind::PpcEmbSda21,
_ => {
return Err(anyhow::Error::msg(format!(
"Unhandled PPC relocation type: {kind}"
)))
}
_ => bail!("Unhandled PPC relocation type: {kind}"),
},
#[cfg(feature = "mips")]
ObjArchitecture::Mips => match kind {
elf::R_MIPS_26 => ObjRelocKind::Mips26,
elf::R_MIPS_HI16 => ObjRelocKind::MipsHi16,
@ -228,12 +225,7 @@ fn relocations_by_section(
_ => bail!("Unhandled MIPS relocation type: {kind}"),
},
},
_ => {
return Err(anyhow::Error::msg(format!(
"Unhandled relocation type: {:?}",
reloc.kind()
)))
}
_ => bail!("Unhandled relocation type: {:?}", reloc.kind()),
};
let target_section = match symbol.section() {
SymbolSection::Common => Some(".comm".to_string()),
@ -248,12 +240,16 @@ fn relocations_by_section(
);
match kind {
ObjRelocKind::Absolute => addend as i64,
#[cfg(feature = "mips")]
ObjRelocKind::MipsHi16 => ((addend & 0x0000FFFF) << 16) as i32 as i64,
#[cfg(feature = "mips")]
ObjRelocKind::MipsLo16
| ObjRelocKind::MipsGot16
| ObjRelocKind::MipsCall16
| ObjRelocKind::MipsGpRel16 => (addend & 0x0000FFFF) as i16 as i64,
#[cfg(feature = "mips")]
ObjRelocKind::MipsGpRel32 => addend as i32 as i64,
#[cfg(feature = "mips")]
ObjRelocKind::Mips26 => ((addend & 0x03FFFFFF) << 2) as i64,
_ => bail!("Unsupported implicit relocation {kind:?}"),
}
@ -266,9 +262,7 @@ fn relocations_by_section(
to_obj_symbol(obj_file, &symbol, addend)
}
SymbolKind::Section => {
if addend < 0 {
return Err(anyhow::Error::msg(format!("Negative addend in reloc: {addend}")));
}
ensure!(addend >= 0, "Negative addend in reloc: {addend}");
find_section_symbol(obj_file, &symbol, addend as u64)
}
kind => Err(anyhow!("Unhandled relocation symbol type {kind:?}")),
@ -302,27 +296,30 @@ fn line_info(obj_file: &File<'_>) -> Result<Option<BTreeMap<u64, u64>>> {
}
// DWARF 2+
let dwarf_cow = gimli::Dwarf::load(|id| {
Ok::<_, gimli::Error>(
obj_file
.section_by_name(id.name())
.and_then(|section| section.uncompressed_data().ok())
.unwrap_or(Cow::Borrowed(&[][..])),
)
})?;
let endian = match obj_file.endianness() {
Endianness::Little => gimli::RunTimeEndian::Little,
Endianness::Big => gimli::RunTimeEndian::Big,
};
let dwarf = dwarf_cow.borrow(|section| gimli::EndianSlice::new(section, endian));
let mut iter = dwarf.units();
while let Some(header) = iter.next()? {
let unit = dwarf.unit(header)?;
if let Some(program) = unit.line_program.clone() {
let mut rows = program.rows();
while let Some((_header, row)) = rows.next_row()? {
if let Some(line) = row.line() {
map.insert(row.address(), line.get());
#[cfg(feature = "dwarf")]
{
let dwarf_cow = gimli::Dwarf::load(|id| {
Ok::<_, gimli::Error>(
obj_file
.section_by_name(id.name())
.and_then(|section| section.uncompressed_data().ok())
.unwrap_or(Cow::Borrowed(&[][..])),
)
})?;
let endian = match obj_file.endianness() {
Endianness::Little => gimli::RunTimeEndian::Little,
Endianness::Big => gimli::RunTimeEndian::Big,
};
let dwarf = dwarf_cow.borrow(|section| gimli::EndianSlice::new(section, endian));
let mut iter = dwarf.units();
while let Some(header) = iter.next()? {
let unit = dwarf.unit(header)?;
if let Some(program) = unit.line_program.clone() {
let mut rows = program.rows();
while let Some((_header, row)) = rows.next_row()? {
if let Some(line) = row.line() {
map.insert(row.address(), line.get());
}
}
}
}
@ -341,14 +338,11 @@ pub fn read(obj_path: &Path) -> Result<ObjInfo> {
};
let obj_file = File::parse(&*data)?;
let architecture = match obj_file.architecture() {
#[cfg(feature = "ppc")]
Architecture::PowerPc => ObjArchitecture::PowerPc,
#[cfg(feature = "mips")]
Architecture::Mips => ObjArchitecture::Mips,
_ => {
return Err(anyhow::Error::msg(format!(
"Unsupported architecture: {:?}",
obj_file.architecture()
)))
}
_ => bail!("Unsupported architecture: {:?}", obj_file.architecture()),
};
let mut result = ObjInfo {
architecture,

View File

@ -5,7 +5,7 @@ use rabbitizer::{config, Abi, InstrCategory, Instruction, OperandType};
use crate::{
diff::ProcessCodeResult,
obj::{ObjIns, ObjInsArg, ObjReloc},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc},
};
fn configure_rabbitizer() {
@ -63,23 +63,27 @@ pub fn process_code(
args.push(ObjInsArg::Reloc);
}
} else {
args.push(ObjInsArg::MipsArg(op.disassemble(&instruction, None)));
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
op.disassemble(&instruction, None),
)));
}
}
OperandType::cpu_immediate_base => {
if reloc.is_some() {
args.push(ObjInsArg::RelocWithBase);
} else {
args.push(ObjInsArg::MipsArgWithBase(
args.push(ObjInsArg::ArgWithBase(ObjInsArgValue::Opaque(
OperandType::cpu_immediate.disassemble(&instruction, None),
));
)));
}
args.push(ObjInsArg::MipsArg(
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
OperandType::cpu_rs.disassemble(&instruction, None),
));
)));
}
_ => {
args.push(ObjInsArg::MipsArg(op.disassemble(&instruction, None)));
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
op.disassemble(&instruction, None),
)));
}
}
}

View File

@ -1,12 +1,16 @@
pub mod elf;
#[cfg(feature = "mips")]
pub mod mips;
#[cfg(feature = "ppc")]
pub mod ppc;
use std::{collections::BTreeMap, path::PathBuf};
use std::{collections::BTreeMap, fmt, path::PathBuf};
use filetime::FileTime;
use flagset::{flags, FlagSet};
use crate::util::ReallySigned;
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum ObjSectionKind {
Code,
@ -23,7 +27,8 @@ flags! {
}
}
#[derive(Debug, Copy, Clone, Default)]
pub struct ObjSymbolFlagSet(pub(crate) FlagSet<ObjSymbolFlags>);
pub struct ObjSymbolFlagSet(pub FlagSet<ObjSymbolFlags>);
#[derive(Debug, Clone)]
pub struct ObjSection {
pub name: String,
@ -40,11 +45,40 @@ pub struct ObjSection {
pub match_percent: f32,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum ObjInsArgValue {
Signed(i16),
Unsigned(u16),
Opaque(String),
}
impl ObjInsArgValue {
pub fn loose_eq(&self, other: &ObjInsArgValue) -> bool {
match (self, other) {
(ObjInsArgValue::Signed(a), ObjInsArgValue::Signed(b)) => a == b,
(ObjInsArgValue::Unsigned(a), ObjInsArgValue::Unsigned(b)) => a == b,
(ObjInsArgValue::Signed(a), ObjInsArgValue::Unsigned(b))
| (ObjInsArgValue::Unsigned(b), ObjInsArgValue::Signed(a)) => *a as u16 == *b,
(ObjInsArgValue::Opaque(a), ObjInsArgValue::Opaque(b)) => a == b,
_ => false,
}
}
}
impl fmt::Display for ObjInsArgValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ObjInsArgValue::Signed(v) => write!(f, "{:#x}", ReallySigned(*v)),
ObjInsArgValue::Unsigned(v) => write!(f, "{:#x}", v),
ObjInsArgValue::Opaque(v) => write!(f, "{}", v),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum ObjInsArg {
PpcArg(ppc750cl::Argument),
MipsArg(String),
MipsArgWithBase(String),
Arg(ObjInsArgValue),
ArgWithBase(ObjInsArgValue),
Reloc,
RelocWithBase,
BranchOffset(i32),
@ -53,29 +87,8 @@ pub enum ObjInsArg {
impl ObjInsArg {
pub fn loose_eq(&self, other: &ObjInsArg) -> bool {
match (self, other) {
(ObjInsArg::PpcArg(a), ObjInsArg::PpcArg(b)) => {
a == b
|| match (a, b) {
// Consider Simm and Offset equivalent
(ppc750cl::Argument::Simm(simm), ppc750cl::Argument::Offset(off))
| (ppc750cl::Argument::Offset(off), ppc750cl::Argument::Simm(simm)) => {
simm.0 == off.0
}
// Consider Uimm and Offset equivalent
(ppc750cl::Argument::Uimm(uimm), ppc750cl::Argument::Offset(off))
| (ppc750cl::Argument::Offset(off), ppc750cl::Argument::Uimm(uimm)) => {
uimm.0 == off.0 as u16
}
// Consider Uimm and Simm equivalent
(ppc750cl::Argument::Uimm(uimm), ppc750cl::Argument::Simm(simm))
| (ppc750cl::Argument::Simm(simm), ppc750cl::Argument::Uimm(uimm)) => {
uimm.0 == simm.0 as u16
}
_ => false,
}
}
(ObjInsArg::MipsArg(a), ObjInsArg::MipsArg(b)) => a == b,
(ObjInsArg::MipsArgWithBase(a), ObjInsArg::MipsArgWithBase(b)) => a == b,
(ObjInsArg::Arg(a), ObjInsArg::Arg(b)) => a.loose_eq(b),
(ObjInsArg::ArgWithBase(a), ObjInsArg::ArgWithBase(b)) => a.loose_eq(b),
(ObjInsArg::Reloc, ObjInsArg::Reloc) => true,
(ObjInsArg::RelocWithBase, ObjInsArg::RelocWithBase) => true,
(ObjInsArg::BranchOffset(a), ObjInsArg::BranchOffset(b)) => a == b,
@ -89,6 +102,7 @@ pub struct ObjInsArgDiff {
/// Incrementing index for coloring
pub idx: usize,
}
#[derive(Debug, Clone)]
pub struct ObjInsBranchFrom {
/// Source instruction indices
@ -96,6 +110,7 @@ pub struct ObjInsBranchFrom {
/// Incrementing index for coloring
pub branch_idx: usize,
}
#[derive(Debug, Clone)]
pub struct ObjInsBranchTo {
/// Target instruction index
@ -103,6 +118,7 @@ pub struct ObjInsBranchTo {
/// Incrementing index for coloring
pub branch_idx: usize,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub enum ObjInsDiffKind {
#[default]
@ -113,6 +129,7 @@ pub enum ObjInsDiffKind {
Delete,
Insert,
}
#[derive(Debug, Clone)]
pub struct ObjIns {
pub address: u32,
@ -127,6 +144,7 @@ pub struct ObjIns {
/// Original (unsimplified) instruction
pub orig: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct ObjInsDiff {
pub ins: Option<ObjIns>,
@ -139,6 +157,7 @@ pub struct ObjInsDiff {
/// Arg diffs
pub arg_diff: Vec<Option<ObjInsArgDiff>>,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub enum ObjDataDiffKind {
#[default]
@ -147,6 +166,7 @@ pub enum ObjDataDiffKind {
Delete,
Insert,
}
#[derive(Debug, Clone, Default)]
pub struct ObjDataDiff {
pub data: Vec<u8>,
@ -154,6 +174,7 @@ pub struct ObjDataDiff {
pub len: usize,
pub symbol: String,
}
#[derive(Debug, Clone)]
pub struct ObjSymbol {
pub name: String,
@ -170,11 +191,15 @@ pub struct ObjSymbol {
pub instructions: Vec<ObjInsDiff>,
pub match_percent: Option<f32>,
}
#[derive(Debug, Copy, Clone)]
pub enum ObjArchitecture {
#[cfg(feature = "ppc")]
PowerPc,
#[cfg(feature = "mips")]
Mips,
}
#[derive(Debug, Clone)]
pub struct ObjInfo {
pub architecture: ObjArchitecture,
@ -184,27 +209,46 @@ pub struct ObjInfo {
pub common: Vec<ObjSymbol>,
pub line_info: Option<BTreeMap<u64, u64>>,
}
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum ObjRelocKind {
Absolute,
#[cfg(feature = "ppc")]
PpcAddr16Hi,
#[cfg(feature = "ppc")]
PpcAddr16Ha,
#[cfg(feature = "ppc")]
PpcAddr16Lo,
// #[cfg(feature = "ppc")]
// PpcAddr32,
// #[cfg(feature = "ppc")]
// PpcRel32,
// #[cfg(feature = "ppc")]
// PpcAddr24,
#[cfg(feature = "ppc")]
PpcRel24,
// #[cfg(feature = "ppc")]
// PpcAddr14,
#[cfg(feature = "ppc")]
PpcRel14,
#[cfg(feature = "ppc")]
PpcEmbSda21,
#[cfg(feature = "mips")]
Mips26,
#[cfg(feature = "mips")]
MipsHi16,
#[cfg(feature = "mips")]
MipsLo16,
#[cfg(feature = "mips")]
MipsGot16,
#[cfg(feature = "mips")]
MipsCall16,
#[cfg(feature = "mips")]
MipsGpRel16,
#[cfg(feature = "mips")]
MipsGpRel32,
}
#[derive(Debug, Clone)]
pub struct ObjReloc {
pub kind: ObjRelocKind,

View File

@ -5,20 +5,26 @@ use ppc750cl::{disasm_iter, Argument, SimplifiedIns};
use crate::{
diff::ProcessCodeResult,
obj::{ObjIns, ObjInsArg, ObjReloc, ObjRelocKind},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjRelocKind},
};
// Relative relocation, can be Simm or BranchOffset
fn is_relative_arg(arg: &ObjInsArg) -> bool {
matches!(arg, ObjInsArg::PpcArg(Argument::Simm(_)) | ObjInsArg::BranchOffset(_))
matches!(arg, ObjInsArg::Arg(ObjInsArgValue::Signed(_)) | ObjInsArg::BranchOffset(_))
}
// Relative or absolute relocation, can be Uimm, Simm or Offset
fn is_rel_abs_arg(arg: &ObjInsArg) -> bool {
matches!(arg, ObjInsArg::PpcArg(arg) if matches!(arg, Argument::Uimm(_) | Argument::Simm(_) | Argument::Offset(_)))
matches!(
arg,
ObjInsArg::Arg(ObjInsArgValue::Signed(_) | ObjInsArgValue::Unsigned(_))
| ObjInsArg::ArgWithBase(ObjInsArgValue::Signed(_))
)
}
fn is_offset_arg(arg: &ObjInsArg) -> bool { matches!(arg, ObjInsArg::PpcArg(Argument::Offset(_))) }
fn is_offset_arg(arg: &ObjInsArg) -> bool {
matches!(arg, ObjInsArg::ArgWithBase(ObjInsArgValue::Signed(_)))
}
pub fn process_code(
data: &[u8],
@ -48,8 +54,13 @@ pub fn process_code(
.args
.iter()
.map(|a| match a {
Argument::Simm(simm) => ObjInsArg::Arg(ObjInsArgValue::Signed(simm.0)),
Argument::Uimm(uimm) => ObjInsArg::Arg(ObjInsArgValue::Unsigned(uimm.0)),
Argument::Offset(offset) => {
ObjInsArg::ArgWithBase(ObjInsArgValue::Signed(offset.0))
}
Argument::BranchDest(dest) => ObjInsArg::BranchOffset(dest.0),
_ => ObjInsArg::PpcArg(a.clone()),
_ => ObjInsArg::Arg(ObjInsArgValue::Opaque(a.to_string())),
})
.collect();
if let Some(reloc) = reloc {

24
objdiff-core/src/util.rs Normal file
View File

@ -0,0 +1,24 @@
use std::fmt::{LowerHex, UpperHex};
use num_traits::PrimInt;
// https://stackoverflow.com/questions/44711012/how-do-i-format-a-signed-integer-to-a-sign-aware-hexadecimal-representation
pub(crate) struct ReallySigned<N: PrimInt>(pub(crate) N);
impl<N: PrimInt> LowerHex for ReallySigned<N> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let num = self.0.to_i32().unwrap();
let prefix = if f.alternate() { "0x" } else { "" };
let bare_hex = format!("{:x}", num.abs());
f.pad_integral(num >= 0, prefix, &bare_hex)
}
}
impl<N: PrimInt> UpperHex for ReallySigned<N> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let num = self.0.to_i32().unwrap();
let prefix = if f.alternate() { "0x" } else { "" };
let bare_hex = format!("{:X}", num.abs());
f.pad_integral(num >= 0, prefix, &bare_hex)
}
}

83
objdiff-gui/Cargo.toml Normal file
View File

@ -0,0 +1,83 @@
[package]
name = "objdiff-gui"
version = "1.0.0"
edition = "2021"
rust-version = "1.70"
authors = ["Luke Street <luke@street.dev>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/encounter/objdiff"
readme = "../README.md"
description = """
A local diffing tool for decompilation projects.
"""
publish = false
build = "build.rs"
[features]
default = ["wgpu", "wsl"]
wgpu = ["eframe/wgpu"]
wsl = []
[dependencies]
anyhow = "1.0.79"
bytes = "1.5.0"
cfg-if = "1.0.0"
const_format = "0.2.32"
cwdemangle = "0.1.6"
dirs = "5.0.1"
eframe = { version = "0.26.2", features = ["persistence"] }
egui = "0.26.2"
egui_extras = "0.26.2"
filetime = "0.2.23"
float-ord = "0.3.2"
font-kit = "0.12.0"
globset = { version = "0.4.14", features = ["serde1"] }
log = "0.4.20"
notify = "6.1.1"
objdiff-core = { path = "../objdiff-core", features = ["all"] }
png = "0.17.11"
pollster = "0.3.0"
rfd = { version = "0.14.0" } #, default-features = false, features = ['xdg-portal']
ron = "0.8.1"
semver = "1.0.21"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0.111"
serde_yaml = "0.9.30"
shell-escape = "0.1.5"
tempfile = "3.9.0"
thiserror = "1.0.56"
time = { version = "0.3.31", features = ["formatting", "local-offset"] }
toml = "0.8.8"
# For Linux static binaries, use rustls
[target.'cfg(target_os = "linux")'.dependencies]
reqwest = { version = "0.11.23", default-features = false, features = ["blocking", "json", "multipart", "rustls"] }
self_update = { version = "0.39.0", default-features = false, features = ["rustls"] }
# For all other platforms, use native TLS
[target.'cfg(not(target_os = "linux"))'.dependencies]
reqwest = { version = "0.11.23", default-features = false, features = ["blocking", "json", "multipart", "default-tls"] }
self_update = "0.39.0"
[target.'cfg(windows)'.dependencies]
path-slash = "0.2.1"
winapi = "0.3.9"
[target.'cfg(windows)'.build-dependencies]
winres = "0.1.12"
[target.'cfg(unix)'.dependencies]
exec = "0.3.1"
# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tracing-subscriber = "0.3"
# web:
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.7"
tracing-wasm = "0.2"
[build-dependencies]
anyhow = "1.0.79"
vergen = { version = "8.3.1", features = ["build", "cargo", "git", "gitcl"] }

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -12,12 +12,12 @@ use std::{
use filetime::FileTime;
use globset::{Glob, GlobSet};
use notify::{RecursiveMode, Watcher};
use objdiff_core::diff::DiffAlg;
use time::UtcOffset;
use crate::{
app_config::{deserialize_config, AppConfigVersion},
config::{build_globset, load_project_config, ProjectObject, ProjectObjectNode, ScratchConfig},
diff::DiffAlg,
jobs::{
objdiff::{start_build, ObjDiffConfig},
Job, JobQueue, JobResult, JobStatus,

View File

@ -4,7 +4,7 @@ use std::{
path::{Component, Path, PathBuf},
};
use anyhow::{bail, Result};
use anyhow::{ensure, Result};
use filetime::FileTime;
use globset::{Glob, GlobSet, GlobSetBuilder};
@ -163,9 +163,10 @@ pub fn load_project_config(config: &mut AppConfig) -> Result<()> {
let version_str = env!("CARGO_PKG_VERSION");
let version = semver::Version::parse(version_str).unwrap();
let version_req = semver::VersionReq::parse(&format!(">={min_version}"))?;
if !version_req.matches(&version) {
bail!("Project requires objdiff version {} or higher", min_version);
}
ensure!(
version_req.matches(&version),
"Project requires objdiff version {min_version} or higher"
);
}
config.custom_make = project_config.custom_make;
config.target_obj_dir = project_config.target_dir.map(|p| project_dir.join(p));

View File

@ -6,13 +6,15 @@ use std::{
};
use anyhow::{anyhow, Context, Error, Result};
use objdiff_core::{
diff::{diff_objs, DiffAlg, DiffObjConfig},
obj::{elf, ObjInfo},
};
use time::OffsetDateTime;
use crate::{
app::{AppConfig, ObjectConfig},
diff::{diff_objs, DiffAlg, DiffObjConfig},
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState},
obj::{elf, ObjInfo},
};
pub struct BuildStatus {
@ -90,60 +92,61 @@ pub(crate) fn run_make(config: &BuildConfig, arg: &Path) -> BuildStatus {
..Default::default()
};
};
match (|| -> Result<BuildStatus> {
let make = config.custom_make.as_deref().unwrap_or("make");
#[cfg(not(windows))]
let mut command = {
let mut command = Command::new(make);
command.current_dir(cwd).arg(arg);
command
};
#[cfg(windows)]
let mut command = {
use std::os::windows::process::CommandExt;
use path_slash::PathExt;
let mut command = if config.selected_wsl_distro.is_some() {
Command::new("wsl")
} else {
Command::new(make)
};
if let Some(distro) = &config.selected_wsl_distro {
command
.arg("--cd")
.arg(cwd)
.arg("-d")
.arg(distro)
.arg("--")
.arg(make)
.arg(arg.to_slash_lossy().as_ref());
} else {
command.current_dir(cwd).arg(arg.to_slash_lossy().as_ref());
}
command.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW);
command
};
let mut cmdline =
shell_escape::escape(command.get_program().to_string_lossy()).into_owned();
for arg in command.get_args() {
cmdline.push(' ');
cmdline.push_str(shell_escape::escape(arg.to_string_lossy()).as_ref());
}
let output = command.output().context("Failed to execute build")?;
let stdout = from_utf8(&output.stdout).context("Failed to process stdout")?;
let stderr = from_utf8(&output.stderr).context("Failed to process stderr")?;
Ok(BuildStatus {
success: output.status.code().unwrap_or(-1) == 0,
cmdline,
stdout: stdout.to_string(),
stderr: stderr.to_string(),
})
})() {
match run_make_cmd(config, cwd, arg) {
Ok(status) => status,
Err(e) => BuildStatus { success: false, stderr: e.to_string(), ..Default::default() },
}
}
fn run_make_cmd(config: &BuildConfig, cwd: &Path, arg: &Path) -> Result<BuildStatus> {
let make = config.custom_make.as_deref().unwrap_or("make");
#[cfg(not(windows))]
let mut command = {
let mut command = Command::new(make);
command.current_dir(cwd).arg(arg);
command
};
#[cfg(windows)]
let mut command = {
use std::os::windows::process::CommandExt;
use path_slash::PathExt;
let mut command = if config.selected_wsl_distro.is_some() {
Command::new("wsl")
} else {
Command::new(make)
};
if let Some(distro) = &config.selected_wsl_distro {
command
.arg("--cd")
.arg(cwd)
.arg("-d")
.arg(distro)
.arg("--")
.arg(make)
.arg(arg.to_slash_lossy().as_ref());
} else {
command.current_dir(cwd).arg(arg.to_slash_lossy().as_ref());
}
command.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW);
command
};
let mut cmdline = shell_escape::escape(command.get_program().to_string_lossy()).into_owned();
for arg in command.get_args() {
cmdline.push(' ');
cmdline.push_str(shell_escape::escape(arg.to_string_lossy()).as_ref());
}
let output = command.output().context("Failed to execute build")?;
let stdout = from_utf8(&output.stdout).context("Failed to process stdout")?;
let stderr = from_utf8(&output.stderr).context("Failed to process stderr")?;
Ok(BuildStatus {
success: output.status.code().unwrap_or(-1) == 0,
cmdline,
stdout: stdout.to_string(),
stderr: stderr.to_string(),
})
}
fn run_build(
context: &JobContext,
cancel: Receiver<()>,

View File

@ -1,13 +1,21 @@
#![warn(clippy::all, rust_2018_idioms)]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
mod app;
mod app_config;
mod config;
mod fonts;
mod jobs;
mod update;
mod views;
use std::{
path::PathBuf,
rc::Rc,