Compare commits

...

10 Commits

Author SHA1 Message Date
LagoLunatic f2a591356e Improve automatic symbol pairing for nameless literals (#247)
* Improve automatic symbol pairing for nameless literals

* Fix data reloc diffing when the reloc points to an in-function static symbol

* Only pair up literals that match perfectly

* Clippy

* Do two separate passes when pairing up literals

* Fix partially-matching literal pairups not working right

* Remove duplicate $ splitting code

* Implement $ splitting for section names too

* Minor cleanup
2025-08-30 23:07:43 -06:00
Luke Street 8d24ec6373 Detect x86 instruction size differences
Check raw code length when instructions have same disassembly
but different encodings. Marks as OpMismatch to indicate
encoding difference.

Fixes #242
Fixes #233
2025-08-30 22:57:39 -06:00
Luke Street 58430d947b ci: Remove quotes from CARGO_ZIGBUILD_ZIG_PATH 2025-08-30 15:49:14 -06:00
Luke Street 1533125f9d Resolve objdiff-wasm clippy warnings 2025-08-30 15:02:10 -06:00
Luke Street 5654060dc8 ci: Use uv tool instead of venv 2025-08-30 15:02:10 -06:00
Luke Street 84079c3934 objdiff-wasm build improvements 2025-08-30 15:02:10 -06:00
LagoLunatic 48804dc2e3 Merge pull request #245 from LagoLunatic/subi
PPC pooled data references: Add support for `subi`, `addis`, and `subis` instructions
2025-08-23 18:05:47 -04:00
LagoLunatic 8cfa8b7dab Update default watch patterns to include more extensions (#246) 2025-08-23 15:37:34 -06:00
LagoLunatic 93a4d7e55d PPC pooled data references: Add support for subi, addis, and subis instructions 2025-08-23 16:37:57 -04:00
iFarbod 7c424a7966 Ignore switchD_ labels generated by Ghidra (#241)
Seeing how commonly used boricj's delinker extension is, it makes sense for this one to be included by default, before #238 is considered and worked on.
2025-08-19 10:23:07 -06:00
21 changed files with 309 additions and 161 deletions
+13 -11
View File
@@ -36,9 +36,9 @@ jobs:
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Cargo check
run: cargo check --all-targets --all-features
run: cargo check --all-targets --all-features --workspace
- name: Cargo clippy
run: cargo clippy --all-targets --all-features
run: cargo clippy --all-targets --all-features --workspace
fmt:
name: Format
@@ -92,7 +92,7 @@ jobs:
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Cargo test
run: cargo test --release --features all
run: cargo test --release --all-features --workspace
build-cli:
name: Build objdiff-cli
@@ -146,13 +146,14 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install uv
if: matrix.build == 'zigbuild'
uses: astral-sh/setup-uv@v6
- name: Install cargo-zigbuild
if: matrix.build == 'zigbuild'
run: |
python3 -m venv .venv
. .venv/bin/activate
echo PATH=$PATH >> $GITHUB_ENV
pip install ziglang==0.13.0.post1 cargo-zigbuild==0.19.8
uv tool install cargo-zigbuild==0.20.1 --with-executables-from ziglang==0.15.1
echo "CARGO_ZIGBUILD_ZIG_PATH=$(uv tool dir)/cargo-zigbuild/bin/python-zig" >> $GITHUB_ENV
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
@@ -213,13 +214,14 @@ jobs:
sudo apt-get -y install ${{ matrix.packages }}
- name: Checkout
uses: actions/checkout@v4
- name: Install uv
if: matrix.build == 'zigbuild'
uses: astral-sh/setup-uv@v6
- name: Install cargo-zigbuild
if: matrix.build == 'zigbuild'
run: |
python3 -m venv .venv
. .venv/bin/activate
echo PATH=$PATH >> $GITHUB_ENV
pip install ziglang==0.13.0.post1 cargo-zigbuild==0.19.8
uv tool install cargo-zigbuild==0.20.1 --with-executables-from ziglang==0.15.1
echo "CARGO_ZIGBUILD_ZIG_PATH=$(uv tool dir)/cargo-zigbuild/bin/python-zig" >> $GITHUB_ENV
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
+1 -1
View File
@@ -24,7 +24,7 @@ repos:
description: Run cargo clippy on all project files.
language: system
entry: cargo
args: ["+nightly", "clippy", "--all-targets", "--all-features"]
args: ["+nightly", "clippy", "--all-targets", "--all-features", "--workspace"]
pass_filenames: false
- id: cargo-deny
name: cargo deny
Generated
+25 -25
View File
@@ -3436,7 +3436,7 @@ dependencies = [
[[package]]
name = "objdiff-cli"
version = "3.0.0"
version = "3.0.1"
dependencies = [
"anyhow",
"argp",
@@ -3459,7 +3459,7 @@ dependencies = [
[[package]]
name = "objdiff-core"
version = "3.0.0"
version = "3.0.1"
dependencies = [
"anyhow",
"arm-attr",
@@ -3514,7 +3514,7 @@ dependencies = [
[[package]]
name = "objdiff-gui"
version = "3.0.0"
version = "3.0.1"
dependencies = [
"anyhow",
"argp",
@@ -3551,7 +3551,7 @@ dependencies = [
[[package]]
name = "objdiff-wasm"
version = "3.0.0"
version = "3.0.1"
dependencies = [
"log",
"objdiff-core",
@@ -5863,9 +5863,9 @@ dependencies = [
[[package]]
name = "wasm-encoder"
version = "0.235.0"
version = "0.236.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3bc393c395cb621367ff02d854179882b9a351b4e0c93d1397e6090b53a5c2a"
checksum = "724fccfd4f3c24b7e589d333fc0429c68042897a7e8a5f8694f31792471841e7"
dependencies = [
"leb128fmt",
"wasmparser",
@@ -5873,9 +5873,9 @@ dependencies = [
[[package]]
name = "wasm-metadata"
version = "0.235.0"
version = "0.236.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b055604ba04189d54b8c0ab2c2fc98848f208e103882d5c0b984f045d5ea4d20"
checksum = "c909f94a49a8de3365f3c0344f064818f1e369ff1740c5b04f455f85d454768e"
dependencies = [
"anyhow",
"indexmap",
@@ -5898,9 +5898,9 @@ dependencies = [
[[package]]
name = "wasmparser"
version = "0.235.0"
version = "0.236.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "161296c618fa2d63f6ed5fffd1112937e803cb9ec71b32b01a76321555660917"
checksum = "a9b1e81f3eb254cf7404a82cee6926a4a3ccc5aad80cc3d43608a070c67aa1d7"
dependencies = [
"bitflags 2.9.1",
"hashbrown",
@@ -6726,19 +6726,19 @@ dependencies = [
[[package]]
name = "wit-bindgen"
version = "0.43.0"
version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a18712ff1ec5bd09da500fe1e91dec11256b310da0ff33f8b4ec92b927cf0c6"
checksum = "04bd9ed271234163b18c92783b0d406f08ca32c232e972f207a68c7b324c44bf"
dependencies = [
"wit-bindgen-rt 0.43.0",
"wit-bindgen-rt 0.44.0",
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen-core"
version = "0.43.0"
version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c53468e077362201de11999c85c07c36e12048a990a3e0d69da2bd61da355d0"
checksum = "b4103c7a3e178b75cd8b0b574fa199ed015e8399c9859b003865cc28834b474b"
dependencies = [
"anyhow",
"heck",
@@ -6756,18 +6756,18 @@ dependencies = [
[[package]]
name = "wit-bindgen-rt"
version = "0.43.0"
version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fd734226eac1fd7c450956964e3a9094c9cee65e9dafdf126feef8c0096db65"
checksum = "653c85dd7aee6fe6f4bded0d242406deadae9819029ce6f7d258c920c384358a"
dependencies = [
"bitflags 2.9.1",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.43.0"
version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "531ebfcec48e56473805285febdb450e270fa75b2dacb92816861d0473b4c15f"
checksum = "95d164b3b6fbd2b0dd8b639b1012110c0bc256519a0a6def410d4020fa8ae106"
dependencies = [
"anyhow",
"heck",
@@ -6781,9 +6781,9 @@ dependencies = [
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.43.0"
version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7852bf8a9d1ea80884d26b864ddebd7b0c7636697c6ca10f4c6c93945e023966"
checksum = "2c9100a5e1ac85e526dcd4ef49c3ff7689e026fa5e56e2a2047fd377fc682e02"
dependencies = [
"anyhow",
"prettyplease",
@@ -6796,9 +6796,9 @@ dependencies = [
[[package]]
name = "wit-component"
version = "0.235.0"
version = "0.236.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64a57a11109cc553396f89f3a38a158a97d0b1adaec113bd73e0f64d30fb601f"
checksum = "3622959ed7ed6341c38e5aa35af243632534b0a36226852faa802939ce11e00f"
dependencies = [
"anyhow",
"bitflags 2.9.1",
@@ -6840,9 +6840,9 @@ dependencies = [
[[package]]
name = "wit-parser"
version = "0.235.0"
version = "0.236.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a1f95a87d03a33e259af286b857a95911eb46236a0f726cbaec1227b3dfc67a"
checksum = "16e4833a20cd6e85d6abfea0e63a399472d6f88c6262957c17f546879a80ba15"
dependencies = [
"anyhow",
"id-arena",
+17 -7
View File
@@ -5,18 +5,28 @@ members = [
"objdiff-gui",
"objdiff-wasm",
]
default-members = [
"objdiff-cli",
"objdiff-core",
"objdiff-gui",
# Exclude objdiff-wasm by default
]
resolver = "3"
[workspace.package]
version = "3.0.1"
authors = ["Luke Street <luke@street.dev>"]
edition = "2024"
license = "MIT OR Apache-2.0"
repository = "https://github.com/encounter/objdiff"
rust-version = "1.88"
[profile.release-lto]
inherits = "release"
lto = "fat"
strip = "debuginfo"
codegen-units = 1
[workspace.package]
version = "3.0.0"
authors = ["Luke Street <luke@street.dev>"]
edition = "2024"
license = "MIT OR Apache-2.0"
repository = "https://github.com/encounter/objdiff"
rust-version = "1.88"
[profile.release-min]
inherits = "release-lto"
opt-level = "z"
+7 -1
View File
@@ -90,17 +90,23 @@ file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it
"build_base": true,
"watch_patterns": [
"*.c",
"*.cc",
"*.cp",
"*.cpp",
"*.cxx",
"*.c++",
"*.h",
"*.hh",
"*.hp",
"*.hpp",
"*.hxx",
"*.h++",
"*.pch",
"*.pch++",
"*.inc",
"*.s",
"*.S",
"*.asm",
"*.inc",
"*.py",
"*.yml",
"*.txt",
+7 -1
View File
@@ -57,17 +57,23 @@
},
"default": [
"*.c",
"*.cc",
"*.cp",
"*.cpp",
"*.cxx",
"*.c++",
"*.h",
"*.hh",
"*.hp",
"*.hpp",
"*.hxx",
"*.h++",
"*.pch",
"*.pch++",
"*.inc",
"*.s",
"*.S",
"*.asm",
"*.inc",
"*.py",
"*.yml",
"*.txt",
+2 -1
View File
@@ -74,6 +74,7 @@ ignore = [
#"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-0436", reason = "Unmaintained paste crate is an indirect dependency" },
{ id = "RUSTSEC-2025-0052", reason = "Unmaintained async-std crate is an 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.
@@ -241,8 +242,8 @@ allow-git = []
[sources.allow-org]
# github.com organizations to allow git sources for
github = [
"enarx", # flagset
"encounter",
"gimli-rs", # gimli
]
# gitlab.com organizations to allow git sources for
gitlab = []
+52 -14
View File
@@ -675,7 +675,7 @@ fn make_symbol_ref(symbol: &object::Symbol) -> Result<ExtabSymbolRef> {
#[derive(Debug)]
struct PoolReference {
addr_src_gpr: powerpc::GPR,
addr_offset: i16,
addr_offset: i64,
addr_dst_gpr: Option<powerpc::GPR>,
}
@@ -683,16 +683,20 @@ struct PoolReference {
// If so, return information pertaining to where the instruction is getting that address from and
// what it's doing with the address (e.g. copying it into another register, adding an offset, etc).
fn get_pool_reference_for_inst(
opcode: powerpc::Opcode,
ins: powerpc::Ins,
simplified: &powerpc::ParsedIns,
) -> Option<PoolReference> {
use powerpc::{Argument, Opcode};
let args = &simplified.args;
if flow_analysis::guess_data_type_from_load_store_inst_op(opcode).is_some() {
if flow_analysis::guess_data_type_from_load_store_inst_op(ins.op).is_some() {
match (args[1], args[2]) {
(Argument::Offset(offset), Argument::GPR(addr_src_gpr)) => {
// e.g. lwz. Immediate offset.
Some(PoolReference { addr_src_gpr, addr_offset: offset.0, addr_dst_gpr: None })
Some(PoolReference {
addr_src_gpr,
addr_offset: offset.0 as i64,
addr_dst_gpr: None,
})
}
(Argument::GPR(addr_src_gpr), Argument::GPR(_offset_gpr)) => {
// e.g. lwzx. The offset is in a register and was likely calculated from an index.
@@ -712,17 +716,51 @@ fn get_pool_reference_for_inst(
// If either of these match, we also want to return the destination register that the
// address is being copied into so that we can detect any future references to that new
// register as well.
match (opcode, args[0], args[1], args[2]) {
match (ins.op, args[0], args[1], args[2]) {
(
// `addi` or `subi`
Opcode::Addi,
Argument::GPR(addr_dst_gpr),
Argument::GPR(addr_src_gpr),
Argument::Simm(simm),
) => Some(PoolReference {
addr_src_gpr,
addr_offset: simm.0,
addr_dst_gpr: Some(addr_dst_gpr),
}),
) => {
let offset = if simplified.mnemonic == "addi" { simm.0 } else { -simm.0 };
Some(PoolReference {
addr_src_gpr,
addr_offset: offset as i64,
addr_dst_gpr: Some(addr_dst_gpr),
})
}
(
// `addis`
Opcode::Addis,
Argument::GPR(addr_dst_gpr),
Argument::GPR(addr_src_gpr),
Argument::Uimm(uimm), // Note: `addis` uses UIMM, unlike `addi`, `subi`, and `subis`
) => {
assert_eq!(simplified.mnemonic, "addis");
let offset = (uimm.0 as i64) << 16;
Some(PoolReference {
addr_src_gpr,
addr_offset: offset,
addr_dst_gpr: Some(addr_dst_gpr),
})
}
(
// `subis`
Opcode::Addis,
Argument::GPR(addr_dst_gpr),
Argument::GPR(addr_src_gpr),
Argument::Simm(simm),
) => {
assert_eq!(simplified.mnemonic, "subis");
let offset = (simm.0 as i64) << 16;
Some(PoolReference {
addr_src_gpr,
addr_offset: offset,
addr_dst_gpr: Some(addr_dst_gpr),
})
}
(
// `mr` or `mr.`
Opcode::Or,
@@ -777,13 +815,13 @@ fn clear_overwritten_gprs(ins: powerpc::Ins, gpr_pool_relocs: &mut BTreeMap<u8,
// Also, if this instruction is accessing the middle of a symbol instead of the start, we add an
// addend to indicate that.
fn make_fake_pool_reloc(
offset: i16,
offset: i64,
cur_addr: u32,
pool_reloc: &Relocation,
symbols: &[Symbol],
) -> Option<Relocation> {
let pool_reloc = resolve_relocation(symbols, pool_reloc);
let offset_from_pool = pool_reloc.relocation.addend + offset as i64;
let offset_from_pool = pool_reloc.relocation.addend + offset;
let target_address = pool_reloc.symbol.address.checked_add_signed(offset_from_pool)?;
let target_symbol;
let addend;
@@ -946,7 +984,7 @@ fn generate_fake_pool_relocations_for_function(
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
}
}
} else if let Some(pool_ref) = get_pool_reference_for_inst(ins.op, &simplified) {
} else if let Some(pool_ref) = get_pool_reference_for_inst(ins, &simplified) {
// This instruction doesn't have a real relocation, so it may be a reference to one of
// the already-loaded pools.
if let Some(pool_reloc) = gpr_pool_relocs.get(&pool_ref.addr_src_gpr.0) {
@@ -965,7 +1003,7 @@ fn generate_fake_pool_relocations_for_function(
// with the offset within the .data section of an array variable into r21.
// Then the body of the loop will `lwzx` one of the array elements from r21.
let mut new_reloc = pool_reloc.clone();
new_reloc.addend += pool_ref.addr_offset as i64;
new_reloc.addend += pool_ref.addr_offset;
gpr_pool_relocs.insert(addr_dst_gpr.0, new_reloc);
} else {
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
+3 -2
View File
@@ -204,8 +204,9 @@ pub struct ScratchConfig {
pub const CONFIG_FILENAMES: [&str; 3] = ["objdiff.json", "objdiff.yml", "objdiff.yaml"];
pub const DEFAULT_WATCH_PATTERNS: &[&str] = &[
"*.c", "*.cp", "*.cpp", "*.cxx", "*.h", "*.hp", "*.hpp", "*.hxx", "*.s", "*.S", "*.asm",
"*.inc", "*.py", "*.yml", "*.txt", "*.json",
"*.c", "*.cc", "*.cp", "*.cpp", "*.cxx", "*.c++", "*.h", "*.hh", "*.hp", "*.hpp", "*.hxx",
"*.h++", "*.pch", "*.pch++", "*.inc", "*.s", "*.S", "*.asm", "*.py", "*.yml", "*.txt",
"*.json",
];
pub const DEFAULT_IGNORE_PATTERNS: &[&str] = &["build/**/*"];
+8
View File
@@ -496,6 +496,14 @@ fn diff_instruction(
result.right_args_diff.push(InstructionArgDiffIndex::new(b_diff));
}
}
if result.kind == InstructionDiffKind::None
&& left_resolved.code.len() != right_resolved.code.len()
{
// If everything else matches but the raw code length differs (e.g. x86 instructions
// with same disassembly but different encoding), mark as op mismatch
result.kind = InstructionDiffKind::OpMismatch;
state.diff_score += PENALTY_REG_DIFF;
}
return Ok(result);
}
+16 -2
View File
@@ -35,6 +35,20 @@ pub fn diff_bss_symbol(
))
}
pub fn symbol_name_matches(left_name: &str, right_name: &str) -> bool {
// Match Metrowerks symbol$1234 against symbol$2345
if let Some((prefix, suffix)) = left_name.split_once('$') {
if !suffix.chars().all(char::is_numeric) {
return false;
}
right_name
.split_once('$')
.is_some_and(|(p, s)| p == prefix && s.chars().all(char::is_numeric))
} else {
left_name == right_name
}
}
fn reloc_eq(
left_obj: &Object,
right_obj: &Object,
@@ -45,8 +59,8 @@ fn reloc_eq(
return false;
}
let symbol_name_addend_matches =
left.symbol.name == right.symbol.name && left.relocation.addend == right.relocation.addend;
let symbol_name_addend_matches = symbol_name_matches(&left.symbol.name, &right.symbol.name)
&& left.relocation.addend == right.relocation.addend;
match (left.symbol.section, right.symbol.section) {
(Some(sl), Some(sr)) => {
// Match if section and name+addend or address match
+127 -69
View File
@@ -13,7 +13,7 @@ use crate::{
code::{diff_code, no_diff_code},
data::{
diff_bss_section, diff_bss_symbol, diff_data_section, diff_data_symbol,
diff_generic_section, no_diff_bss_section, no_diff_data_section,
diff_generic_section, no_diff_bss_section, no_diff_data_section, symbol_name_matches,
},
},
obj::{InstructionRef, Object, Relocation, SectionKind, Symbol, SymbolFlag},
@@ -575,47 +575,60 @@ fn matching_symbols(
&mut matches,
)?;
}
for (symbol_idx, symbol) in left.symbols.iter().enumerate() {
if symbol.size == 0 || symbol.flags.contains(SymbolFlag::Ignored) {
continue;
}
let section_kind = symbol_section_kind(left, symbol);
if section_kind == SectionKind::Unknown {
continue;
}
if left_used.contains(&symbol_idx) {
continue;
}
let symbol_match = SymbolMatch {
left: Some(symbol_idx),
right: find_symbol(right, left, symbol, Some(&right_used)),
prev: find_symbol(prev, left, symbol, None),
section_kind,
};
matches.push(symbol_match);
if let Some(right) = symbol_match.right {
right_used.insert(right);
// Do two passes for nameless literals. The first only pairs up perfect matches to ensure
// those are correct first, while the second pass catches near matches.
for fuzzy_literals in [false, true] {
for (symbol_idx, symbol) in left.symbols.iter().enumerate() {
if symbol.size == 0 || symbol.flags.contains(SymbolFlag::Ignored) {
continue;
}
let section_kind = symbol_section_kind(left, symbol);
if section_kind == SectionKind::Unknown {
continue;
}
if left_used.contains(&symbol_idx) {
continue;
}
let symbol_match = SymbolMatch {
left: Some(symbol_idx),
right: find_symbol(right, left, symbol_idx, Some(&right_used), fuzzy_literals),
prev: find_symbol(prev, left, symbol_idx, None, fuzzy_literals),
section_kind,
};
matches.push(symbol_match);
if let Some(right) = symbol_match.right {
left_used.insert(symbol_idx);
right_used.insert(right);
}
}
}
}
if let Some(right) = right {
for (symbol_idx, symbol) in right.symbols.iter().enumerate() {
if symbol.size == 0 || symbol.flags.contains(SymbolFlag::Ignored) {
continue;
// Do two passes for nameless literals. The first only pairs up perfect matches to ensure
// those are correct first, while the second pass catches near matches.
for fuzzy_literals in [false, true] {
for (symbol_idx, symbol) in right.symbols.iter().enumerate() {
if symbol.size == 0 || symbol.flags.contains(SymbolFlag::Ignored) {
continue;
}
let section_kind = symbol_section_kind(right, symbol);
if section_kind == SectionKind::Unknown {
continue;
}
if right_used.contains(&symbol_idx) {
continue;
}
let symbol_match = SymbolMatch {
left: None,
right: Some(symbol_idx),
prev: find_symbol(prev, right, symbol_idx, None, fuzzy_literals),
section_kind,
};
matches.push(symbol_match);
if symbol_match.prev.is_some() {
right_used.insert(symbol_idx);
}
}
let section_kind = symbol_section_kind(right, symbol);
if section_kind == SectionKind::Unknown {
continue;
}
if right_used.contains(&symbol_idx) {
continue;
}
matches.push(SymbolMatch {
left: None,
right: Some(symbol_idx),
prev: find_symbol(prev, right, symbol, None),
section_kind,
});
}
}
Ok(matches)
@@ -637,7 +650,10 @@ where
fn symbol_section<'obj>(obj: &'obj Object, symbol: &Symbol) -> Option<(&'obj str, SectionKind)> {
if let Some(section) = symbol.section.and_then(|section_idx| obj.sections.get(section_idx)) {
Some((section.name.as_str(), section.kind))
// Match x86 .rdata$r against .rdata$rs
let section_name =
section.name.split_once('$').map_or(section.name.as_str(), |(prefix, _)| prefix);
Some((section_name, section.kind))
} else if symbol.flags.contains(SymbolFlag::Common) {
Some((".comm", SectionKind::Common))
} else {
@@ -653,52 +669,94 @@ fn symbol_section_kind(obj: &Object, symbol: &Symbol) -> SectionKind {
}
}
/// Check if a symbol is a compiler-generated literal like @1234.
fn is_symbol_compiler_generated_literal(symbol: &Symbol) -> bool {
if !symbol.name.starts_with('@') {
return false;
}
if !symbol.name[1..].chars().all(char::is_numeric) {
// Exclude @stringBase0, @GUARD@, etc.
return false;
}
true
}
fn find_symbol(
obj: Option<&Object>,
in_obj: &Object,
in_symbol: &Symbol,
in_symbol_idx: usize,
used: Option<&BTreeSet<usize>>,
fuzzy_literals: bool,
) -> Option<usize> {
let in_symbol = &in_obj.symbols[in_symbol_idx];
let obj = obj?;
let (section_name, section_kind) = symbol_section(in_obj, in_symbol)?;
// Match compiler-generated symbols against each other (e.g. @251 -> @60)
// If they are in the same section and have the same value
if is_symbol_compiler_generated_literal(in_symbol)
&& matches!(section_kind, SectionKind::Data | SectionKind::Bss)
{
let mut closest_match_symbol_idx = None;
let mut closest_match_percent = 0.0;
for (symbol_idx, symbol) in unmatched_symbols(obj, used) {
let Some(section_index) = symbol.section else {
continue;
};
if obj.sections[section_index].name != section_name {
continue;
}
if !is_symbol_compiler_generated_literal(symbol) {
continue;
}
match section_kind {
SectionKind::Data => {
// For data, pick the first symbol with exactly matching bytes and relocations.
// If no symbols match exactly, and `fuzzy_literals` is true, pick the closest
// plausible match instead.
if let Ok((left_diff, _right_diff)) =
diff_data_symbol(in_obj, obj, in_symbol_idx, symbol_idx)
&& let Some(match_percent) = left_diff.match_percent
&& (match_percent == 100.0
|| (fuzzy_literals
&& match_percent >= 50.0
&& match_percent > closest_match_percent))
{
closest_match_symbol_idx = Some(symbol_idx);
closest_match_percent = match_percent;
if match_percent == 100.0 {
break;
}
}
}
SectionKind::Bss => {
// For BSS, pick the first symbol that has the exact matching size.
if in_symbol.size == symbol.size {
closest_match_symbol_idx = Some(symbol_idx);
break;
}
}
_ => unreachable!(),
}
}
return closest_match_symbol_idx;
}
// Try to find an exact name match
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|(_, symbol)| {
symbol.name == in_symbol.name && symbol_section_kind(obj, symbol) == section_kind
}) {
return Some(symbol_idx);
}
// Match compiler-generated symbols against each other (e.g. @251 -> @60)
// If they are at the same address in the same section
if in_symbol.name.starts_with('@')
&& matches!(section_kind, SectionKind::Data | SectionKind::Bss)
&& let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|(_, symbol)| {
let Some(section_index) = symbol.section else {
return false;
};
symbol.name.starts_with('@')
&& symbol.address == in_symbol.address
&& obj.sections[section_index].name == section_name
})
{
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|&(_, symbol)| {
symbol_name_matches(&in_symbol.name, &symbol.name)
&& symbol_section_kind(obj, symbol) == section_kind
&& symbol_section(obj, symbol).is_some_and(|(name, _)| name == section_name)
}) {
return Some(symbol_idx);
}
// Match Metrowerks symbol$1234 against symbol$2345
if let Some((prefix, suffix)) = in_symbol.name.split_once('$') {
if !suffix.chars().all(char::is_numeric) {
return None;
}
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|&(_, symbol)| {
if let Some((p, s)) = symbol.name.split_once('$') {
prefix == p
&& s.chars().all(char::is_numeric)
&& symbol_section_kind(obj, symbol) == section_kind
} else {
false
}
}) {
return Some(symbol_idx);
}
}
None
}
+7 -8
View File
@@ -170,13 +170,6 @@ pub enum JobResult {
CreateScratch(Option<Box<CreateScratchResult>>),
}
fn should_cancel(rx: &Receiver<()>) -> bool {
match rx.try_recv() {
Ok(_) | Err(TryRecvError::Disconnected) => true,
Err(_) => false,
}
}
fn start_job(
waker: Waker,
title: &str,
@@ -203,7 +196,6 @@ fn start_job(
}
});
let id = JOB_ID.fetch_add(1, Ordering::Relaxed);
// log::info!("Started job {}", id); TODO
JobState { id, kind, handle: Some(handle), context, cancel: tx }
}
@@ -228,3 +220,10 @@ fn update_status(
context.waker.wake_by_ref();
Ok(())
}
fn should_cancel(rx: &Receiver<()>) -> bool {
match rx.try_recv() {
Ok(_) | Err(TryRecvError::Disconnected) => true,
Err(_) => false,
}
}
+1 -1
View File
@@ -130,7 +130,7 @@ fn map_symbols(
/// When inferring a symbol's size, we ignore symbols that start with specific prefixes. They are
/// usually emitted as branch targets and do not represent the start of a function or object.
fn is_local_label(symbol: &Symbol) -> bool {
const LABEL_PREFIXES: &[&str] = &[".L", "LAB_"];
const LABEL_PREFIXES: &[&str] = &[".L", "LAB_", "switchD_"];
symbol.size == 0
&& symbol.flags.contains(SymbolFlag::Local)
&& LABEL_PREFIXES.iter().any(|p| symbol.name.starts_with(p))
+6
View File
@@ -0,0 +1,6 @@
[build]
target = "wasm32-wasip2"
[unstable]
build-std = ["panic_abort", "core", "alloc"]
build-std-features = ["compiler-builtins-mem"]
+2 -4
View File
@@ -17,12 +17,13 @@ build = "build.rs"
crate-type = ["cdylib"]
[features]
default = ["std"]
default = []
std = ["objdiff-core/std"]
[dependencies]
log = { version = "0.4", default-features = false }
regex = { version = "1.11", default-features = false, features = ["unicode-case"] }
wit-bindgen = { version = "0.44", default-features = false, features = ["macros"] }
xxhash-rust = { version = "0.8", default-features = false, features = ["xxh3"] }
[dependencies.objdiff-core]
@@ -33,8 +34,5 @@ features = ["arm", "arm64", "mips", "ppc", "superh", "x86", "dwarf"]
[target.'cfg(target_family = "wasm")'.dependencies]
talc = { version = "4.4", default-features = false, features = ["lock_api"] }
[target.'cfg(target_os = "wasi")'.dependencies]
wit-bindgen = { version = "0.43", default-features = false, features = ["macros"] }
[build-dependencies]
wit-deps = "0.5"
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "objdiff-wasm",
"version": "3.0.0",
"version": "3.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "objdiff-wasm",
"version": "3.0.0",
"version": "3.0.1",
"license": "MIT OR Apache-2.0",
"devDependencies": {
"@biomejs/biome": "^1.9.3",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "objdiff-wasm",
"version": "3.0.0",
"version": "3.0.1",
"description": "A local diffing tool for decompilation projects.",
"author": {
"name": "Luke Street",
@@ -19,8 +19,8 @@
"types": "dist/objdiff.d.ts",
"scripts": {
"build": "npm run build:wasm && npm run build:transpile && npm run build:lib",
"build:wasm": "cargo +nightly -Zbuild-std=panic_abort,core,alloc -Zbuild-std-features=compiler-builtins-mem build --target wasm32-wasip2 --release --no-default-features",
"build:transpile": "jco transpile ../target/wasm32-wasip2/release/objdiff_wasm.wasm --no-nodejs-compat --no-wasi-shim --no-namespaced-exports --map wasi:logging/logging=./wasi-logging.js --optimize -o pkg --name objdiff",
"build:wasm": "cargo build --profile release-min --no-default-features",
"build:transpile": "jco transpile ../target/wasm32-wasip2/release-min/objdiff_wasm.wasm --no-nodejs-compat --no-wasi-shim --no-namespaced-exports --map wasi:logging/logging=./wasi-logging.js --optimize -o pkg --name objdiff",
"build:lib": "rslib build"
},
"devDependencies": {
+4
View File
@@ -0,0 +1,4 @@
[toolchain]
channel = "nightly"
components = ["rust-src"]
targets = ["wasm32-wasip2"]
+6 -7
View File
@@ -1,3 +1,4 @@
#![allow(clippy::derivable_impls)]
use alloc::{
format,
rc::{Rc, Weak},
@@ -223,7 +224,7 @@ impl GuestDisplay for Component {
let symbol_display = from_symbol_ref(symbol_ref);
diff::display::symbol_context(obj, symbol_display.symbol as usize)
.into_iter()
.map(|item| ContextItem::from(item))
.map(ContextItem::from)
.collect()
}
@@ -235,7 +236,7 @@ impl GuestDisplay for Component {
let symbol_display = from_symbol_ref(symbol_ref);
diff::display::symbol_hover(obj, symbol_display.symbol as usize, addend, override_color)
.into_iter()
.map(|item| HoverItem::from(item))
.map(HoverItem::from)
.collect()
}
@@ -282,7 +283,7 @@ impl GuestDisplay for Component {
};
diff::display::instruction_context(obj, resolved, &ins)
.into_iter()
.map(|item| ContextItem::from(item))
.map(ContextItem::from)
.collect()
}
@@ -331,7 +332,7 @@ impl GuestDisplay for Component {
};
diff::display::instruction_hover(obj, resolved, &ins)
.into_iter()
.map(|item| HoverItem::from(item))
.map(HoverItem::from)
.collect()
}
}
@@ -527,9 +528,7 @@ impl GuestObjectDiff for ResourceObjectDiff {
fn get_symbol(&self, symbol_ref: SymbolRef) -> Option<SymbolInfo> {
let obj = self.0.as_ref();
let symbol_display = from_symbol_ref(symbol_ref);
let Some(symbol) = obj.symbols.get(symbol_display.symbol) else {
return None;
};
let symbol = obj.symbols.get(symbol_display.symbol)?;
Some(SymbolInfo {
id: to_symbol_ref(symbol_display),
name: symbol.name.clone(),

Some files were not shown because too many files have changed in this diff Show More