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

1233
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,96 +1,11 @@
[package] [workspace]
name = "objdiff" members = [
version = "1.0.0" "objdiff-core",
edition = "2021" "objdiff-gui",
rust-version = "1.70" ]
authors = ["Luke Street <luke@street.dev>"] resolver = "2"
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"
[profile.release-lto] [profile.release-lto]
inherits = "release" inherits = "release"
lto = "thin" lto = "thin"
strip = "debuginfo" 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}, editops::{editops_find, LevEditType},
DiffAlg, DiffObjConfig, ProcessCodeResult, DiffAlg, DiffObjConfig, ProcessCodeResult,
}, },
obj,
obj::{ obj::{
mips, ppc, ObjArchitecture, ObjInfo, ObjInsArg, ObjInsArgDiff, ObjInsBranchFrom, ObjArchitecture, ObjInfo, ObjInsArg, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo,
ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind, ObjReloc, ObjSymbol, ObjSymbolFlags, ObjInsDiff, ObjInsDiffKind, ObjReloc, ObjSymbol, ObjSymbolFlags,
}, },
}; };
@ -27,9 +28,13 @@ pub fn no_diff_code(
) -> Result<()> { ) -> Result<()> {
let code = let code =
&data[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize]; &data[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
let out = match arch { let out: ProcessCodeResult = match arch {
ObjArchitecture::PowerPc => ppc::process_code(code, symbol.address, relocs, line_info)?, #[cfg(feature = "ppc")]
ObjArchitecture::Mips => mips::process_code( ObjArchitecture::PowerPc => {
obj::ppc::process_code(code, symbol.address, relocs, line_info)?
}
#[cfg(feature = "mips")]
ObjArchitecture::Mips => obj::mips::process_code(
code, code,
symbol.address, symbol.address,
symbol.address + symbol.size, symbol.address + symbol.size,
@ -65,19 +70,26 @@ pub fn diff_code(
let right_code = &right_data[right_symbol.section_address as usize let right_code = &right_data[right_symbol.section_address as usize
..(right_symbol.section_address + right_symbol.size) as usize]; ..(right_symbol.section_address + right_symbol.size) as usize];
let (left_out, right_out) = match arch { let (left_out, right_out) = match arch {
#[cfg(feature = "ppc")]
ObjArchitecture::PowerPc => ( ObjArchitecture::PowerPc => (
ppc::process_code(left_code, left_symbol.address, left_relocs, left_line_info)?, obj::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(
right_code,
right_symbol.address,
right_relocs,
right_line_info,
)?,
), ),
#[cfg(feature = "mips")]
ObjArchitecture::Mips => ( ObjArchitecture::Mips => (
mips::process_code( obj::mips::process_code(
left_code, left_code,
left_symbol.address, left_symbol.address,
left_symbol.address + left_symbol.size, left_symbol.address + left_symbol.size,
left_relocs, left_relocs,
left_line_info, left_line_info,
)?, )?,
mips::process_code( obj::mips::process_code(
right_code, right_code,
right_symbol.address, right_symbol.address,
left_symbol.address + left_symbol.size, left_symbol.address + left_symbol.size,
@ -235,12 +247,8 @@ fn diff_instructions_lev(
cur_right = right_iter.next(); cur_right = right_iter.next();
} }
if let (Some(left), Some(right)) = (cur_left, cur_right) { if let (Some(left), Some(right)) = (cur_left, cur_right) {
if (left.address - left_symbol.address as u32) != left_addr { debug_assert_eq!(left.address - left_symbol.address as u32, left_addr);
return Err(anyhow::Error::msg("Instruction address mismatch (left)")); debug_assert_eq!(right.address - right_symbol.address as u32, right_addr);
}
if (right.address - right_symbol.address as u32) != right_addr {
return Err(anyhow::Error::msg("Instruction address mismatch (right)"));
}
match op.op_type { match op.op_type {
LevEditType::Replace => { LevEditType::Replace => {
left_diff.push(ObjInsDiff { ins: Some(left.clone()), ..ObjInsDiff::default() }); left_diff.push(ObjInsDiff { ins: Some(left.clone()), ..ObjInsDiff::default() });
@ -360,8 +368,8 @@ fn arg_eq(
right_diff: &ObjInsDiff, right_diff: &ObjInsDiff,
) -> bool { ) -> bool {
return match left { return match left {
ObjInsArg::PpcArg(l) => match right { ObjInsArg::Arg(l) | ObjInsArg::ArgWithBase(l) => match right {
ObjInsArg::PpcArg(r) => format!("{l}") == format!("{r}"), ObjInsArg::Arg(r) | ObjInsArg::ArgWithBase(r) => l == r,
_ => false, _ => false,
}, },
ObjInsArg::Reloc => { ObjInsArg::Reloc => {
@ -380,9 +388,6 @@ fn arg_eq(
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()), 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(_) => { ObjInsArg::BranchOffset(_) => {
// Compare dest instruction idx after diffing // Compare dest instruction idx after diffing
left_diff.branch_to.as_ref().map(|b| b.ins_idx) left_diff.branch_to.as_ref().map(|b| b.ins_idx)
@ -436,9 +441,8 @@ fn compare_ins(
state.diff_count += 1; state.diff_count += 1;
} }
let a_str = match a { 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::Reloc | ObjInsArg::RelocWithBase => String::new(),
ObjInsArg::MipsArg(str) | ObjInsArg::MipsArgWithBase(str) => str.clone(),
ObjInsArg::BranchOffset(arg) => format!("{arg}"), ObjInsArg::BranchOffset(arg) => format!("{arg}"),
}; };
let a_diff = if let Some(idx) = state.left_args_idx.get(&a_str) { let a_diff = if let Some(idx) = state.left_args_idx.get(&a_str) {
@ -450,9 +454,8 @@ fn compare_ins(
ObjInsArgDiff { idx } ObjInsArgDiff { idx }
}; };
let b_str = match b { 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::Reloc | ObjInsArg::RelocWithBase => String::new(),
ObjInsArg::MipsArg(str) | ObjInsArg::MipsArgWithBase(str) => str.clone(),
ObjInsArg::BranchOffset(arg) => format!("{arg}"), ObjInsArg::BranchOffset(arg) => format!("{arg}"),
}; };
let b_diff = if let Some(idx) = state.right_args_idx.get(&b_str) { 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}, time::{Duration, Instant},
}; };
use anyhow::{bail, Result}; use anyhow::{ensure, Result};
use similar::{capture_diff_slices_deadline, Algorithm}; use similar::{capture_diff_slices_deadline, Algorithm};
use crate::{ use crate::{
@ -177,15 +177,14 @@ pub fn diff_data_similar(
pub fn diff_data_lev(left: &mut ObjSection, right: &mut ObjSection) -> Result<()> { 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); let matrix_size = (left.data.len() as u64).saturating_mul(right.data.len() as u64);
if matrix_size > 1_000_000_000 { ensure!(
bail!( matrix_size < 1_000_000_000,
"Data section {} too large for Levenshtein diff ({} * {} = {})", "Data section {} too large for Levenshtein diff ({} * {} = {})",
left.name, left.name,
left.data.len(), left.data.len(),
right.data.len(), right.data.len(),
matrix_size matrix_size
); );
}
let edit_ops = editops_find(&left.data, &right.data); let edit_ops = editops_find(&left.data, &right.data);
if edit_ops.is_empty() && !left.data.is_empty() { 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 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 byteorder::{BigEndian, ReadBytesExt};
use cwdemangle::demangle;
use filetime::FileTime; use filetime::FileTime;
use flagset::Flags; use flagset::Flags;
use object::{ use object::{
@ -53,9 +52,14 @@ fn to_obj_symbol(obj_file: &File<'_>, symbol: &Symbol<'_, '_>, addend: i64) -> R
} else { } else {
symbol.address() 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 { Ok(ObjSymbol {
name: name.to_string(), name: name.to_string(),
demangled_name: demangle(name, &Default::default()), demangled_name,
address: symbol.address(), address: symbol.address(),
section_address, section_address,
size: symbol.size(), size: symbol.size(),
@ -194,16 +198,12 @@ fn relocations_by_section(
RelocationTarget::Symbol(idx) => obj_file RelocationTarget::Symbol(idx) => obj_file
.symbol_by_index(idx) .symbol_by_index(idx)
.context("Failed to locate relocation target symbol")?, .context("Failed to locate relocation target symbol")?,
_ => { _ => bail!("Unhandled relocation target: {:?}", reloc.target()),
return Err(anyhow::Error::msg(format!(
"Unhandled relocation target: {:?}",
reloc.target()
)));
}
}; };
let kind = match reloc.kind() { let kind = match reloc.kind() {
RelocationKind::Absolute => ObjRelocKind::Absolute, RelocationKind::Absolute => ObjRelocKind::Absolute,
RelocationKind::Elf(kind) => match arch { RelocationKind::Elf(kind) => match arch {
#[cfg(feature = "ppc")]
ObjArchitecture::PowerPc => match kind { ObjArchitecture::PowerPc => match kind {
elf::R_PPC_ADDR16_LO => ObjRelocKind::PpcAddr16Lo, elf::R_PPC_ADDR16_LO => ObjRelocKind::PpcAddr16Lo,
elf::R_PPC_ADDR16_HI => ObjRelocKind::PpcAddr16Hi, elf::R_PPC_ADDR16_HI => ObjRelocKind::PpcAddr16Hi,
@ -211,12 +211,9 @@ fn relocations_by_section(
elf::R_PPC_REL24 => ObjRelocKind::PpcRel24, elf::R_PPC_REL24 => ObjRelocKind::PpcRel24,
elf::R_PPC_REL14 => ObjRelocKind::PpcRel14, elf::R_PPC_REL14 => ObjRelocKind::PpcRel14,
elf::R_PPC_EMB_SDA21 => ObjRelocKind::PpcEmbSda21, elf::R_PPC_EMB_SDA21 => ObjRelocKind::PpcEmbSda21,
_ => { _ => bail!("Unhandled PPC relocation type: {kind}"),
return Err(anyhow::Error::msg(format!(
"Unhandled PPC relocation type: {kind}"
)))
}
}, },
#[cfg(feature = "mips")]
ObjArchitecture::Mips => match kind { ObjArchitecture::Mips => match kind {
elf::R_MIPS_26 => ObjRelocKind::Mips26, elf::R_MIPS_26 => ObjRelocKind::Mips26,
elf::R_MIPS_HI16 => ObjRelocKind::MipsHi16, elf::R_MIPS_HI16 => ObjRelocKind::MipsHi16,
@ -228,12 +225,7 @@ fn relocations_by_section(
_ => bail!("Unhandled MIPS relocation type: {kind}"), _ => bail!("Unhandled MIPS relocation type: {kind}"),
}, },
}, },
_ => { _ => bail!("Unhandled relocation type: {:?}", reloc.kind()),
return Err(anyhow::Error::msg(format!(
"Unhandled relocation type: {:?}",
reloc.kind()
)))
}
}; };
let target_section = match symbol.section() { let target_section = match symbol.section() {
SymbolSection::Common => Some(".comm".to_string()), SymbolSection::Common => Some(".comm".to_string()),
@ -248,12 +240,16 @@ fn relocations_by_section(
); );
match kind { match kind {
ObjRelocKind::Absolute => addend as i64, ObjRelocKind::Absolute => addend as i64,
#[cfg(feature = "mips")]
ObjRelocKind::MipsHi16 => ((addend & 0x0000FFFF) << 16) as i32 as i64, ObjRelocKind::MipsHi16 => ((addend & 0x0000FFFF) << 16) as i32 as i64,
#[cfg(feature = "mips")]
ObjRelocKind::MipsLo16 ObjRelocKind::MipsLo16
| ObjRelocKind::MipsGot16 | ObjRelocKind::MipsGot16
| ObjRelocKind::MipsCall16 | ObjRelocKind::MipsCall16
| ObjRelocKind::MipsGpRel16 => (addend & 0x0000FFFF) as i16 as i64, | ObjRelocKind::MipsGpRel16 => (addend & 0x0000FFFF) as i16 as i64,
#[cfg(feature = "mips")]
ObjRelocKind::MipsGpRel32 => addend as i32 as i64, ObjRelocKind::MipsGpRel32 => addend as i32 as i64,
#[cfg(feature = "mips")]
ObjRelocKind::Mips26 => ((addend & 0x03FFFFFF) << 2) as i64, ObjRelocKind::Mips26 => ((addend & 0x03FFFFFF) << 2) as i64,
_ => bail!("Unsupported implicit relocation {kind:?}"), _ => bail!("Unsupported implicit relocation {kind:?}"),
} }
@ -266,9 +262,7 @@ fn relocations_by_section(
to_obj_symbol(obj_file, &symbol, addend) to_obj_symbol(obj_file, &symbol, addend)
} }
SymbolKind::Section => { SymbolKind::Section => {
if addend < 0 { ensure!(addend >= 0, "Negative addend in reloc: {addend}");
return Err(anyhow::Error::msg(format!("Negative addend in reloc: {addend}")));
}
find_section_symbol(obj_file, &symbol, addend as u64) find_section_symbol(obj_file, &symbol, addend as u64)
} }
kind => Err(anyhow!("Unhandled relocation symbol type {kind:?}")), 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+ // DWARF 2+
let dwarf_cow = gimli::Dwarf::load(|id| { #[cfg(feature = "dwarf")]
Ok::<_, gimli::Error>( {
obj_file let dwarf_cow = gimli::Dwarf::load(|id| {
.section_by_name(id.name()) Ok::<_, gimli::Error>(
.and_then(|section| section.uncompressed_data().ok()) obj_file
.unwrap_or(Cow::Borrowed(&[][..])), .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 endian = match obj_file.endianness() {
}; Endianness::Little => gimli::RunTimeEndian::Little,
let dwarf = dwarf_cow.borrow(|section| gimli::EndianSlice::new(section, endian)); Endianness::Big => gimli::RunTimeEndian::Big,
let mut iter = dwarf.units(); };
while let Some(header) = iter.next()? { let dwarf = dwarf_cow.borrow(|section| gimli::EndianSlice::new(section, endian));
let unit = dwarf.unit(header)?; let mut iter = dwarf.units();
if let Some(program) = unit.line_program.clone() { while let Some(header) = iter.next()? {
let mut rows = program.rows(); let unit = dwarf.unit(header)?;
while let Some((_header, row)) = rows.next_row()? { if let Some(program) = unit.line_program.clone() {
if let Some(line) = row.line() { let mut rows = program.rows();
map.insert(row.address(), line.get()); 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 obj_file = File::parse(&*data)?;
let architecture = match obj_file.architecture() { let architecture = match obj_file.architecture() {
#[cfg(feature = "ppc")]
Architecture::PowerPc => ObjArchitecture::PowerPc, Architecture::PowerPc => ObjArchitecture::PowerPc,
#[cfg(feature = "mips")]
Architecture::Mips => ObjArchitecture::Mips, Architecture::Mips => ObjArchitecture::Mips,
_ => { _ => bail!("Unsupported architecture: {:?}", obj_file.architecture()),
return Err(anyhow::Error::msg(format!(
"Unsupported architecture: {:?}",
obj_file.architecture()
)))
}
}; };
let mut result = ObjInfo { let mut result = ObjInfo {
architecture, architecture,

View File

@ -5,7 +5,7 @@ use rabbitizer::{config, Abi, InstrCategory, Instruction, OperandType};
use crate::{ use crate::{
diff::ProcessCodeResult, diff::ProcessCodeResult,
obj::{ObjIns, ObjInsArg, ObjReloc}, obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc},
}; };
fn configure_rabbitizer() { fn configure_rabbitizer() {
@ -63,23 +63,27 @@ pub fn process_code(
args.push(ObjInsArg::Reloc); args.push(ObjInsArg::Reloc);
} }
} else { } else {
args.push(ObjInsArg::MipsArg(op.disassemble(&instruction, None))); args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
op.disassemble(&instruction, None),
)));
} }
} }
OperandType::cpu_immediate_base => { OperandType::cpu_immediate_base => {
if reloc.is_some() { if reloc.is_some() {
args.push(ObjInsArg::RelocWithBase); args.push(ObjInsArg::RelocWithBase);
} else { } else {
args.push(ObjInsArg::MipsArgWithBase( args.push(ObjInsArg::ArgWithBase(ObjInsArgValue::Opaque(
OperandType::cpu_immediate.disassemble(&instruction, None), OperandType::cpu_immediate.disassemble(&instruction, None),
)); )));
} }
args.push(ObjInsArg::MipsArg( args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
OperandType::cpu_rs.disassemble(&instruction, None), 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; pub mod elf;
#[cfg(feature = "mips")]
pub mod mips; pub mod mips;
#[cfg(feature = "ppc")]
pub mod ppc; pub mod ppc;
use std::{collections::BTreeMap, path::PathBuf}; use std::{collections::BTreeMap, fmt, path::PathBuf};
use filetime::FileTime; use filetime::FileTime;
use flagset::{flags, FlagSet}; use flagset::{flags, FlagSet};
use crate::util::ReallySigned;
#[derive(Debug, Eq, PartialEq, Copy, Clone)] #[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum ObjSectionKind { pub enum ObjSectionKind {
Code, Code,
@ -23,7 +27,8 @@ flags! {
} }
} }
#[derive(Debug, Copy, Clone, Default)] #[derive(Debug, Copy, Clone, Default)]
pub struct ObjSymbolFlagSet(pub(crate) FlagSet<ObjSymbolFlags>); pub struct ObjSymbolFlagSet(pub FlagSet<ObjSymbolFlags>);
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjSection { pub struct ObjSection {
pub name: String, pub name: String,
@ -40,11 +45,40 @@ pub struct ObjSection {
pub match_percent: f32, 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)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum ObjInsArg { pub enum ObjInsArg {
PpcArg(ppc750cl::Argument), Arg(ObjInsArgValue),
MipsArg(String), ArgWithBase(ObjInsArgValue),
MipsArgWithBase(String),
Reloc, Reloc,
RelocWithBase, RelocWithBase,
BranchOffset(i32), BranchOffset(i32),
@ -53,29 +87,8 @@ pub enum ObjInsArg {
impl ObjInsArg { impl ObjInsArg {
pub fn loose_eq(&self, other: &ObjInsArg) -> bool { pub fn loose_eq(&self, other: &ObjInsArg) -> bool {
match (self, other) { match (self, other) {
(ObjInsArg::PpcArg(a), ObjInsArg::PpcArg(b)) => { (ObjInsArg::Arg(a), ObjInsArg::Arg(b)) => a.loose_eq(b),
a == b (ObjInsArg::ArgWithBase(a), ObjInsArg::ArgWithBase(b)) => a.loose_eq(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::Reloc, ObjInsArg::Reloc) => true, (ObjInsArg::Reloc, ObjInsArg::Reloc) => true,
(ObjInsArg::RelocWithBase, ObjInsArg::RelocWithBase) => true, (ObjInsArg::RelocWithBase, ObjInsArg::RelocWithBase) => true,
(ObjInsArg::BranchOffset(a), ObjInsArg::BranchOffset(b)) => a == b, (ObjInsArg::BranchOffset(a), ObjInsArg::BranchOffset(b)) => a == b,
@ -89,6 +102,7 @@ pub struct ObjInsArgDiff {
/// Incrementing index for coloring /// Incrementing index for coloring
pub idx: usize, pub idx: usize,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjInsBranchFrom { pub struct ObjInsBranchFrom {
/// Source instruction indices /// Source instruction indices
@ -96,6 +110,7 @@ pub struct ObjInsBranchFrom {
/// Incrementing index for coloring /// Incrementing index for coloring
pub branch_idx: usize, pub branch_idx: usize,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjInsBranchTo { pub struct ObjInsBranchTo {
/// Target instruction index /// Target instruction index
@ -103,6 +118,7 @@ pub struct ObjInsBranchTo {
/// Incrementing index for coloring /// Incrementing index for coloring
pub branch_idx: usize, pub branch_idx: usize,
} }
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub enum ObjInsDiffKind { pub enum ObjInsDiffKind {
#[default] #[default]
@ -113,6 +129,7 @@ pub enum ObjInsDiffKind {
Delete, Delete,
Insert, Insert,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjIns { pub struct ObjIns {
pub address: u32, pub address: u32,
@ -127,6 +144,7 @@ pub struct ObjIns {
/// Original (unsimplified) instruction /// Original (unsimplified) instruction
pub orig: Option<String>, pub orig: Option<String>,
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct ObjInsDiff { pub struct ObjInsDiff {
pub ins: Option<ObjIns>, pub ins: Option<ObjIns>,
@ -139,6 +157,7 @@ pub struct ObjInsDiff {
/// Arg diffs /// Arg diffs
pub arg_diff: Vec<Option<ObjInsArgDiff>>, pub arg_diff: Vec<Option<ObjInsArgDiff>>,
} }
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub enum ObjDataDiffKind { pub enum ObjDataDiffKind {
#[default] #[default]
@ -147,6 +166,7 @@ pub enum ObjDataDiffKind {
Delete, Delete,
Insert, Insert,
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct ObjDataDiff { pub struct ObjDataDiff {
pub data: Vec<u8>, pub data: Vec<u8>,
@ -154,6 +174,7 @@ pub struct ObjDataDiff {
pub len: usize, pub len: usize,
pub symbol: String, pub symbol: String,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjSymbol { pub struct ObjSymbol {
pub name: String, pub name: String,
@ -170,11 +191,15 @@ pub struct ObjSymbol {
pub instructions: Vec<ObjInsDiff>, pub instructions: Vec<ObjInsDiff>,
pub match_percent: Option<f32>, pub match_percent: Option<f32>,
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum ObjArchitecture { pub enum ObjArchitecture {
#[cfg(feature = "ppc")]
PowerPc, PowerPc,
#[cfg(feature = "mips")]
Mips, Mips,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjInfo { pub struct ObjInfo {
pub architecture: ObjArchitecture, pub architecture: ObjArchitecture,
@ -184,27 +209,46 @@ pub struct ObjInfo {
pub common: Vec<ObjSymbol>, pub common: Vec<ObjSymbol>,
pub line_info: Option<BTreeMap<u64, u64>>, pub line_info: Option<BTreeMap<u64, u64>>,
} }
#[derive(Debug, Eq, PartialEq, Copy, Clone)] #[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum ObjRelocKind { pub enum ObjRelocKind {
Absolute, Absolute,
#[cfg(feature = "ppc")]
PpcAddr16Hi, PpcAddr16Hi,
#[cfg(feature = "ppc")]
PpcAddr16Ha, PpcAddr16Ha,
#[cfg(feature = "ppc")]
PpcAddr16Lo, PpcAddr16Lo,
// #[cfg(feature = "ppc")]
// PpcAddr32, // PpcAddr32,
// #[cfg(feature = "ppc")]
// PpcRel32, // PpcRel32,
// #[cfg(feature = "ppc")]
// PpcAddr24, // PpcAddr24,
#[cfg(feature = "ppc")]
PpcRel24, PpcRel24,
// #[cfg(feature = "ppc")]
// PpcAddr14, // PpcAddr14,
#[cfg(feature = "ppc")]
PpcRel14, PpcRel14,
#[cfg(feature = "ppc")]
PpcEmbSda21, PpcEmbSda21,
#[cfg(feature = "mips")]
Mips26, Mips26,
#[cfg(feature = "mips")]
MipsHi16, MipsHi16,
#[cfg(feature = "mips")]
MipsLo16, MipsLo16,
#[cfg(feature = "mips")]
MipsGot16, MipsGot16,
#[cfg(feature = "mips")]
MipsCall16, MipsCall16,
#[cfg(feature = "mips")]
MipsGpRel16, MipsGpRel16,
#[cfg(feature = "mips")]
MipsGpRel32, MipsGpRel32,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ObjReloc { pub struct ObjReloc {
pub kind: ObjRelocKind, pub kind: ObjRelocKind,

View File

@ -5,20 +5,26 @@ use ppc750cl::{disasm_iter, Argument, SimplifiedIns};
use crate::{ use crate::{
diff::ProcessCodeResult, diff::ProcessCodeResult,
obj::{ObjIns, ObjInsArg, ObjReloc, ObjRelocKind}, obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjRelocKind},
}; };
// Relative relocation, can be Simm or BranchOffset // Relative relocation, can be Simm or BranchOffset
fn is_relative_arg(arg: &ObjInsArg) -> bool { 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 // Relative or absolute relocation, can be Uimm, Simm or Offset
fn is_rel_abs_arg(arg: &ObjInsArg) -> bool { 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( pub fn process_code(
data: &[u8], data: &[u8],
@ -48,8 +54,13 @@ pub fn process_code(
.args .args
.iter() .iter()
.map(|a| match a { .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), Argument::BranchDest(dest) => ObjInsArg::BranchOffset(dest.0),
_ => ObjInsArg::PpcArg(a.clone()), _ => ObjInsArg::Arg(ObjInsArgValue::Opaque(a.to_string())),
}) })
.collect(); .collect();
if let Some(reloc) = reloc { 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 filetime::FileTime;
use globset::{Glob, GlobSet}; use globset::{Glob, GlobSet};
use notify::{RecursiveMode, Watcher}; use notify::{RecursiveMode, Watcher};
use objdiff_core::diff::DiffAlg;
use time::UtcOffset; use time::UtcOffset;
use crate::{ use crate::{
app_config::{deserialize_config, AppConfigVersion}, app_config::{deserialize_config, AppConfigVersion},
config::{build_globset, load_project_config, ProjectObject, ProjectObjectNode, ScratchConfig}, config::{build_globset, load_project_config, ProjectObject, ProjectObjectNode, ScratchConfig},
diff::DiffAlg,
jobs::{ jobs::{
objdiff::{start_build, ObjDiffConfig}, objdiff::{start_build, ObjDiffConfig},
Job, JobQueue, JobResult, JobStatus, Job, JobQueue, JobResult, JobStatus,

View File

@ -4,7 +4,7 @@ use std::{
path::{Component, Path, PathBuf}, path::{Component, Path, PathBuf},
}; };
use anyhow::{bail, Result}; use anyhow::{ensure, Result};
use filetime::FileTime; use filetime::FileTime;
use globset::{Glob, GlobSet, GlobSetBuilder}; 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_str = env!("CARGO_PKG_VERSION");
let version = semver::Version::parse(version_str).unwrap(); let version = semver::Version::parse(version_str).unwrap();
let version_req = semver::VersionReq::parse(&format!(">={min_version}"))?; let version_req = semver::VersionReq::parse(&format!(">={min_version}"))?;
if !version_req.matches(&version) { ensure!(
bail!("Project requires objdiff version {} or higher", min_version); version_req.matches(&version),
} "Project requires objdiff version {min_version} or higher"
);
} }
config.custom_make = project_config.custom_make; config.custom_make = project_config.custom_make;
config.target_obj_dir = project_config.target_dir.map(|p| project_dir.join(p)); 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 anyhow::{anyhow, Context, Error, Result};
use objdiff_core::{
diff::{diff_objs, DiffAlg, DiffObjConfig},
obj::{elf, ObjInfo},
};
use time::OffsetDateTime; use time::OffsetDateTime;
use crate::{ use crate::{
app::{AppConfig, ObjectConfig}, app::{AppConfig, ObjectConfig},
diff::{diff_objs, DiffAlg, DiffObjConfig},
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState}, jobs::{start_job, update_status, Job, JobContext, JobResult, JobState},
obj::{elf, ObjInfo},
}; };
pub struct BuildStatus { pub struct BuildStatus {
@ -90,60 +92,61 @@ pub(crate) fn run_make(config: &BuildConfig, arg: &Path) -> BuildStatus {
..Default::default() ..Default::default()
}; };
}; };
match (|| -> Result<BuildStatus> { match run_make_cmd(config, cwd, arg) {
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(),
})
})() {
Ok(status) => status, Ok(status) => status,
Err(e) => BuildStatus { success: false, stderr: e.to_string(), ..Default::default() }, 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( fn run_build(
context: &JobContext, context: &JobContext,
cancel: Receiver<()>, cancel: Receiver<()>,

View File

@ -1,13 +1,21 @@
#![warn(clippy::all, rust_2018_idioms)] #![warn(clippy::all, rust_2018_idioms)]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release #![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::{ use std::{
path::PathBuf, path::PathBuf,
rc::Rc, rc::Rc,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use anyhow::{Error, Result}; use anyhow::{ensure, Result};
use cfg_if::cfg_if; use cfg_if::cfg_if;
use time::UtcOffset; use time::UtcOffset;
@ -17,12 +25,8 @@ fn load_icon() -> Result<egui::IconData> {
let mut reader = decoder.read_info()?; let mut reader = decoder.read_info()?;
let mut buf = vec![0; reader.output_buffer_size()]; let mut buf = vec![0; reader.output_buffer_size()];
let info = reader.next_frame(&mut buf)?; let info = reader.next_frame(&mut buf)?;
if info.bit_depth != png::BitDepth::Eight { ensure!(info.bit_depth == png::BitDepth::Eight);
return Err(Error::msg("Invalid bit depth")); ensure!(info.color_type == png::ColorType::Rgba);
}
if info.color_type != png::ColorType::Rgba {
return Err(Error::msg("Invalid color type"));
}
buf.truncate(info.buffer_size()); buf.truncate(info.buffer_size());
Ok(egui::IconData { rgba: buf, width: info.width, height: info.height }) Ok(egui::IconData { rgba: buf, width: info.width, height: info.height })
} }
@ -57,7 +61,7 @@ fn main() {
eframe::run_native( eframe::run_native(
"objdiff", "objdiff",
native_options, native_options,
Box::new(move |cc| Box::new(objdiff::App::new(cc, utc_offset, exec_path_clone))), Box::new(move |cc| Box::new(app::App::new(cc, utc_offset, exec_path_clone))),
) )
.expect("Failed to run eframe application"); .expect("Failed to run eframe application");

View File

@ -14,12 +14,12 @@ use egui::{
SelectableLabel, TextFormat, Widget, WidgetText, SelectableLabel, TextFormat, Widget, WidgetText,
}; };
use globset::Glob; use globset::Glob;
use objdiff_core::diff::DiffAlg;
use self_update::cargo_crate_version; use self_update::cargo_crate_version;
use crate::{ use crate::{
app::{AppConfig, AppConfigRef, ObjectConfig}, app::{AppConfig, AppConfigRef, ObjectConfig},
config::{ProjectObject, ProjectObjectNode}, config::{ProjectObject, ProjectObjectNode},
diff::DiffAlg,
jobs::{ jobs::{
check_update::{start_check_update, CheckUpdateResult}, check_update::{start_check_update, CheckUpdateResult},
update::start_update, update::start_update,
@ -605,7 +605,7 @@ fn split_obj_config_ui(
ui.separator(); ui.separator();
ui.horizontal(|ui| { ui.horizontal(|ui| {
subheading(ui, "Custom make program", appearance); subheading(ui, "Build program", appearance);
ui.link(HELP_ICON).on_hover_ui(|ui| { ui.link(HELP_ICON).on_hover_ui(|ui| {
let mut job = LayoutJob::default(); let mut job = LayoutJob::default();
job.append("By default, objdiff will build with ", 0.0, text_format.clone()); job.append("By default, objdiff will build with ", 0.0, text_format.clone());
@ -630,7 +630,7 @@ fn split_obj_config_ui(
if ui if ui
.add_enabled( .add_enabled(
config.project_config_info.is_none(), config.project_config_info.is_none(),
egui::TextEdit::singleline(&mut custom_make_str), egui::TextEdit::singleline(&mut custom_make_str).hint_text("make"),
) )
.on_disabled_hover_text(CONFIG_DISABLED_TEXT) .on_disabled_hover_text(CONFIG_DISABLED_TEXT)
.changed() .changed()

View File

@ -1,16 +1,14 @@
use std::{cmp::min, default::Default, mem::take}; use std::{cmp::min, default::Default, mem::take};
use egui::{text::LayoutJob, Align, Label, Layout, Sense, Vec2}; use egui::{text::LayoutJob, Align, Label, Layout, Sense, Vec2, Widget};
use egui_extras::{Column, TableBuilder}; use egui_extras::{Column, TableBuilder};
use objdiff_core::obj::{ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjSection};
use time::format_description; use time::format_description;
use crate::{ use crate::views::{
obj::{ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjSection}, appearance::Appearance,
views::{ symbol_diff::{DiffViewState, SymbolReference, View},
appearance::Appearance, write_text,
symbol_diff::{DiffViewState, SymbolReference, View},
write_text,
},
}; };
const BYTES_PER_ROW: usize = 16; const BYTES_PER_ROW: usize = 16;
@ -90,7 +88,7 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], appeara
write_text(text.as_str(), base_color, &mut job, appearance.code_font.clone()); write_text(text.as_str(), base_color, &mut job, appearance.code_font.clone());
} }
} }
ui.add(Label::new(job).sense(Sense::click())); Label::new(job).sense(Sense::click()).ui(ui);
// .on_hover_ui_at_pointer(|ui| ins_hover_ui(ui, ins)) // .on_hover_ui_at_pointer(|ui| ins_hover_ui(ui, ins))
// .context_menu(|ui| ins_context_menu(ui, ins)); // .context_menu(|ui| ins_context_menu(ui, ins));
} }
@ -252,6 +250,7 @@ pub fn data_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance: &A
ui.separator(); ui.separator();
// Table // Table
ui.style_mut().interaction.selectable_labels = false;
let available_height = ui.available_height(); let available_height = ui.available_height();
let table = TableBuilder::new(ui) let table = TableBuilder::new(ui)
.striped(false) .striped(false)

View File

@ -3,22 +3,20 @@ use std::{
default::Default, default::Default,
}; };
use cwdemangle::demangle; use egui::{
use egui::{text::LayoutJob, Align, Color32, Label, Layout, RichText, Sense, TextFormat, Vec2}; text::LayoutJob, Align, Color32, Label, Layout, RichText, Sense, TextFormat, Vec2, Widget,
};
use egui_extras::{Column, TableBuilder, TableRow}; use egui_extras::{Column, TableBuilder, TableRow};
use ppc750cl::Argument; use objdiff_core::obj::{
ObjInfo, ObjIns, ObjInsArg, ObjInsArgDiff, ObjInsArgValue, ObjInsDiff, ObjInsDiffKind,
ObjReloc, ObjRelocKind, ObjSymbol,
};
use time::format_description; use time::format_description;
use crate::{ use crate::views::{
obj::{ appearance::Appearance,
ObjInfo, ObjIns, ObjInsArg, ObjInsArgDiff, ObjInsDiff, ObjInsDiffKind, ObjReloc, symbol_diff::{match_color_for_symbol, DiffViewState, SymbolReference, View},
ObjRelocKind, ObjSymbol, write_text,
},
views::{
appearance::Appearance,
symbol_diff::{match_color_for_symbol, DiffViewState, SymbolReference, View},
write_text,
},
}; };
#[derive(Default)] #[derive(Default)]
@ -162,11 +160,9 @@ fn write_ins(
} else { } else {
Color32::TRANSPARENT Color32::TRANSPARENT
}); });
if ui let response = Label::new(op_label).sense(Sense::click()).ui(ui);
.add(Label::new(op_label).sense(Sense::click())) response.context_menu(|ui| ins_context_menu(ui, ins));
.context_menu(|ui| ins_context_menu(ui, ins)) if response.clicked() {
.clicked()
{
if highlighted_op { if highlighted_op {
ins_view_state.highlight = HighlightKind::None; ins_view_state.highlight = HighlightKind::None;
} else { } else {
@ -215,19 +211,14 @@ fn write_ins(
}; };
let mut new_writing_offset = false; let mut new_writing_offset = false;
match arg { match arg {
ObjInsArg::PpcArg(arg) => match arg { ObjInsArg::Arg(arg) => {
Argument::Offset(val) => { job.append(&arg.to_string(), 0.0, text_format);
job.append(&format!("{val}"), 0.0, text_format); }
write_text("(", base_color, &mut job, appearance.code_font.clone()); ObjInsArg::ArgWithBase(arg) => {
new_writing_offset = true; job.append(&arg.to_string(), 0.0, text_format);
} write_text("(", base_color, &mut job, appearance.code_font.clone());
Argument::Uimm(_) | Argument::Simm(_) => { new_writing_offset = true;
job.append(&format!("{arg}"), 0.0, text_format); }
}
_ => {
job.append(&format!("{arg}"), 0.0, text_format);
}
},
ObjInsArg::Reloc => { ObjInsArg::Reloc => {
write_reloc( write_reloc(
ins.reloc.as_ref().unwrap(), ins.reloc.as_ref().unwrap(),
@ -248,14 +239,6 @@ fn write_ins(
write_text("(", base_color, &mut job, appearance.code_font.clone()); write_text("(", base_color, &mut job, appearance.code_font.clone());
new_writing_offset = true; new_writing_offset = true;
} }
ObjInsArg::MipsArg(str) => {
job.append(str.strip_prefix('$').unwrap_or(str), 0.0, text_format);
}
ObjInsArg::MipsArgWithBase(str) => {
job.append(str.strip_prefix('$').unwrap_or(str), 0.0, text_format);
write_text("(", base_color, &mut job, appearance.code_font.clone());
new_writing_offset = true;
}
ObjInsArg::BranchOffset(offset) => { ObjInsArg::BranchOffset(offset) => {
let addr = offset + ins.address as i32 - base_addr as i32; let addr = offset + ins.address as i32 - base_addr as i32;
job.append(&format!("{addr:x}"), 0.0, text_format); job.append(&format!("{addr:x}"), 0.0, text_format);
@ -264,12 +247,14 @@ fn write_ins(
if writing_offset { if writing_offset {
write_text(")", base_color, &mut job, appearance.code_font.clone()); write_text(")", base_color, &mut job, appearance.code_font.clone());
} }
// For text selection / copy
if i == ins.args.len() - 1 {
write_text("\n", base_color, &mut job, appearance.code_font.clone());
}
writing_offset = new_writing_offset; writing_offset = new_writing_offset;
if ui let response = Label::new(job).sense(Sense::click()).ui(ui);
.add(Label::new(job).sense(Sense::click())) response.context_menu(|ui| ins_context_menu(ui, ins));
.context_menu(|ui| ins_context_menu(ui, ins)) if response.clicked() {
.clicked()
{
if highlighted_arg { if highlighted_arg {
ins_view_state.highlight = HighlightKind::None; ins_view_state.highlight = HighlightKind::None;
} else if matches!(arg, ObjInsArg::Reloc | ObjInsArg::RelocWithBase) { } else if matches!(arg, ObjInsArg::Reloc | ObjInsArg::RelocWithBase) {
@ -297,16 +282,13 @@ fn ins_hover_ui(ui: &mut egui::Ui, ins: &ObjIns, appearance: &Appearance) {
} }
for arg in &ins.args { for arg in &ins.args {
if let ObjInsArg::PpcArg(arg) = arg { if let ObjInsArg::Arg(arg) | ObjInsArg::ArgWithBase(arg) = arg {
match arg { match arg {
Argument::Uimm(v) => { ObjInsArgValue::Signed(v) => {
ui.label(format!("{} == {}", v, v.0)); ui.label(format!("{arg} == {v}"));
} }
Argument::Simm(v) => { ObjInsArgValue::Unsigned(v) => {
ui.label(format!("{} == {}", v, v.0)); ui.label(format!("{arg} == {v}"));
}
Argument::Offset(v) => {
ui.label(format!("{} == {}", v, v.0));
} }
_ => {} _ => {}
} }
@ -341,35 +323,25 @@ fn ins_context_menu(ui: &mut egui::Ui, ins: &ObjIns) {
// if ui.button("Copy hex").clicked() {} // if ui.button("Copy hex").clicked() {}
for arg in &ins.args { for arg in &ins.args {
if let ObjInsArg::PpcArg(arg) = arg { if let ObjInsArg::Arg(arg) | ObjInsArg::ArgWithBase(arg) = arg {
match arg { match arg {
Argument::Uimm(v) => { ObjInsArgValue::Signed(v) => {
if ui.button(format!("Copy \"{v}\"")).clicked() { if ui.button(format!("Copy \"{arg}\"")).clicked() {
ui.output_mut(|output| output.copied_text = format!("{v}")); ui.output_mut(|output| output.copied_text = arg.to_string());
ui.close_menu(); ui.close_menu();
} }
if ui.button(format!("Copy \"{}\"", v.0)).clicked() { if ui.button(format!("Copy \"{v}\"")).clicked() {
ui.output_mut(|output| output.copied_text = format!("{}", v.0)); ui.output_mut(|output| output.copied_text = v.to_string());
ui.close_menu(); ui.close_menu();
} }
} }
Argument::Simm(v) => { ObjInsArgValue::Unsigned(v) => {
if ui.button(format!("Copy \"{arg}\"")).clicked() {
ui.output_mut(|output| output.copied_text = arg.to_string());
ui.close_menu();
}
if ui.button(format!("Copy \"{v}\"")).clicked() { if ui.button(format!("Copy \"{v}\"")).clicked() {
ui.output_mut(|output| output.copied_text = format!("{v}")); ui.output_mut(|output| output.copied_text = v.to_string());
ui.close_menu();
}
if ui.button(format!("Copy \"{}\"", v.0)).clicked() {
ui.output_mut(|output| output.copied_text = format!("{}", v.0));
ui.close_menu();
}
}
Argument::Offset(v) => {
if ui.button(format!("Copy \"{v}\"")).clicked() {
ui.output_mut(|output| output.copied_text = format!("{v}"));
ui.close_menu();
}
if ui.button(format!("Copy \"{}\"", v.0)).clicked() {
ui.output_mut(|output| output.copied_text = format!("{}", v.0));
ui.close_menu(); ui.close_menu();
} }
} }
@ -451,11 +423,9 @@ fn asm_row_ui(
}, },
..Default::default() ..Default::default()
}); });
if ui let response = Label::new(job).sense(Sense::click()).selectable(false).ui(ui);
.add(Label::new(job).sense(Sense::click())) response.context_menu(|ui| ins_context_menu(ui, ins));
.context_menu(|ui| ins_context_menu(ui, ins)) if response.clicked() {
.clicked()
{
if addr_highlight { if addr_highlight {
ins_view_state.highlight = HighlightKind::None; ins_view_state.highlight = HighlightKind::None;
} else { } else {
@ -484,7 +454,7 @@ fn asm_row_ui(
..Default::default() ..Default::default()
}); });
} }
ui.add(Label::new(job)); Label::new(job).selectable(false).ui(ui);
write_ins(ins, &ins_diff.kind, &ins_diff.arg_diff, base_addr, ui, appearance, ins_view_state); write_ins(ins, &ins_diff.kind, &ins_diff.arg_diff, base_addr, ui, appearance, ins_view_state);
if let Some(branch) = &ins_diff.branch_to { if let Some(branch) = &ins_diff.branch_to {
let mut job = LayoutJob::default(); let mut job = LayoutJob::default();
@ -494,7 +464,7 @@ fn asm_row_ui(
&mut job, &mut job,
appearance.code_font.clone(), appearance.code_font.clone(),
); );
ui.add(Label::new(job)); Label::new(job).selectable(false).ui(ui);
} }
} }
@ -509,13 +479,8 @@ fn asm_col_ui(
asm_row_ui(ui, ins_diff, symbol, appearance, ins_view_state); asm_row_ui(ui, ins_diff, symbol, appearance, ins_view_state);
}); });
if let Some(ins) = &ins_diff.ins { if let Some(ins) = &ins_diff.ins {
response response.on_hover_ui_at_pointer(|ui| ins_hover_ui(ui, ins, appearance));
.on_hover_ui_at_pointer(|ui| { // .context_menu(|ui| ins_context_menu(ui, ins));
ins_hover_ui(ui, ins, appearance);
})
.context_menu(|ui| {
ins_context_menu(ui, ins);
});
} }
} }
@ -604,8 +569,10 @@ pub fn function_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance
} }
}); });
let demangled = demangle(&selected_symbol.symbol_name, &Default::default()); let name = selected_symbol
let name = demangled.as_deref().unwrap_or(&selected_symbol.symbol_name); .demangled_symbol_name
.as_deref()
.unwrap_or(&selected_symbol.symbol_name);
let mut job = LayoutJob::simple( let mut job = LayoutJob::simple(
name.to_string(), name.to_string(),
appearance.code_font.clone(), appearance.code_font.clone(),
@ -681,6 +648,7 @@ pub fn function_diff_ui(ui: &mut egui::Ui, state: &mut DiffViewState, appearance
ui.separator(); ui.separator();
// Table // Table
ui.style_mut().interaction.selectable_labels = false;
let available_height = ui.available_height(); let available_height = ui.available_height();
let table = TableBuilder::new(ui) let table = TableBuilder::new(ui)
.striped(false) .striped(false)

View File

@ -5,6 +5,7 @@ use egui::{
SelectableLabel, TextEdit, Ui, Vec2, Widget, SelectableLabel, TextEdit, Ui, Vec2, Widget,
}; };
use egui_extras::{Size, StripBuilder}; use egui_extras::{Size, StripBuilder};
use objdiff_core::obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags};
use crate::{ use crate::{
app::AppConfigRef, app::AppConfigRef,
@ -13,12 +14,12 @@ use crate::{
objdiff::{BuildStatus, ObjDiffResult}, objdiff::{BuildStatus, ObjDiffResult},
Job, JobQueue, JobResult, Job, JobQueue, JobResult,
}, },
obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags},
views::{appearance::Appearance, function_diff::FunctionViewState, write_text}, views::{appearance::Appearance, function_diff::FunctionViewState, write_text},
}; };
pub struct SymbolReference { pub struct SymbolReference {
pub symbol_name: String, pub symbol_name: String,
pub demangled_symbol_name: Option<String>,
pub section_name: String, pub section_name: String,
} }
@ -210,19 +211,21 @@ fn symbol_ui(
write_text(name, appearance.highlight_color, &mut job, appearance.code_font.clone()); write_text(name, appearance.highlight_color, &mut job, appearance.code_font.clone());
let response = SelectableLabel::new(selected, job) let response = SelectableLabel::new(selected, job)
.ui(ui) .ui(ui)
.context_menu(|ui| symbol_context_menu_ui(ui, symbol))
.on_hover_ui_at_pointer(|ui| symbol_hover_ui(ui, symbol, appearance)); .on_hover_ui_at_pointer(|ui| symbol_hover_ui(ui, symbol, appearance));
response.context_menu(|ui| symbol_context_menu_ui(ui, symbol));
if response.clicked() { if response.clicked() {
if let Some(section) = section { if let Some(section) = section {
if section.kind == ObjSectionKind::Code { if section.kind == ObjSectionKind::Code {
state.selected_symbol = Some(SymbolReference { state.selected_symbol = Some(SymbolReference {
symbol_name: symbol.name.clone(), symbol_name: symbol.name.clone(),
demangled_symbol_name: symbol.demangled_name.clone(),
section_name: section.name.clone(), section_name: section.name.clone(),
}); });
ret = Some(View::FunctionDiff); ret = Some(View::FunctionDiff);
} else if section.kind == ObjSectionKind::Data { } else if section.kind == ObjSectionKind::Data {
state.selected_symbol = Some(SymbolReference { state.selected_symbol = Some(SymbolReference {
symbol_name: section.name.clone(), symbol_name: section.name.clone(),
demangled_symbol_name: None,
section_name: section.name.clone(), section_name: section.name.clone(),
}); });
ret = Some(View::DataDiff); ret = Some(View::DataDiff);

View File

@ -1,13 +0,0 @@
#![warn(clippy::all, rust_2018_idioms)]
pub use app::App;
mod app;
mod app_config;
mod config;
mod diff;
mod fonts;
mod jobs;
mod obj;
mod update;
mod views;