Compare commits

..

23 Commits

Author SHA1 Message Date
LagoLunatic
221c1a8cda Merge 0b7afa9f1e into 7aa878b48e 2024-12-01 22:33:58 -07:00
7aa878b48e Update all dependencies & clippy fixes 2024-12-01 22:22:35 -07:00
LagoLunatic
0b7afa9f1e Remove trailing newline when displaying decoded rlwinm info 2024-12-01 20:34:48 -05:00
LagoLunatic
5841d4db8a Display decoded rlwinm info to hover tooltip 2024-12-01 20:27:56 -05:00
LagoLunatic
7a360d4a99 Update .gitignore 2024-12-01 20:26:17 -05:00
LagoLunatic
40c97a1bc3 Fix missing dependency feature for objdiff-gui 2024-12-01 20:26:17 -05:00
a119d9a6dd Add scratch preset_id field for decomp.me
Resolves #133
2024-11-07 09:27:13 -07:00
robojumper
ebf653816a Combine nested otherwise empty directories in objects view (#137) 2024-11-07 08:21:39 -07:00
424434edd6 Experimental ARM64 support
Based on yaxpeax-arm, but with a heavy dose of
custom code to work around its limitations.

Please report any issues or unhandled relocations.
2024-10-31 17:39:12 -06:00
7f14b684bf Ignore PlainText segments when diffing 2024-10-31 17:27:27 -06:00
c5da7f7dd5 Show diff color when symbols differ 2024-10-31 17:26:59 -06:00
2fd655850a Ignore Absolute relocations and log warning 2024-10-31 17:24:49 -06:00
79bd7317c1 Match BranchDest->Reloc with relaxed relocation diffs 2024-10-31 17:24:33 -06:00
21f8f2407c Relax symbol comparison logic
The Ghidra delinker plugin emits functions with type STT_OBJECT,
rather than STT_FUNC. The current logic was preventing these from
being compared based on their symbol type. Relax this condition
for now.
2024-10-29 22:46:02 -06:00
d2b7a9ef25 Fix missing common BSS symbols
Resolves #128
2024-10-28 17:54:49 -06:00
2cf9cf24d6 Version v2.3.3 2024-10-20 20:01:35 -07:00
Anghelo Carvajal
5ef3416457 Improve dependency gating on objdiff-core (#126)
* Reduce dependencies for no features

* Add missing deps to every feature

* Add missing `dep:`s

* Gate even more deps behind features

Removes dependency on tsify-next / wasm-bindgen unless
compiling with the wasm feature by using `#[cfg_attr]`

* Fix wasm

---------

Co-authored-by: Luke Street <luke@street.dev>
2024-10-20 19:04:29 -07:00
Aetias
6ff8d002f7 Fix panic when parsing DWARF 2 line info for empty section (#125)
* Fix panic when parsing DWARF 2 line info for empty section

* Fix panic when parsing DWARF 2 line info for empty section
May as well remove both unwraps :p
2024-10-19 09:39:18 -06:00
9ca157d717 Lighten default blue diff color
The old default was very dark and blended in with
the dark theme's background.
2024-10-18 17:51:29 -06:00
Steven Casper
67b63311fc Fix data tooltip panic (#123)
* Fix data tooltip panic

Prevents panicing when attempting to display the data tooltip for a symbol that is too large by just using as many bytes as needed from the begging of the symbol.

* Don't attempt to interpret wrongly sized data

* Reference data display improvment issue

* Log failure to display a symbol's value
2024-10-14 22:03:30 -06:00
72ea1c8911 ci: Use rust-lld on Windows 2024-10-12 18:57:49 -06:00
d4a540857d ci: Add Rust workspace cache 2024-10-12 18:41:42 -06:00
676488433f Fix resolving symbols for section-relative relocations
Also fixes MIPS `j` handling when jumping within the function.

Reworks `ObjReloc` struct to be a little more sensible.
2024-10-11 18:09:18 -06:00
38 changed files with 4041 additions and 690 deletions

5
.cargo/config.toml Normal file
View File

@@ -0,0 +1,5 @@
[target.x86_64-pc-windows-msvc]
linker = "rust-lld"
[target.aarch64-pc-windows-msvc]
linker = "rust-lld"

View File

@@ -30,6 +30,8 @@ jobs:
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Cargo check
run: cargo check --all-features --all-targets
- name: Cargo clippy
@@ -85,6 +87,8 @@ jobs:
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Cargo test
run: cargo test --release --all-features
@@ -151,6 +155,10 @@ jobs:
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
- name: Cargo build
run: >
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
@@ -202,6 +210,10 @@ jobs:
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
- name: Cargo build
run: >
cargo build --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}

6
.gitignore vendored
View File

@@ -3,10 +3,6 @@ target/
**/*.rs.bk
generated/
# cargo-mobile
.cargo/
/gen
# macOS
.DS_Store
@@ -22,4 +18,4 @@ android.keystore
*.frag
*.vert
*.metal
.vscode/launch.json
.vscode/

1191
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,7 @@ strip = "debuginfo"
codegen-units = 1
[workspace.package]
version = "2.3.1"
version = "2.4.0"
authors = ["Luke Street <luke@street.dev>"]
edition = "2021"
license = "MIT OR Apache-2.0"

View File

@@ -20,6 +20,7 @@ Supports:
- MIPS (N64, PS1, PS2, PSP)
- x86 (COFF only at the moment)
- ARM (GBA, DS, 3DS)
- ARM64 (Switch, experimental)
See [Usage](#usage) for more information.

View File

@@ -70,10 +70,10 @@ feature-depth = 1
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = [
"RUSTSEC-2024-0370",
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
{ id = "RUSTSEC-2024-0384", reason = "Unmaintained indirect dependency" },
]
# If this is true, then cargo deny will use the git executable to fetch advisory database.
# If this is false, then it uses a built-in git library.
@@ -98,7 +98,7 @@ allow = [
"BSL-1.0",
"CC0-1.0",
"MPL-2.0",
"Unicode-DFS-2016",
"Unicode-3.0",
"Zlib",
"0BSD",
"OFL-1.1",
@@ -240,7 +240,7 @@ allow-git = []
[sources.allow-org]
# github.com organizations to allow git sources for
github = ["encounter"]
github = ["notify-rs"]
# gitlab.com organizations to allow git sources for
gitlab = []
# bitbucket.org organizations to allow git sources for

View File

@@ -20,7 +20,7 @@ enable-ansi-support = "0.2"
memmap2 = "0.9"
objdiff-core = { path = "../objdiff-core", features = ["all"] }
prost = "0.13"
ratatui = "0.28"
ratatui = "0.29"
rayon = "1.10"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@@ -844,10 +844,14 @@ impl FunctionDiffUi {
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
}
}
DiffText::Symbol(sym) => {
DiffText::Symbol(sym, diff) => {
let name = sym.demangled_name.as_ref().unwrap_or(&sym.name);
label_text = name.clone();
base_color = Color::White;
if let Some(diff) = diff {
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
} else {
base_color = Color::White;
}
}
DiffText::Spacing(n) => {
line.spans.push(Span::raw(" ".repeat(n)));

View File

@@ -16,16 +16,17 @@ documentation = "https://docs.rs/objdiff-core"
crate-type = ["cdylib", "rlib"]
[features]
all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "bindings"]
any-arch = ["bimap"] # Implicit, used to check if any arch is enabled
config = ["bimap", "globset", "semver", "serde_json", "serde_yaml"]
dwarf = ["gimli"]
mips = ["any-arch", "rabbitizer"]
ppc = ["any-arch", "cwdemangle", "cwextab", "ppc750cl"]
x86 = ["any-arch", "cpp_demangle", "iced-x86", "msvc-demangler"]
arm = ["any-arch", "cpp_demangle", "unarm", "arm-attr"]
bindings = ["serde_json", "prost", "pbjson"]
wasm = ["bindings", "console_error_panic_hook", "console_log"]
all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "arm64", "bindings"]
any-arch = ["config", "dep:bimap", "dep:strum", "dep:similar", "dep:flagset", "dep:log", "dep:memmap2", "dep:byteorder", "dep:num-traits"] # Implicit, used to check if any arch is enabled
config = ["dep:bimap", "dep:globset", "dep:semver", "dep:serde_json", "dep:serde_yaml", "dep:serde", "dep:filetime"]
dwarf = ["dep:gimli"]
mips = ["any-arch", "dep:rabbitizer"]
ppc = ["any-arch", "dep:cwdemangle", "dep:cwextab", "dep:ppc750cl"]
x86 = ["any-arch", "dep:cpp_demangle", "dep:iced-x86", "dep:msvc-demangler"]
arm = ["any-arch", "dep:cpp_demangle", "dep:unarm", "dep:arm-attr"]
arm64 = ["any-arch", "dep:cpp_demangle", "dep:yaxpeax-arch", "dep:yaxpeax-arm"]
bindings = ["dep:serde_json", "dep:prost", "dep:pbjson", "dep:serde", "dep:prost-build", "dep:pbjson-build"]
wasm = ["bindings", "any-arch", "dep:console_error_panic_hook", "dep:console_log", "dep:wasm-bindgen", "dep:tsify-next", "dep:log"]
[package.metadata.docs.rs]
features = ["all"]
@@ -33,20 +34,20 @@ features = ["all"]
[dependencies]
anyhow = "1.0"
bimap = { version = "0.6", features = ["serde"], optional = true }
byteorder = "1.5"
filetime = "0.2"
flagset = "0.4"
log = "0.4"
memmap2 = "0.9"
num-traits = "0.2"
byteorder = { version = "1.5", optional = true }
filetime = { version = "0.2", optional = true }
flagset = { version = "0.4", optional = true }
log = { version = "0.4", optional = true }
memmap2 = { version = "0.9", optional = true }
num-traits = { version = "0.2", optional = true }
object = { version = "0.36", features = ["read_core", "std", "elf", "pe"], default-features = false }
pbjson = { version = "0.7", optional = true }
prost = { version = "0.13", optional = true }
serde = { version = "1.0", features = ["derive"] }
similar = { version = "2.6", default-features = false }
strum = { version = "0.26", features = ["derive"] }
wasm-bindgen = "0.2"
tsify-next = { version = "0.5", default-features = false, features = ["js"] }
serde = { version = "1.0", features = ["derive"], optional = true }
similar = { version = "2.6", default-features = false, optional = true }
strum = { version = "0.26", features = ["derive"], optional = true }
wasm-bindgen = { version = "0.2", optional = true }
tsify-next = { version = "0.5", default-features = false, features = ["js"], optional = true }
console_log = { version = "1.0", optional = true }
console_error_panic_hook = { version = "0.1", optional = true }
@@ -76,6 +77,10 @@ msvc-demangler = { version = "0.10", optional = true }
unarm = { version = "1.6", optional = true }
arm-attr = { version = "0.1", optional = true }
# arm64
yaxpeax-arch = { version = "0.3", default-features = false, features = ["std"], optional = true }
yaxpeax-arm = { version = "0.3", default-features = false, features = ["std"], optional = true }
[build-dependencies]
prost-build = "0.13"
pbjson-build = "0.7"
prost-build = { version = "0.13", optional = true }
pbjson-build = { version = "0.7", optional = true }

View File

@@ -11,4 +11,5 @@ objdiff-core contains the core functionality of [objdiff](https://github.com/enc
- **`ppc`**: Enables the PowerPC backend powered by [ppc750cl](https://github.com/encounter/ppc750cl).
- **`x86`**: Enables the x86 backend powered by [iced-x86](https://crates.io/crates/iced-x86).
- **`arm`**: Enables the ARM backend powered by [unarm](https://github.com/AetiasHax/unarm).
- **`arm64`**: Enables the ARM64 backend powered by [yaxpeax-arm](https://github.com/iximeow/yaxpeax-arm).
- **`bindings`**: Enables serialization and deserialization of objdiff data structures.

View File

@@ -1,6 +1,11 @@
use std::path::{Path, PathBuf};
fn main() {
#[cfg(feature = "bindings")]
compile_protos();
}
#[cfg(feature = "bindings")]
fn compile_protos() {
use std::path::{Path, PathBuf};
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("protos");
let descriptor_path = root.join("proto_descriptor.bin");
println!("cargo:rerun-if-changed={}", descriptor_path.display());

View File

@@ -124,11 +124,9 @@ impl ObjArch for ObjArchArm {
.get(&SectionIndex(section_index))
.map(|x| x.as_slice())
.unwrap_or(&fallback_mappings);
let first_mapping_idx =
match mapping_symbols.binary_search_by_key(&start_addr, |x| x.address) {
Ok(idx) => idx,
Err(idx) => idx - 1,
};
let first_mapping_idx = mapping_symbols
.binary_search_by_key(&start_addr, |x| x.address)
.unwrap_or_else(|idx| idx - 1);
let first_mapping = mapping_symbols[first_mapping_idx].mapping;
let mut mappings_iter =
@@ -215,7 +213,7 @@ impl ObjArch for ObjArchArm {
address: address as u64,
size: (parser.address - address) as u8,
op: ins.opcode_id(),
mnemonic: parsed_ins.mnemonic.to_string(),
mnemonic: Cow::Borrowed(parsed_ins.mnemonic),
args,
reloc,
branch_dest,
@@ -234,7 +232,7 @@ impl ObjArch for ObjArchArm {
section: &ObjSection,
address: u64,
reloc: &Relocation,
) -> anyhow::Result<i64> {
) -> Result<i64> {
let address = address as usize;
Ok(match reloc.flags() {
// ARM calls

File diff suppressed because it is too large Load Diff

View File

@@ -83,7 +83,7 @@ impl ObjArch for ObjArchMips {
&self,
address: u64,
code: &[u8],
_section_index: usize,
section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig,
@@ -119,7 +119,7 @@ impl ObjArch for ObjArchMips {
let op = instruction.unique_id as u16;
ops.push(op);
let mnemonic = instruction.opcode_name().to_string();
let mnemonic = instruction.opcode_name();
let is_branch = instruction.is_branch();
let branch_offset = instruction.branch_offset();
let mut branch_dest = if is_branch {
@@ -140,11 +140,18 @@ impl ObjArch for ObjArchMips {
| OperandType::cpu_label
| OperandType::cpu_branch_target_label => {
if let Some(reloc) = reloc {
if matches!(&reloc.target_section, Some(s) if s == ".text")
&& reloc.target.address > start_address
&& reloc.target.address < end_address
// If the relocation target is within the current function, we can
// convert it into a relative branch target. Note that we check
// target_address > start_address instead of >= so that recursive
// tail calls are not considered branch targets.
let target_address =
reloc.target.address.checked_add_signed(reloc.addend);
if reloc.target.orig_section_index == Some(section_index)
&& matches!(target_address, Some(addr) if addr > start_address && addr < end_address)
{
args.push(ObjInsArg::BranchDest(reloc.target.address));
let target_address = target_address.unwrap();
args.push(ObjInsArg::BranchDest(target_address));
branch_dest = Some(target_address);
} else {
push_reloc(&mut args, reloc)?;
branch_dest = None;
@@ -195,7 +202,7 @@ impl ObjArch for ObjArchMips {
address: cur_addr as u64,
size: 4,
op,
mnemonic,
mnemonic: Cow::Borrowed(mnemonic),
args,
reloc: reloc.cloned(),
branch_dest,

View File

@@ -12,6 +12,8 @@ use crate::{
#[cfg(feature = "arm")]
mod arm;
#[cfg(feature = "arm64")]
mod arm64;
#[cfg(feature = "mips")]
pub mod mips;
#[cfg(feature = "ppc")]
@@ -34,7 +36,11 @@ pub enum DataType {
impl DataType {
pub fn display_bytes<Endian: ByteOrder>(&self, bytes: &[u8]) -> Option<String> {
if self.required_len().is_some_and(|l| bytes.len() < l) {
// TODO: Attempt to interpret large symbols as arrays of a smaller type,
// fallback to intrepreting it as bytes.
// https://github.com/encounter/objdiff/issues/124
if self.required_len().is_some_and(|l| bytes.len() != l) {
log::warn!("Failed to display a symbol value for a symbol whose size doesn't match the instruction referencing it.");
return None;
}
@@ -161,6 +167,8 @@ pub fn new_arch(object: &File) -> Result<Box<dyn ObjArch>> {
Architecture::I386 | Architecture::X86_64 => Box::new(x86::ObjArchX86::new(object)?),
#[cfg(feature = "arm")]
Architecture::Arm => Box::new(arm::ObjArchArm::new(object)?),
#[cfg(feature = "arm64")]
Architecture::Aarch64 => Box::new(arm64::ObjArchArm64::new(object)?),
arch => bail!("Unsupported architecture: {arch:?}"),
})
}

View File

@@ -143,7 +143,7 @@ impl ObjArch for ObjArchPpc {
insts.push(ObjIns {
address: cur_addr as u64,
size: 4,
mnemonic: simplified.mnemonic.to_string(),
mnemonic: Cow::Borrowed(simplified.mnemonic),
args,
reloc: reloc.cloned(),
op: ins.op as u16,

View File

@@ -51,7 +51,7 @@ impl ObjArch for ObjArchX86 {
address: 0,
size: 0,
op: 0,
mnemonic: String::new(),
mnemonic: Cow::Borrowed("<invalid>"),
args: vec![],
reloc: None,
branch_dest: None,
@@ -76,7 +76,7 @@ impl ObjArch for ObjArchX86 {
address,
size: instruction.len() as u8,
op,
mnemonic: String::new(),
mnemonic: Cow::Borrowed("<invalid>"),
args: vec![],
reloc: reloc.cloned(),
branch_dest: None,
@@ -242,7 +242,8 @@ impl FormatterOutput for InstructionFormatterOutput {
fn write_mnemonic(&mut self, _instruction: &Instruction, text: &str) {
self.formatted.push_str(text);
self.ins.mnemonic = text.to_string();
// TODO: can iced-x86 guarantee 'static here?
self.ins.mnemonic = Cow::Owned(text.to_string());
}
fn write_number(

View File

@@ -1,3 +1,4 @@
#![allow(clippy::needless_lifetimes)] // Generated serde code
use crate::{
diff::{
ObjDataDiff, ObjDataDiffKind, ObjDiff, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo,
@@ -70,9 +71,13 @@ impl FunctionDiff {
// let (_section, symbol) = object.section_symbol(symbol_ref);
// Symbol::from(symbol)
// });
let instructions = symbol_diff.instructions.iter().map(InstructionDiff::from).collect();
let instructions = symbol_diff
.instructions
.iter()
.map(|ins_diff| InstructionDiff::new(object, ins_diff))
.collect();
Self {
symbol: Some(Symbol::from(symbol)),
symbol: Some(Symbol::new(symbol)),
// diff_symbol,
instructions,
match_percent: symbol_diff.match_percent,
@@ -90,8 +95,8 @@ impl DataDiff {
}
}
impl<'a> From<&'a ObjSymbol> for Symbol {
fn from(value: &'a ObjSymbol) -> Self {
impl Symbol {
pub fn new(value: &ObjSymbol) -> Self {
Self {
name: value.name.to_string(),
demangled_name: value.demangled_name.clone(),
@@ -122,29 +127,29 @@ fn symbol_flags(value: ObjSymbolFlagSet) -> u32 {
flags
}
impl<'a> From<&'a ObjIns> for Instruction {
fn from(value: &'a ObjIns) -> Self {
impl Instruction {
pub fn new(object: &ObjInfo, instruction: &ObjIns) -> Self {
Self {
address: value.address,
size: value.size as u32,
opcode: value.op as u32,
mnemonic: value.mnemonic.clone(),
formatted: value.formatted.clone(),
arguments: value.args.iter().map(Argument::from).collect(),
relocation: value.reloc.as_ref().map(Relocation::from),
branch_dest: value.branch_dest,
line_number: value.line,
original: value.orig.clone(),
address: instruction.address,
size: instruction.size as u32,
opcode: instruction.op as u32,
mnemonic: instruction.mnemonic.to_string(),
formatted: instruction.formatted.clone(),
arguments: instruction.args.iter().map(Argument::new).collect(),
relocation: instruction.reloc.as_ref().map(|reloc| Relocation::new(object, reloc)),
branch_dest: instruction.branch_dest,
line_number: instruction.line,
original: instruction.orig.clone(),
}
}
}
impl<'a> From<&'a ObjInsArg> for Argument {
fn from(value: &'a ObjInsArg) -> Self {
impl Argument {
pub fn new(value: &ObjInsArg) -> Self {
Self {
value: Some(match value {
ObjInsArg::PlainText(s) => argument::Value::PlainText(s.to_string()),
ObjInsArg::Arg(v) => argument::Value::Argument(ArgumentValue::from(v)),
ObjInsArg::Arg(v) => argument::Value::Argument(ArgumentValue::new(v)),
ObjInsArg::Reloc => argument::Value::Relocation(ArgumentRelocation {}),
ObjInsArg::BranchDest(dest) => argument::Value::BranchDest(*dest),
}),
@@ -152,8 +157,8 @@ impl<'a> From<&'a ObjInsArg> for Argument {
}
}
impl From<&ObjInsArgValue> for ArgumentValue {
fn from(value: &ObjInsArgValue) -> Self {
impl ArgumentValue {
pub fn new(value: &ObjInsArgValue) -> Self {
Self {
value: Some(match value {
ObjInsArgValue::Signed(v) => argument_value::Value::Signed(*v),
@@ -164,42 +169,39 @@ impl From<&ObjInsArgValue> for ArgumentValue {
}
}
impl<'a> From<&'a ObjReloc> for Relocation {
fn from(value: &ObjReloc) -> Self {
impl Relocation {
pub fn new(object: &ObjInfo, reloc: &ObjReloc) -> Self {
Self {
r#type: match value.flags {
r#type: match reloc.flags {
object::RelocationFlags::Elf { r_type } => r_type,
object::RelocationFlags::MachO { r_type, .. } => r_type as u32,
object::RelocationFlags::Coff { typ } => typ as u32,
object::RelocationFlags::Xcoff { r_rtype, .. } => r_rtype as u32,
_ => unreachable!(),
},
type_name: String::new(), // TODO
target: Some(RelocationTarget::from(&value.target)),
type_name: object.arch.display_reloc(reloc.flags).into_owned(),
target: Some(RelocationTarget {
symbol: Some(Symbol::new(&reloc.target)),
addend: reloc.addend,
}),
}
}
}
impl<'a> From<&'a ObjSymbol> for RelocationTarget {
fn from(value: &'a ObjSymbol) -> Self {
Self { symbol: Some(Symbol::from(value)), addend: value.addend }
}
}
impl<'a> From<&'a ObjInsDiff> for InstructionDiff {
fn from(value: &'a ObjInsDiff) -> Self {
impl InstructionDiff {
pub fn new(object: &ObjInfo, instruction_diff: &ObjInsDiff) -> Self {
Self {
instruction: value.ins.as_ref().map(Instruction::from),
diff_kind: DiffKind::from(value.kind) as i32,
branch_from: value.branch_from.as_ref().map(InstructionBranchFrom::from),
branch_to: value.branch_to.as_ref().map(InstructionBranchTo::from),
arg_diff: value.arg_diff.iter().map(ArgumentDiff::from).collect(),
instruction: instruction_diff.ins.as_ref().map(|ins| Instruction::new(object, ins)),
diff_kind: DiffKind::from(instruction_diff.kind) as i32,
branch_from: instruction_diff.branch_from.as_ref().map(InstructionBranchFrom::new),
branch_to: instruction_diff.branch_to.as_ref().map(InstructionBranchTo::new),
arg_diff: instruction_diff.arg_diff.iter().map(ArgumentDiff::new).collect(),
}
}
}
impl From<&Option<ObjInsArgDiff>> for ArgumentDiff {
fn from(value: &Option<ObjInsArgDiff>) -> Self {
impl ArgumentDiff {
pub fn new(value: &Option<ObjInsArgDiff>) -> Self {
Self { diff_index: value.as_ref().map(|v| v.idx as u32) }
}
}
@@ -228,8 +230,8 @@ impl From<ObjDataDiffKind> for DiffKind {
}
}
impl<'a> From<&'a ObjInsBranchFrom> for InstructionBranchFrom {
fn from(value: &'a ObjInsBranchFrom) -> Self {
impl InstructionBranchFrom {
pub fn new(value: &ObjInsBranchFrom) -> Self {
Self {
instruction_index: value.ins_idx.iter().map(|&x| x as u32).collect(),
branch_index: value.branch_idx as u32,
@@ -237,8 +239,8 @@ impl<'a> From<&'a ObjInsBranchFrom> for InstructionBranchFrom {
}
}
impl<'a> From<&'a ObjInsBranchTo> for InstructionBranchTo {
fn from(value: &'a ObjInsBranchTo) -> Self {
impl InstructionBranchTo {
pub fn new(value: &ObjInsBranchTo) -> Self {
Self { instruction_index: value.ins_idx as u32, branch_index: value.branch_idx as u32 }
}
}

View File

@@ -1,3 +1,4 @@
#![allow(clippy::needless_lifetimes)] // Generated serde code
use std::ops::AddAssign;
use anyhow::{bail, Result};
@@ -173,8 +174,7 @@ impl Report {
continue;
}
fn is_sub_category(id: &str, parent: &str, sep: char) -> bool {
id.starts_with(parent)
&& id.get(parent.len()..).map_or(false, |s| s.starts_with(sep))
id.starts_with(parent) && id.get(parent.len()..).is_some_and(|s| s.starts_with(sep))
}
let mut sub_categories = self
.categories

View File

@@ -165,6 +165,8 @@ pub struct ScratchConfig {
pub ctx_path: Option<PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub build_ctx: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub preset_id: Option<u32>,
}
pub const CONFIG_FILENAMES: [&str; 3] = ["objdiff.json", "objdiff.yml", "objdiff.yaml"];

View File

@@ -9,7 +9,7 @@ use crate::{
DiffObjConfig, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind,
ObjSymbolDiff,
},
obj::{ObjInfo, ObjInsArg, ObjReloc, ObjSymbol, ObjSymbolFlags, SymbolRef},
obj::{ObjInfo, ObjInsArg, ObjReloc, ObjSymbolFlags, SymbolRef},
};
pub fn process_code_symbol(
@@ -45,6 +45,8 @@ pub fn no_diff_code(out: &ProcessCodeResult, symbol_ref: SymbolRef) -> Result<Ob
}
pub fn diff_code(
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left_out: &ProcessCodeResult,
right_out: &ProcessCodeResult,
left_symbol_ref: SymbolRef,
@@ -60,7 +62,7 @@ pub fn diff_code(
let mut diff_state = InsDiffState::default();
for (left, right) in left_diff.iter_mut().zip(right_diff.iter_mut()) {
let result = compare_ins(config, left, right, &mut diff_state)?;
let result = compare_ins(config, left_obj, right_obj, left, right, &mut diff_state)?;
left.kind = result.kind;
right.kind = result.kind;
left.arg_diff = result.left_args_diff;
@@ -170,12 +172,33 @@ fn resolve_branches(vec: &mut [ObjInsDiff]) {
}
}
fn address_eq(left: &ObjSymbol, right: &ObjSymbol) -> bool {
left.address as i64 + left.addend == right.address as i64 + right.addend
fn address_eq(left: &ObjReloc, right: &ObjReloc) -> bool {
left.target.address as i64 + left.addend == right.target.address as i64 + right.addend
}
fn section_name_eq(
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left_orig_section_index: usize,
right_orig_section_index: usize,
) -> bool {
let Some(left_section) =
left_obj.sections.iter().find(|s| s.orig_index == left_orig_section_index)
else {
return false;
};
let Some(right_section) =
right_obj.sections.iter().find(|s| s.orig_index == right_orig_section_index)
else {
return false;
};
left_section.name == right_section.name
}
fn reloc_eq(
config: &DiffObjConfig,
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left_reloc: Option<&ObjReloc>,
right_reloc: Option<&ObjReloc>,
) -> bool {
@@ -189,23 +212,26 @@ fn reloc_eq(
return true;
}
let name_matches = left.target.name == right.target.name;
match (&left.target_section, &right.target_section) {
let symbol_name_matches = left.target.name == right.target.name;
match (&left.target.orig_section_index, &right.target.orig_section_index) {
(Some(sl), Some(sr)) => {
// Match if section and name or address match
sl == sr && (name_matches || address_eq(&left.target, &right.target))
section_name_eq(left_obj, right_obj, *sl, *sr)
&& (symbol_name_matches || address_eq(left, right))
}
(Some(_), None) => false,
(None, Some(_)) => {
// Match if possibly stripped weak symbol
name_matches && right.target.flags.0.contains(ObjSymbolFlags::Weak)
symbol_name_matches && right.target.flags.0.contains(ObjSymbolFlags::Weak)
}
(None, None) => name_matches,
(None, None) => symbol_name_matches,
}
}
fn arg_eq(
config: &DiffObjConfig,
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left: &ObjInsArg,
right: &ObjInsArg,
left_diff: &ObjInsDiff,
@@ -227,15 +253,23 @@ fn arg_eq(
matches!(right, ObjInsArg::Reloc)
&& reloc_eq(
config,
left_obj,
right_obj,
left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
)
}
ObjInsArg::BranchDest(_) => {
ObjInsArg::BranchDest(_) => match right {
// Compare dest instruction idx after diffing
left_diff.branch_to.as_ref().map(|b| b.ins_idx)
== right_diff.branch_to.as_ref().map(|b| b.ins_idx)
}
ObjInsArg::BranchDest(_) => {
left_diff.branch_to.as_ref().map(|b| b.ins_idx)
== right_diff.branch_to.as_ref().map(|b| b.ins_idx)
}
// If relocations are relaxed, match if left is a constant and right is a reloc
// Useful for instances where the target object is created without relocations
ObjInsArg::Reloc => config.relax_reloc_diffs,
_ => false,
},
}
}
@@ -257,21 +291,18 @@ struct InsDiffResult {
fn compare_ins(
config: &DiffObjConfig,
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left: &ObjInsDiff,
right: &ObjInsDiff,
state: &mut InsDiffState,
) -> Result<InsDiffResult> {
let mut result = InsDiffResult::default();
if let (Some(left_ins), Some(right_ins)) = (&left.ins, &right.ins) {
if left_ins.args.len() != right_ins.args.len()
|| left_ins.op != right_ins.op
// Check if any PlainText segments differ (punctuation and spacing)
// This indicates a more significant difference than a simple arg mismatch
|| !left_ins.args.iter().zip(&right_ins.args).all(|(a, b)| match (a, b) {
(ObjInsArg::PlainText(l), ObjInsArg::PlainText(r)) => l == r,
_ => true,
})
{
// Count only non-PlainText args
let left_args_count = left_ins.iter_args().count();
let right_args_count = right_ins.iter_args().count();
if left_args_count != right_args_count || left_ins.op != right_ins.op {
// Totally different op
result.kind = ObjInsDiffKind::Replace;
state.diff_count += 1;
@@ -282,8 +313,8 @@ fn compare_ins(
result.kind = ObjInsDiffKind::OpMismatch;
state.diff_count += 1;
}
for (a, b) in left_ins.args.iter().zip(&right_ins.args) {
if arg_eq(config, a, b, left, right) {
for (a, b) in left_ins.iter_args().zip(right_ins.iter_args()) {
if arg_eq(config, left_obj, right_obj, a, b, left, right) {
result.left_args_diff.push(None);
result.right_args_diff.push(None);
} else {
@@ -294,8 +325,11 @@ fn compare_ins(
let a_str = match a {
ObjInsArg::PlainText(arg) => arg.to_string(),
ObjInsArg::Arg(arg) => arg.to_string(),
ObjInsArg::Reloc => String::new(),
ObjInsArg::BranchDest(arg) => format!("{arg}"),
ObjInsArg::Reloc => left_ins
.reloc
.as_ref()
.map_or_else(|| "<unknown>".to_string(), |r| r.target.name.clone()),
ObjInsArg::BranchDest(arg) => arg.to_string(),
};
let a_diff = if let Some(idx) = state.left_args_idx.get(&a_str) {
ObjInsArgDiff { idx: *idx }
@@ -308,8 +342,11 @@ fn compare_ins(
let b_str = match b {
ObjInsArg::PlainText(arg) => arg.to_string(),
ObjInsArg::Arg(arg) => arg.to_string(),
ObjInsArg::Reloc => String::new(),
ObjInsArg::BranchDest(arg) => format!("{arg}"),
ObjInsArg::Reloc => right_ins
.reloc
.as_ref()
.map_or_else(|| "<unknown>".to_string(), |r| r.target.name.clone()),
ObjInsArg::BranchDest(arg) => arg.to_string(),
};
let b_diff = if let Some(idx) = state.right_args_idx.get(&b_str) {
ObjInsArgDiff { idx: *idx }

View File

@@ -22,7 +22,7 @@ pub enum DiffText<'a> {
/// Branch destination
BranchDest(u64, Option<&'a ObjInsArgDiff>),
/// Symbol name
Symbol(&'a ObjSymbol),
Symbol(&'a ObjSymbol, Option<&'a ObjInsArgDiff>),
/// Number of spaces
Spacing(usize),
/// End of line
@@ -58,20 +58,23 @@ pub fn display_diff<E>(
cb(DiffText::Spacing(4))?;
}
cb(DiffText::Opcode(&ins.mnemonic, ins.op))?;
let mut arg_diff_idx = 0; // non-PlainText index
for (i, arg) in ins.args.iter().enumerate() {
if i == 0 {
cb(DiffText::Spacing(1))?;
}
let diff = ins_diff.arg_diff.get(i).and_then(|o| o.as_ref());
let diff = ins_diff.arg_diff.get(arg_diff_idx).and_then(|o| o.as_ref());
match arg {
ObjInsArg::PlainText(s) => {
cb(DiffText::Basic(s))?;
}
ObjInsArg::Arg(v) => {
cb(DiffText::Argument(v, diff))?;
arg_diff_idx += 1;
}
ObjInsArg::Reloc => {
display_reloc_name(ins.reloc.as_ref().unwrap(), &mut cb)?;
display_reloc_name(ins.reloc.as_ref().unwrap(), &mut cb, diff)?;
arg_diff_idx += 1;
}
ObjInsArg::BranchDest(dest) => {
if let Some(dest) = dest.checked_sub(base_addr) {
@@ -79,6 +82,7 @@ pub fn display_diff<E>(
} else {
cb(DiffText::Basic("<unknown>"))?;
}
arg_diff_idx += 1;
}
}
}
@@ -92,11 +96,12 @@ pub fn display_diff<E>(
fn display_reloc_name<E>(
reloc: &ObjReloc,
mut cb: impl FnMut(DiffText) -> Result<(), E>,
diff: Option<&ObjInsArgDiff>,
) -> Result<(), E> {
cb(DiffText::Symbol(&reloc.target))?;
match reloc.target.addend.cmp(&0i64) {
Ordering::Greater => cb(DiffText::Basic(&format!("+{:#x}", reloc.target.addend))),
Ordering::Less => cb(DiffText::Basic(&format!("-{:#x}", -reloc.target.addend))),
cb(DiffText::Symbol(&reloc.target, diff))?;
match reloc.addend.cmp(&0i64) {
Ordering::Greater => cb(DiffText::Basic(&format!("+{:#x}", reloc.addend))),
Ordering::Less => cb(DiffText::Basic(&format!("-{:#x}", -reloc.addend))),
_ => Ok(()),
}
}
@@ -106,7 +111,7 @@ impl PartialEq<DiffText<'_>> for HighlightKind {
match (self, other) {
(HighlightKind::Opcode(a), DiffText::Opcode(_, b)) => a == b,
(HighlightKind::Arg(a), DiffText::Argument(b, _)) => a.loose_eq(b),
(HighlightKind::Symbol(a), DiffText::Symbol(b)) => a == &b.name,
(HighlightKind::Symbol(a), DiffText::Symbol(b, _)) => a == &b.name,
(HighlightKind::Address(a), DiffText::Address(b) | DiffText::BranchDest(b, _)) => {
a == b
}
@@ -124,7 +129,7 @@ impl From<DiffText<'_>> for HighlightKind {
match value {
DiffText::Opcode(_, op) => HighlightKind::Opcode(op),
DiffText::Argument(arg, _) => HighlightKind::Arg(arg.clone()),
DiffText::Symbol(sym) => HighlightKind::Symbol(sym.name.to_string()),
DiffText::Symbol(sym, _) => HighlightKind::Symbol(sym.name.to_string()),
DiffText::Address(addr) | DiffText::BranchDest(addr, _) => HighlightKind::Address(addr),
_ => HighlightKind::None,
}

View File

@@ -11,7 +11,7 @@ use crate::{
diff_generic_section, no_diff_symbol,
},
},
obj::{ObjInfo, ObjIns, ObjSection, ObjSectionKind, ObjSymbol, SymbolRef},
obj::{ObjInfo, ObjIns, ObjSection, ObjSectionKind, ObjSymbol, SymbolRef, SECTION_COMMON},
};
pub mod code;
@@ -29,8 +29,8 @@ pub mod display;
serde::Serialize,
strum::VariantArray,
strum::EnumMessage,
tsify_next::Tsify,
)]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
pub enum X86Formatter {
#[default]
#[strum(message = "Intel (default)")]
@@ -54,8 +54,8 @@ pub enum X86Formatter {
serde::Serialize,
strum::VariantArray,
strum::EnumMessage,
tsify_next::Tsify,
)]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
pub enum MipsAbi {
#[default]
#[strum(message = "Auto (default)")]
@@ -79,8 +79,8 @@ pub enum MipsAbi {
serde::Serialize,
strum::VariantArray,
strum::EnumMessage,
tsify_next::Tsify,
)]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
pub enum MipsInstrCategory {
#[default]
#[strum(message = "Auto (default)")]
@@ -108,8 +108,8 @@ pub enum MipsInstrCategory {
serde::Serialize,
strum::VariantArray,
strum::EnumMessage,
tsify_next::Tsify,
)]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
pub enum ArmArchVersion {
#[default]
#[strum(message = "Auto (default)")]
@@ -133,8 +133,8 @@ pub enum ArmArchVersion {
serde::Serialize,
strum::VariantArray,
strum::EnumMessage,
tsify_next::Tsify,
)]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
pub enum ArmR9Usage {
#[default]
#[strum(
@@ -154,8 +154,9 @@ pub enum ArmR9Usage {
#[inline]
const fn default_true() -> bool { true }
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, tsify_next::Tsify)]
#[tsify(from_wasm_abi)]
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
#[cfg_attr(feature = "wasm", tsify(from_wasm_abi))]
#[serde(default)]
pub struct DiffObjConfig {
pub relax_reloc_diffs: bool,
@@ -244,7 +245,7 @@ pub struct ObjInsDiff {
pub branch_from: Option<ObjInsBranchFrom>,
/// Branches to instruction
pub branch_to: Option<ObjInsBranchTo>,
/// Arg diffs
/// Arg diffs (only contains non-PlainText args)
pub arg_diff: Vec<Option<ObjInsArgDiff>>,
}
@@ -339,7 +340,7 @@ impl ObjDiff {
}
for (symbol_idx, _) in obj.common.iter().enumerate() {
result.common.push(ObjSymbolDiff {
symbol_ref: SymbolRef { section_idx: obj.sections.len(), symbol_idx },
symbol_ref: SymbolRef { section_idx: SECTION_COMMON, symbol_idx },
target_symbol: None,
instructions: vec![],
match_percent: None,
@@ -360,7 +361,7 @@ impl ObjDiff {
#[inline]
pub fn symbol_diff(&self, symbol_ref: SymbolRef) -> &ObjSymbolDiff {
if symbol_ref.section_idx == self.sections.len() {
if symbol_ref.section_idx == SECTION_COMMON {
&self.common[symbol_ref.symbol_idx]
} else {
&self.section_diff(symbol_ref.section_idx).symbols[symbol_ref.symbol_idx]
@@ -369,7 +370,7 @@ impl ObjDiff {
#[inline]
pub fn symbol_diff_mut(&mut self, symbol_ref: SymbolRef) -> &mut ObjSymbolDiff {
if symbol_ref.section_idx == self.sections.len() {
if symbol_ref.section_idx == SECTION_COMMON {
&mut self.common[symbol_ref.symbol_idx]
} else {
&mut self.section_diff_mut(symbol_ref.section_idx).symbols[symbol_ref.symbol_idx]
@@ -411,6 +412,8 @@ pub fn diff_objs(
let left_code = process_code_symbol(left_obj, left_symbol_ref, config)?;
let right_code = process_code_symbol(right_obj, right_symbol_ref, config)?;
let (left_diff, right_diff) = diff_code(
left_obj,
right_obj,
&left_code,
&right_code,
left_symbol_ref,
@@ -424,6 +427,8 @@ pub fn diff_objs(
let (prev_obj, prev_out) = prev.as_mut().unwrap();
let prev_code = process_code_symbol(prev_obj, prev_symbol_ref, config)?;
let (_, prev_diff) = diff_code(
left_obj,
right_obj,
&right_code,
&prev_code,
right_symbol_ref,
@@ -572,7 +577,7 @@ fn generate_mapping_symbols(
let Some(base_symbol_ref) = symbol_ref_by_name(base_obj, base_name) else {
return Ok(());
};
let (base_section, base_symbol) = base_obj.section_symbol(base_symbol_ref);
let (base_section, _base_symbol) = base_obj.section_symbol(base_symbol_ref);
let Some(base_section) = base_section else {
return Ok(());
};
@@ -583,15 +588,15 @@ fn generate_mapping_symbols(
for (target_section_index, target_section) in
target_obj.sections.iter().enumerate().filter(|(_, s)| s.kind == base_section.kind)
{
for (target_symbol_index, _target_symbol) in
target_section.symbols.iter().enumerate().filter(|(_, s)| s.kind == base_symbol.kind)
{
for (target_symbol_index, _target_symbol) in target_section.symbols.iter().enumerate() {
let target_symbol_ref =
SymbolRef { section_idx: target_section_index, symbol_idx: target_symbol_index };
match base_section.kind {
ObjSectionKind::Code => {
let target_code = process_code_symbol(target_obj, target_symbol_ref, config)?;
let (left_diff, _right_diff) = diff_code(
target_obj,
base_obj,
&target_code,
base_code.as_ref().unwrap(),
target_symbol_ref,
@@ -751,7 +756,7 @@ fn matching_symbols(
}
}
for (symbol_idx, symbol) in left.common.iter().enumerate() {
let symbol_ref = SymbolRef { section_idx: left.sections.len(), symbol_idx };
let symbol_ref = SymbolRef { section_idx: SECTION_COMMON, symbol_idx };
if left_used.contains(&symbol_ref) {
continue;
}
@@ -783,7 +788,7 @@ fn matching_symbols(
}
}
for (symbol_idx, symbol) in right.common.iter().enumerate() {
let symbol_ref = SymbolRef { section_idx: right.sections.len(), symbol_idx };
let symbol_ref = SymbolRef { section_idx: SECTION_COMMON, symbol_idx };
if right_used.contains(&symbol_ref) {
continue;
}
@@ -876,7 +881,7 @@ fn find_common_symbol(obj: Option<&ObjInfo>, in_symbol: &ObjSymbol) -> Option<Sy
let obj = obj?;
for (symbol_idx, symbol) in obj.common.iter().enumerate() {
if symbol.name == in_symbol.name {
return Some(SymbolRef { section_idx: obj.sections.len(), symbol_idx });
return Some(SymbolRef { section_idx: SECTION_COMMON, symbol_idx });
}
}
None

View File

@@ -8,4 +8,5 @@ pub mod config;
pub mod diff;
#[cfg(feature = "any-arch")]
pub mod obj;
#[cfg(feature = "any-arch")]
pub mod util;

View File

@@ -85,6 +85,9 @@ pub enum ObjInsArg {
}
impl ObjInsArg {
#[inline]
pub fn is_plain_text(&self) -> bool { matches!(self, ObjInsArg::PlainText(_)) }
pub fn loose_eq(&self, other: &ObjInsArg) -> bool {
match (self, other) {
(ObjInsArg::Arg(a), ObjInsArg::Arg(b)) => a.loose_eq(b),
@@ -100,7 +103,7 @@ pub struct ObjIns {
pub address: u64,
pub size: u8,
pub op: u16,
pub mnemonic: String,
pub mnemonic: Cow<'static, str>,
pub args: Vec<ObjInsArg>,
pub reloc: Option<ObjReloc>,
pub branch_dest: Option<u64>,
@@ -112,6 +115,14 @@ pub struct ObjIns {
pub orig: Option<String>,
}
impl ObjIns {
/// Iterate over non-PlainText arguments.
#[inline]
pub fn iter_args(&self) -> impl DoubleEndedIterator<Item = &ObjInsArg> {
self.args.iter().filter(|a| !a.is_plain_text())
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
pub enum ObjSymbolKind {
#[default]
@@ -131,7 +142,7 @@ pub struct ObjSymbol {
pub size_known: bool,
pub kind: ObjSymbolKind,
pub flags: ObjSymbolFlagSet,
pub addend: i64,
pub orig_section_index: Option<usize>,
/// Original virtual address (from .note.split section)
pub virtual_address: Option<u64>,
/// Original index in object symbol table
@@ -155,7 +166,7 @@ pub struct ObjReloc {
pub flags: RelocationFlags,
pub address: u64,
pub target: ObjSymbol,
pub target_section: Option<String>,
pub addend: i64,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
@@ -164,9 +175,11 @@ pub struct SymbolRef {
pub symbol_idx: usize,
}
pub const SECTION_COMMON: usize = usize::MAX - 1;
impl ObjInfo {
pub fn section_symbol(&self, symbol_ref: SymbolRef) -> (Option<&ObjSection>, &ObjSymbol) {
if symbol_ref.section_idx == self.sections.len() {
if symbol_ref.section_idx == SECTION_COMMON {
let symbol = &self.common[symbol_ref.symbol_idx];
return (None, symbol);
}

View File

@@ -13,8 +13,8 @@ use object::{
endian::LittleEndian as LE,
pe::{ImageAuxSymbolFunctionBeginEnd, ImageLinenumber},
read::coff::{CoffFile, CoffHeader, ImageSymbol},
BinaryFormat, File, Object, ObjectSection, ObjectSymbol, RelocationTarget, SectionIndex,
SectionKind, Symbol, SymbolIndex, SymbolKind, SymbolScope, SymbolSection,
BinaryFormat, File, Object, ObjectSection, ObjectSymbol, RelocationTarget, Section,
SectionIndex, SectionKind, Symbol, SymbolIndex, SymbolKind, SymbolScope,
};
use crate::{
@@ -41,7 +41,6 @@ fn to_obj_symbol(
arch: &dyn ObjArch,
obj_file: &File<'_>,
symbol: &Symbol<'_, '_>,
addend: i64,
split_meta: Option<&SplitMeta>,
) -> Result<ObjSymbol> {
let mut name = symbol.name().context("Failed to process symbol name")?;
@@ -65,10 +64,8 @@ fn to_obj_symbol(
if obj_file.format() == BinaryFormat::Elf && symbol.scope() == SymbolScope::Linkage {
flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::Hidden);
}
if arch
.ppc()
.and_then(|a| a.extab.as_ref())
.map_or(false, |e| e.contains_key(&symbol.index().0))
#[cfg(feature = "ppc")]
if arch.ppc().and_then(|a| a.extab.as_ref()).is_some_and(|e| e.contains_key(&symbol.index().0))
{
flags = ObjSymbolFlagSet(flags.0 | ObjSymbolFlags::HasExtra);
}
@@ -111,7 +108,7 @@ fn to_obj_symbol(
size_known: symbol.size() != 0,
kind,
flags,
addend,
orig_section_index: symbol.section_index().map(|i| i.0),
virtual_address,
original_index: Some(symbol.index().0),
bytes: bytes.to_vec(),
@@ -177,7 +174,7 @@ fn symbols_by_section(
continue;
}
}
result.push(to_obj_symbol(arch, obj_file, symbol, 0, split_meta)?);
result.push(to_obj_symbol(arch, obj_file, symbol, split_meta)?);
}
result.sort_by(|a, b| a.address.cmp(&b.address).then(a.size.cmp(&b.size)));
let mut iter = result.iter_mut().peekable();
@@ -217,7 +214,7 @@ fn symbols_by_section(
ObjSectionKind::Data | ObjSectionKind::Bss => ObjSymbolKind::Object,
},
flags: Default::default(),
addend: 0,
orig_section_index: Some(section.orig_index),
virtual_address: None,
original_index: None,
bytes: Vec::new(),
@@ -234,7 +231,7 @@ fn common_symbols(
obj_file
.symbols()
.filter(Symbol::is_common)
.map(|symbol| to_obj_symbol(arch, obj_file, &symbol, 0, split_meta))
.map(|symbol| to_obj_symbol(arch, obj_file, &symbol, split_meta))
.collect::<Result<Vec<ObjSymbol>>>()
}
@@ -245,10 +242,18 @@ fn best_symbol<'r, 'data, 'file>(
symbols: &'r [Symbol<'data, 'file>],
address: u64,
) -> Option<&'r Symbol<'data, 'file>> {
let closest_symbol_index = match symbols.binary_search_by_key(&address, |s| s.address()) {
let mut closest_symbol_index = match symbols.binary_search_by_key(&address, |s| s.address()) {
Ok(index) => Some(index),
Err(index) => index.checked_sub(1),
}?;
// The binary search may not find the first symbol at the address, so work backwards
let target_address = symbols[closest_symbol_index].address();
while let Some(prev_index) = closest_symbol_index.checked_sub(1) {
if symbols[prev_index].address() != target_address {
break;
}
closest_symbol_index = prev_index;
}
let mut best_symbol: Option<&'r Symbol<'data, 'file>> = None;
for symbol in symbols.iter().skip(closest_symbol_index) {
if symbol.address() > address {
@@ -276,24 +281,15 @@ fn best_symbol<'r, 'data, 'file>(
fn find_section_symbol(
arch: &dyn ObjArch,
obj_file: &File<'_>,
section: &Section,
section_symbols: &[Symbol<'_, '_>],
target: &Symbol<'_, '_>,
address: u64,
split_meta: Option<&SplitMeta>,
) -> Result<ObjSymbol> {
if let Some(symbol) = best_symbol(section_symbols, address) {
return to_obj_symbol(
arch,
obj_file,
symbol,
address as i64 - symbol.address() as i64,
split_meta,
);
return to_obj_symbol(arch, obj_file, symbol, split_meta);
}
// Fallback to section symbol
let section_index =
target.section_index().ok_or_else(|| anyhow::Error::msg("Unknown section index"))?;
let section = obj_file.section_by_index(section_index)?;
Ok(ObjSymbol {
name: section.name()?.to_string(),
demangled_name: None,
@@ -303,7 +299,7 @@ fn find_section_symbol(
size_known: false,
kind: ObjSymbolKind::Section,
flags: Default::default(),
addend: address as i64 - section.address() as i64,
orig_section_index: Some(section.index().0),
virtual_address: None,
original_index: None,
bytes: Vec::new(),
@@ -314,7 +310,7 @@ fn relocations_by_section(
arch: &dyn ObjArch,
obj_file: &File<'_>,
section: &ObjSection,
section_symbols: &[Symbol<'_, '_>],
section_symbols: &[Vec<Symbol<'_, '_>>],
split_meta: Option<&SplitMeta>,
) -> Result<Vec<ObjReloc>> {
let obj_section = obj_file.section_by_index(SectionIndex(section.orig_index))?;
@@ -336,40 +332,43 @@ fn relocations_by_section(
};
symbol
}
RelocationTarget::Absolute => {
log::warn!("Ignoring absolute relocation @ {}:{:#x}", section.name, address);
continue;
}
_ => bail!("Unhandled relocation target: {:?}", reloc.target()),
};
let flags = reloc.flags(); // TODO validate reloc here?
let target_section = match symbol.section() {
SymbolSection::Common => Some(".comm".to_string()),
SymbolSection::Section(idx) => {
obj_file.section_by_index(idx).and_then(|s| s.name().map(|s| s.to_string())).ok()
}
_ => None,
};
let addend = if reloc.has_implicit_addend() {
let mut addend = if reloc.has_implicit_addend() {
arch.implcit_addend(obj_file, section, address, &reloc)?
} else {
reloc.addend()
};
// println!("Reloc: {reloc:?}, symbol: {symbol:?}, addend: {addend:#x}");
let target = match symbol.kind() {
SymbolKind::Text | SymbolKind::Data | SymbolKind::Label | SymbolKind::Unknown => {
to_obj_symbol(arch, obj_file, &symbol, addend, split_meta)
to_obj_symbol(arch, obj_file, &symbol, split_meta)?
}
SymbolKind::Section => {
ensure!(addend >= 0, "Negative addend in reloc: {addend}");
find_section_symbol(
ensure!(addend >= 0, "Negative addend in section reloc: {addend}");
let section_index = symbol
.section_index()
.ok_or_else(|| anyhow!("Section symbol {symbol:?} has no section index"))?;
let section = obj_file.section_by_index(section_index)?;
let symbol = find_section_symbol(
arch,
obj_file,
section_symbols,
&symbol,
&section,
&section_symbols[section_index.0],
addend as u64,
split_meta,
)
)?;
// Adjust addend to be relative to the selected symbol
addend = (symbol.address - section.address()) as i64;
symbol
}
kind => Err(anyhow!("Unhandled relocation symbol type {kind:?}")),
}?;
relocations.push(ObjReloc { flags, address, target, target_section });
kind => bail!("Unhandled relocation symbol type {kind:?}"),
};
relocations.push(ObjReloc { flags, address, target, addend });
}
Ok(relocations)
}
@@ -434,9 +433,9 @@ fn line_info(obj_file: &File<'_>, sections: &mut [ObjSection], obj_data: &[u8])
let mut text_sections =
obj_file.sections().filter(|s| s.kind() == SectionKind::Text);
let section_index = text_sections.next().map(|s| s.index().0);
let mut lines = section_index.map(|index| {
&mut sections.iter_mut().find(|s| s.orig_index == index).unwrap().line_info
});
let mut lines = section_index
.and_then(|index| sections.iter_mut().find(|s| s.orig_index == index))
.map(|s| &mut s.line_info);
let mut rows = program.rows();
while let Some((_header, row)) = rows.next_row()? {
@@ -447,13 +446,9 @@ fn line_info(obj_file: &File<'_>, sections: &mut [ObjSection], obj_data: &[u8])
// The next row is the start of a new sequence, which means we must
// advance to the next .text section.
let section_index = text_sections.next().map(|s| s.index().0);
lines = section_index.map(|index| {
&mut sections
.iter_mut()
.find(|s| s.orig_index == index)
.unwrap()
.line_info
});
lines = section_index
.and_then(|index| sections.iter_mut().find(|s| s.orig_index == index))
.map(|s| &mut s.line_info);
}
}
}
@@ -591,7 +586,7 @@ fn update_combined_symbol(symbol: ObjSymbol, address_change: i64) -> Result<ObjS
size_known: symbol.size_known,
kind: symbol.kind,
flags: symbol.flags,
addend: symbol.addend,
orig_section_index: symbol.orig_section_index,
virtual_address: if let Some(virtual_address) = symbol.virtual_address {
Some((virtual_address as i64 + address_change).try_into()?)
} else {
@@ -617,8 +612,8 @@ fn combine_sections(section: ObjSection, combine: ObjSection) -> Result<ObjSecti
relocations.push(ObjReloc {
flags: reloc.flags,
address: (reloc.address as i64 + address_change).try_into()?,
target: reloc.target, // TODO: Should be updated?
target_section: reloc.target_section, // TODO: Same as above
target: reloc.target, // TODO: Should be updated?
addend: reloc.addend,
});
}
@@ -698,27 +693,38 @@ pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<ObjInfo> {
let obj_file = File::parse(data)?;
let arch = new_arch(&obj_file)?;
let split_meta = split_meta(&obj_file)?;
let mut sections = filter_sections(&obj_file, split_meta.as_ref())?;
let mut name_counts: HashMap<String, u32> = HashMap::new();
for section in &mut sections {
// Create sorted symbol list for each section
let mut section_symbols = Vec::with_capacity(obj_file.sections().count());
for section in obj_file.sections() {
let mut symbols = obj_file
.symbols()
.filter(|s| s.section_index() == Some(SectionIndex(section.orig_index)))
.filter(|s| s.section_index() == Some(section.index()))
.collect::<Vec<_>>();
symbols.sort_by_key(|s| s.address());
let section_index = section.index().0;
if section_index >= section_symbols.len() {
section_symbols.resize_with(section_index + 1, Vec::new);
}
section_symbols[section_index] = symbols;
}
let mut sections = filter_sections(&obj_file, split_meta.as_ref())?;
let mut section_name_counts: HashMap<String, u32> = HashMap::new();
for section in &mut sections {
section.symbols = symbols_by_section(
arch.as_ref(),
&obj_file,
section,
&symbols,
&section_symbols[section.orig_index],
split_meta.as_ref(),
&mut name_counts,
&mut section_name_counts,
)?;
section.relocations = relocations_by_section(
arch.as_ref(),
&obj_file,
section,
&symbols,
&section_symbols,
split_meta.as_ref(),
)?;
}

View File

@@ -25,7 +25,7 @@ wsl = []
[dependencies]
anyhow = "1.0"
bytes = "1.7"
bytes = "1.9"
cfg-if = "1.0"
const_format = "0.2"
cwdemangle = "1.0"
@@ -42,7 +42,7 @@ notify = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39db
objdiff-core = { path = "../objdiff-core", features = ["all"] }
open = "5.3"
png = "0.17"
pollster = "0.3"
pollster = "0.4"
regex = "1.11"
rfd = { version = "0.15" } #, default-features = false, features = ['xdg-portal']
rlwinmdec = "1.0"
@@ -51,7 +51,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
shell-escape = "0.1"
strum = { version = "0.26", features = ["derive"] }
tempfile = "3.13"
tempfile = "3.14"
time = { version = "0.3", features = ["formatting", "local-offset"] }
# Keep version in sync with egui
@@ -95,7 +95,7 @@ exec = "0.3"
# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tracing-subscriber = "0.3"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# web:
[target.'cfg(target_arch = "wasm32")'.dependencies]

View File

@@ -71,6 +71,7 @@ impl ScratchConfigV1 {
c_flags: self.c_flags,
ctx_path: self.ctx_path,
build_ctx: self.build_ctx.then_some(true),
preset_id: None,
}
}
}

View File

@@ -12,6 +12,21 @@ pub enum ProjectObjectNode {
Dir(String, Vec<ProjectObjectNode>),
}
fn join_single_dir_entries(nodes: &mut Vec<ProjectObjectNode>) {
for node in nodes {
if let ProjectObjectNode::Dir(my_name, my_nodes) = node {
join_single_dir_entries(my_nodes);
// If this directory consists of a single sub-directory...
if let [ProjectObjectNode::Dir(sub_name, sub_nodes)] = &mut my_nodes[..] {
// ... join the two names with a path separator and eliminate the layer
*my_name += "/";
*my_name += sub_name;
*my_nodes = std::mem::take(sub_nodes);
}
}
}
}
fn find_dir<'a>(
name: &str,
nodes: &'a mut Vec<ProjectObjectNode>,
@@ -60,6 +75,14 @@ fn build_nodes(
let filename = path.file_name().unwrap().to_str().unwrap().to_string();
out_nodes.push(ProjectObjectNode::Unit(filename, idx));
}
// Within the top-level module directories, join paths. Leave the
// top-level name intact though since it's the module name.
for node in &mut nodes {
if let ProjectObjectNode::Dir(_, sub_nodes) = node {
join_single_dir_entries(sub_nodes);
}
}
nodes
}

View File

@@ -23,6 +23,7 @@ pub struct CreateScratchConfig {
pub compiler_flags: String,
pub function_name: String,
pub target_obj: PathBuf,
pub preset_id: Option<u32>,
}
impl CreateScratchConfig {
@@ -45,6 +46,7 @@ impl CreateScratchConfig {
compiler_flags: scratch_config.c_flags.clone().unwrap_or_default(),
function_name,
target_obj: target_path.to_path_buf(),
preset_id: scratch_config.preset_id,
})
}
@@ -101,15 +103,18 @@ fn run_create_scratch(
let obj_path = project_dir.join(&config.target_obj);
let file = reqwest::blocking::multipart::Part::file(&obj_path)
.with_context(|| format!("Failed to open {}", obj_path.display()))?;
let form = reqwest::blocking::multipart::Form::new()
let mut form = reqwest::blocking::multipart::Form::new()
.text("compiler", config.compiler.clone())
.text("platform", config.platform.clone())
.text("compiler_flags", config.compiler_flags.clone())
.text("diff_label", config.function_name.clone())
.text("diff_flags", diff_flags)
.text("context", context.unwrap_or_default())
.text("source_code", "// Move related code from Context tab to here")
.part("target_obj", file);
.text("source_code", "// Move related code from Context tab to here");
if let Some(preset) = config.preset_id {
form = form.text("preset", preset.to_string());
}
form = form.part("target_obj", file);
let client = reqwest::blocking::Client::new();
let response = client
.post(formatcp!("{API_HOST}/api/scratch"))

View File

@@ -152,16 +152,14 @@ fn start_job(
let context = JobContext { status: status.clone(), egui: ctx.clone() };
let context_inner = JobContext { status: status.clone(), egui: ctx.clone() };
let (tx, rx) = std::sync::mpsc::channel();
let handle = std::thread::spawn(move || {
return match run(context_inner, rx) {
Ok(state) => state,
Err(e) => {
if let Ok(mut w) = status.write() {
w.error = Some(e);
}
JobResult::None
let handle = std::thread::spawn(move || match run(context_inner, rx) {
Ok(state) => state,
Err(e) => {
if let Ok(mut w) = status.write() {
w.error = Some(e);
}
};
JobResult::None
}
});
let id = JOB_ID.fetch_add(1, Ordering::Relaxed);
log::info!("Started job {}", id);

View File

@@ -205,7 +205,7 @@ pub const DEFAULT_COLOR_ROTATION: [Color32; 9] = [
Color32::from_rgb(255, 0, 0),
Color32::from_rgb(255, 255, 0),
Color32::from_rgb(255, 192, 203),
Color32::from_rgb(0, 0, 255),
Color32::from_rgb(128, 128, 255),
Color32::from_rgb(0, 255, 0),
Color32::from_rgb(213, 138, 138),
];

View File

@@ -213,8 +213,8 @@ pub fn data_diff_ui(
let right_ctx = SectionDiffContext::new(result.second_obj.as_ref(), section_name);
// If both sides are missing a symbol, switch to symbol diff view
if !right_ctx.map_or(false, |ctx| ctx.has_section())
&& !left_ctx.map_or(false, |ctx| ctx.has_section())
if !right_ctx.is_some_and(|ctx| ctx.has_section())
&& !left_ctx.is_some_and(|ctx| ctx.has_section())
{
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
}

View File

@@ -101,7 +101,7 @@ pub fn extab_diff_ui(
let right_diff_symbol = right_ctx.and_then(|ctx| {
ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol)
});
if left_diff_symbol.is_some() && right_ctx.map_or(false, |ctx| !ctx.has_symbol()) {
if left_diff_symbol.is_some() && right_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
let (right_section, right_symbol) =
right_ctx.unwrap().obj.section_symbol(left_diff_symbol.unwrap());
let symbol_ref = SymbolRefByName::new(right_symbol, right_section);
@@ -111,7 +111,7 @@ pub fn extab_diff_ui(
left_symbol: state.symbol_state.left_symbol.clone(),
right_symbol: Some(symbol_ref),
}));
} else if right_diff_symbol.is_some() && left_ctx.map_or(false, |ctx| !ctx.has_symbol()) {
} else if right_diff_symbol.is_some() && left_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
let (left_section, left_symbol) =
left_ctx.unwrap().obj.section_symbol(right_diff_symbol.unwrap());
let symbol_ref = SymbolRefByName::new(left_symbol, left_section);
@@ -124,8 +124,8 @@ pub fn extab_diff_ui(
}
// If both sides are missing a symbol, switch to symbol diff view
if right_ctx.map_or(false, |ctx| !ctx.has_symbol())
&& left_ctx.map_or(false, |ctx| !ctx.has_symbol())
if right_ctx.is_some_and(|ctx| !ctx.has_symbol())
&& left_ctx.is_some_and(|ctx| !ctx.has_symbol())
{
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
}
@@ -144,7 +144,7 @@ pub fn extab_diff_ui(
.add_enabled(
!state.scratch_running
&& state.scratch_available
&& left_ctx.map_or(false, |ctx| ctx.has_symbol()),
&& left_ctx.is_some_and(|ctx| ctx.has_symbol()),
egui::Button::new("📲 decomp.me"),
)
.on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)")

View File

@@ -3,7 +3,6 @@ use std::default::Default;
use egui::{text::LayoutJob, Id, Label, Response, RichText, Sense, Widget};
use egui_extras::TableRow;
use objdiff_core::{
arch::ObjArch,
diff::{
display::{display_diff, DiffText, HighlightKind},
ObjDiff, ObjInsDiff, ObjInsDiffKind,
@@ -77,7 +76,7 @@ impl FunctionViewState {
fn ins_hover_ui(
ui: &mut egui::Ui,
arch: &dyn ObjArch,
obj: &ObjInfo,
section: &ObjSection,
ins: &ObjIns,
symbol: &ObjSymbol,
@@ -120,10 +119,17 @@ fn ins_hover_ui(
}
if let Some(reloc) = &ins.reloc {
ui.label(format!("Relocation type: {}", arch.display_reloc(reloc.flags)));
ui.label(format!("Relocation type: {}", obj.arch.display_reloc(reloc.flags)));
ui.colored_label(appearance.highlight_color, format!("Name: {}", reloc.target.name));
if let Some(section) = &reloc.target_section {
ui.colored_label(appearance.highlight_color, format!("Section: {section}"));
if let Some(orig_section_index) = reloc.target.orig_section_index {
if let Some(section) =
obj.sections.iter().find(|s| s.orig_index == orig_section_index)
{
ui.colored_label(
appearance.highlight_color,
format!("Section: {}", section.name),
);
}
ui.colored_label(
appearance.highlight_color,
format!("Address: {:x}", reloc.target.address),
@@ -132,9 +138,10 @@ fn ins_hover_ui(
appearance.highlight_color,
format!("Size: {:x}", reloc.target.size),
);
if let Some(s) = arch
if let Some(s) = obj
.arch
.guess_data_type(ins)
.and_then(|ty| arch.display_data_type(ty, &reloc.target.bytes))
.and_then(|ty| obj.arch.display_data_type(ty, &reloc.target.bytes))
{
ui.colored_label(appearance.highlight_color, s);
}
@@ -142,6 +149,10 @@ fn ins_hover_ui(
ui.colored_label(appearance.highlight_color, "Extern".to_string());
}
}
if let Some(demangled) = rlwinmdec::decode(&ins.formatted) {
ui.colored_label(appearance.highlight_color, demangled.trim());
}
});
}
@@ -285,10 +296,14 @@ fn diff_text_ui(
base_color = appearance.diff_colors[diff.idx % appearance.diff_colors.len()]
}
}
DiffText::Symbol(sym) => {
DiffText::Symbol(sym, diff) => {
let name = sym.demangled_name.as_ref().unwrap_or(&sym.name);
label_text = name.clone();
base_color = appearance.emphasized_text_color;
if let Some(diff) = diff {
base_color = appearance.diff_colors[diff.idx % appearance.diff_colors.len()]
} else {
base_color = appearance.emphasized_text_color;
}
}
DiffText::Spacing(n) => {
ui.add_space(n as f32 * space_width);
@@ -370,7 +385,7 @@ fn asm_col_ui(
if let Some(ins) = &ins_diff.ins {
response.context_menu(|ui| ins_context_menu(ui, section, ins, symbol));
response.on_hover_ui_at_pointer(|ui| {
ins_hover_ui(ui, ctx.obj.arch.as_ref(), section, ins, symbol, appearance)
ins_hover_ui(ui, ctx.obj, section, ins, symbol, appearance)
})
} else {
response
@@ -629,7 +644,7 @@ pub fn function_diff_ui(
let right_diff_symbol = right_ctx.and_then(|ctx| {
ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol)
});
if left_diff_symbol.is_some() && right_ctx.map_or(false, |ctx| !ctx.has_symbol()) {
if left_diff_symbol.is_some() && right_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
let (right_section, right_symbol) =
right_ctx.unwrap().obj.section_symbol(left_diff_symbol.unwrap());
let symbol_ref = SymbolRefByName::new(right_symbol, right_section);
@@ -639,7 +654,7 @@ pub fn function_diff_ui(
left_symbol: state.symbol_state.left_symbol.clone(),
right_symbol: Some(symbol_ref),
}));
} else if right_diff_symbol.is_some() && left_ctx.map_or(false, |ctx| !ctx.has_symbol()) {
} else if right_diff_symbol.is_some() && left_ctx.is_some_and(|ctx| !ctx.has_symbol()) {
let (left_section, left_symbol) =
left_ctx.unwrap().obj.section_symbol(right_diff_symbol.unwrap());
let symbol_ref = SymbolRefByName::new(left_symbol, left_section);
@@ -652,8 +667,8 @@ pub fn function_diff_ui(
}
// If both sides are missing a symbol, switch to symbol diff view
if right_ctx.map_or(false, |ctx| !ctx.has_symbol())
&& left_ctx.map_or(false, |ctx| !ctx.has_symbol())
if right_ctx.is_some_and(|ctx| !ctx.has_symbol())
&& left_ctx.is_some_and(|ctx| !ctx.has_symbol())
{
return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff()));
}
@@ -672,7 +687,7 @@ pub fn function_diff_ui(
.add_enabled(
!state.scratch_running
&& state.scratch_available
&& left_ctx.map_or(false, |ctx| ctx.has_symbol()),
&& left_ctx.is_some_and(|ctx| ctx.has_symbol()),
egui::Button::new("📲 decomp.me"),
)
.on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)")
@@ -696,7 +711,7 @@ pub fn function_diff_ui(
.font(appearance.code_font.clone())
.color(appearance.highlight_color),
);
if right_ctx.map_or(false, |m| m.has_symbol())
if right_ctx.is_some_and(|m| m.has_symbol())
&& ui
.button("Change target")
.on_hover_text_at_pointer("Choose a different symbol to use as the target")
@@ -766,7 +781,7 @@ pub fn function_diff_ui(
.color(match_color_for_symbol(match_percent, appearance)),
);
}
if left_ctx.map_or(false, |m| m.has_symbol()) {
if left_ctx.is_some_and(|m| m.has_symbol()) {
ui.separator();
if ui
.button("Change base")

View File

@@ -19,7 +19,7 @@ pub fn rlwinm_decode_window(
if let Some(demangled) = rlwinmdec::decode(&state.text) {
ui.scope(|ui| {
ui.style_mut().override_text_style = Some(TextStyle::Monospace);
ui.colored_label(appearance.replace_color, &demangled);
ui.colored_label(appearance.replace_color, demangled.trim());
});
if ui.button("Copy").clicked() {
ui.output_mut(|output| output.copied_text = demangled);

View File

@@ -7,7 +7,9 @@ use egui::{
use objdiff_core::{
arch::ObjArch,
diff::{display::HighlightKind, ObjDiff, ObjSymbolDiff},
obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags, SymbolRef},
obj::{
ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags, SymbolRef, SECTION_COMMON,
},
};
use regex::{Regex, RegexBuilder};
@@ -651,11 +653,11 @@ pub fn symbol_list_ui(
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
// Skip sections with all symbols filtered out
if mapping.keys().any(|symbol_ref| symbol_ref.section_idx == usize::MAX) {
if mapping.keys().any(|symbol_ref| symbol_ref.section_idx == SECTION_COMMON) {
CollapsingHeader::new(".comm").default_open(true).show(ui, |ui| {
for (symbol_ref, symbol_diff) in mapping
.iter()
.filter(|(symbol_ref, _)| symbol_ref.section_idx == usize::MAX)
.filter(|(symbol_ref, _)| symbol_ref.section_idx == SECTION_COMMON)
{
let symbol = ctx.obj.section_symbol(*symbol_ref).1;
if let Some(result) = symbol_ui(