Compare commits

...

17 Commits

Author SHA1 Message Date
Luke Street 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
Luke Street 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
Luke Street 72ea1c8911 ci: Use rust-lld on Windows 2024-10-12 18:57:49 -06:00
Luke Street d4a540857d ci: Add Rust workspace cache 2024-10-12 18:41:42 -06:00
Luke Street 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
Luke Street 83de98b5ee Version v2.3.1 2024-10-10 22:58:33 -06:00
Luke Street c1ba4e91d1 ci: Setup python venv for cargo-zigbuild 2024-10-10 22:39:31 -06:00
Luke Street 575900024d Avoid resetting diff state on unit config reload 2024-10-10 22:31:04 -06:00
Luke Street cbe299e859 Fix logic issue with 0-sized symbols
Fixes #119
2024-10-10 22:20:48 -06:00
Luke Street 741d93e211 Add symbol mapping feature (#118)
This allows users to "map" (or "link") symbols with different names so that they can be compared without having to update either the target or base objects. Symbol mappings are persisted in objdiff.json, so generators will need to ensure that they're preserved when updating. (Example: https://github.com/encounter/dtk-template/commit/d1334bb79e71af1a7b3b090bffda4adc483f722c)

Resolves #117
2024-10-09 21:44:18 -06:00
Luke Street 603dbd6882 Round match percent down before display
Ensures that 100% isn't displayed until it's a
perfect match.
2024-10-07 20:17:56 -06:00
Luke Street 6fb0a63de2 Click on empty space in row to clear highlight
Resolves #116
2024-10-07 19:53:16 -06:00
Luke Street ab2e84a2c6 Deprioritize generated GCC symbols in find_section_symbol
Resolves #115
2024-10-07 19:49:52 -06:00
Luke Street 9596051cb4 Allow collapsing sidebar in symbols view 2024-10-07 19:46:16 -06:00
36 changed files with 2644 additions and 1137 deletions
+5
View File
@@ -0,0 +1,5 @@
[target.x86_64-pc-windows-msvc]
linker = "rust-lld"
[target.aarch64-pc-windows-msvc]
linker = "rust-lld"
+17 -1
View File
@@ -30,6 +30,8 @@ jobs:
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
with: with:
components: clippy components: clippy
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Cargo check - name: Cargo check
run: cargo check --all-features --all-targets run: cargo check --all-features --all-targets
- name: Cargo clippy - name: Cargo clippy
@@ -85,6 +87,8 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Rust toolchain - name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Cargo test - name: Cargo test
run: cargo test --release --all-features run: cargo test --release --all-features
@@ -142,11 +146,19 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install cargo-zigbuild - name: Install cargo-zigbuild
if: matrix.build == 'zigbuild' if: matrix.build == 'zigbuild'
run: pip install ziglang==0.13.0 cargo-zigbuild==0.19.1 run: |
python3 -m venv .venv
. .venv/bin/activate
echo PATH=$PATH >> $GITHUB_ENV
pip install ziglang==0.13.0 cargo-zigbuild==0.19.1
- name: Setup Rust toolchain - name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
with: with:
targets: ${{ matrix.target }} targets: ${{ matrix.target }}
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
- name: Cargo build - name: Cargo build
run: > run: >
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }} cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
@@ -198,6 +210,10 @@ jobs:
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
with: with:
targets: ${{ matrix.target }} targets: ${{ matrix.target }}
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
- name: Cargo build - name: Cargo build
run: > run: >
cargo build --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }} cargo build --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
-4
View File
@@ -3,10 +3,6 @@ target/
**/*.rs.bk **/*.rs.bk
generated/ generated/
# cargo-mobile
.cargo/
/gen
# macOS # macOS
.DS_Store .DS_Store
Generated
+25 -15
View File
@@ -395,6 +395,15 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bimap"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "bit-set" name = "bit-set"
version = "0.6.0" version = "0.6.0"
@@ -1517,15 +1526,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.30" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.30" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]] [[package]]
name = "futures-lite" name = "futures-lite"
@@ -1542,9 +1551,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.30" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1553,21 +1562,21 @@ dependencies = [
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.30" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.30" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.30" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-io", "futures-io",
@@ -2852,7 +2861,7 @@ dependencies = [
[[package]] [[package]]
name = "objdiff-cli" name = "objdiff-cli"
version = "2.2.2" version = "2.3.3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"argp", "argp",
@@ -2874,10 +2883,11 @@ dependencies = [
[[package]] [[package]]
name = "objdiff-core" name = "objdiff-core"
version = "2.2.2" version = "2.3.3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arm-attr", "arm-attr",
"bimap",
"byteorder", "byteorder",
"console_error_panic_hook", "console_error_panic_hook",
"console_log", "console_log",
@@ -2913,7 +2923,7 @@ dependencies = [
[[package]] [[package]]
name = "objdiff-gui" name = "objdiff-gui"
version = "2.2.2" version = "2.3.3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
+1 -1
View File
@@ -13,7 +13,7 @@ strip = "debuginfo"
codegen-units = 1 codegen-units = 1
[workspace.package] [workspace.package]
version = "2.2.2" version = "2.3.3"
authors = ["Luke Street <luke@street.dev>"] authors = ["Luke Street <luke@street.dev>"]
edition = "2021" edition = "2021"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
+7
View File
@@ -133,6 +133,13 @@
}, },
"metadata": { "metadata": {
"ref": "#/$defs/metadata" "ref": "#/$defs/metadata"
},
"symbol_mappings": {
"type": "object",
"description": "Manual symbol mappings from target to base.",
"additionalProperties": {
"type": "string"
}
} }
} }
}, },
+1 -2
View File
@@ -70,7 +70,6 @@ feature-depth = 1
# A list of advisory IDs to ignore. Note that ignored advisories will still # A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered. # output a note when they are encountered.
ignore = [ ignore = [
"RUSTSEC-2024-0370",
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, #{ 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 #"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" }, #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
@@ -240,7 +239,7 @@ allow-git = []
[sources.allow-org] [sources.allow-org]
# github.com organizations to allow git sources for # github.com organizations to allow git sources for
github = ["encounter"] github = ["notify-rs"]
# gitlab.com organizations to allow git sources for # gitlab.com organizations to allow git sources for
gitlab = [] gitlab = []
# bitbucket.org organizations to allow git sources for # bitbucket.org organizations to allow git sources for
+33 -21
View File
@@ -102,26 +102,32 @@ pub fn run(args: Args) -> Result<()> {
let unit_path = let unit_path =
PathBuf::from_str(u).ok().and_then(|p| fs::canonicalize(p).ok()); PathBuf::from_str(u).ok().and_then(|p| fs::canonicalize(p).ok());
let Some(object) = project_config.objects.iter_mut().find_map(|obj| { let Some(object) = project_config
if obj.name.as_deref() == Some(u) { .units
.as_deref_mut()
.unwrap_or_default()
.iter_mut()
.find_map(|obj| {
if obj.name.as_deref() == Some(u) {
resolve_paths(obj);
return Some(obj);
}
let up = unit_path.as_deref()?;
resolve_paths(obj); resolve_paths(obj);
return Some(obj);
}
let up = unit_path.as_deref()?; if [&obj.base_path, &obj.target_path]
.into_iter()
.filter_map(|p| p.as_ref().and_then(|p| p.canonicalize().ok()))
.any(|p| p == up)
{
return Some(obj);
}
resolve_paths(obj); None
})
if [&obj.base_path, &obj.target_path] else {
.into_iter()
.filter_map(|p| p.as_ref().and_then(|p| p.canonicalize().ok()))
.any(|p| p == up)
{
return Some(obj);
}
None
}) else {
bail!("Unit not found: {}", u) bail!("Unit not found: {}", u)
}; };
@@ -129,7 +135,13 @@ pub fn run(args: Args) -> Result<()> {
} else if let Some(symbol_name) = &args.symbol { } else if let Some(symbol_name) = &args.symbol {
let mut idx = None; let mut idx = None;
let mut count = 0usize; let mut count = 0usize;
for (i, obj) in project_config.objects.iter_mut().enumerate() { for (i, obj) in project_config
.units
.as_deref_mut()
.unwrap_or_default()
.iter_mut()
.enumerate()
{
resolve_paths(obj); resolve_paths(obj);
if obj if obj
@@ -148,7 +160,7 @@ pub fn run(args: Args) -> Result<()> {
} }
match (count, idx) { match (count, idx) {
(0, None) => bail!("Symbol not found: {}", symbol_name), (0, None) => bail!("Symbol not found: {}", symbol_name),
(1, Some(i)) => &mut project_config.objects[i], (1, Some(i)) => &mut project_config.units_mut()[i],
(2.., Some(_)) => bail!( (2.., Some(_)) => bail!(
"Multiple instances of {} were found, try specifying a unit", "Multiple instances of {} were found, try specifying a unit",
symbol_name symbol_name
@@ -303,7 +315,7 @@ fn find_function(obj: &ObjInfo, name: &str) -> Option<SymbolRef> {
None None
} }
#[allow(dead_code)] #[expect(dead_code)]
struct FunctionDiffUi { struct FunctionDiffUi {
relax_reloc_diffs: bool, relax_reloc_diffs: bool,
left_highlight: HighlightKind, left_highlight: HighlightKind,
@@ -758,7 +770,7 @@ impl FunctionDiffUi {
self.scroll_y += self.per_page / if half { 2 } else { 1 }; self.scroll_y += self.per_page / if half { 2 } else { 1 };
} }
#[allow(clippy::too_many_arguments)] #[expect(clippy::too_many_arguments)]
fn print_sym( fn print_sym(
&self, &self,
out: &mut Text<'static>, out: &mut Text<'static>,
+6 -4
View File
@@ -94,7 +94,7 @@ fn generate(args: GenerateArgs) -> Result<()> {
}; };
info!( info!(
"Generating report for {} units (using {} threads)", "Generating report for {} units (using {} threads)",
project.objects.len(), project.units().len(),
if args.deduplicate { 1 } else { rayon::current_num_threads() } if args.deduplicate { 1 } else { rayon::current_num_threads() }
); );
@@ -103,7 +103,7 @@ fn generate(args: GenerateArgs) -> Result<()> {
let mut existing_functions: HashSet<String> = HashSet::new(); let mut existing_functions: HashSet<String> = HashSet::new();
if args.deduplicate { if args.deduplicate {
// If deduplicating, we need to run single-threaded // If deduplicating, we need to run single-threaded
for object in &mut project.objects { for object in project.units.as_deref_mut().unwrap_or_default() {
if let Some(unit) = report_object( if let Some(unit) = report_object(
object, object,
project_dir, project_dir,
@@ -116,7 +116,9 @@ fn generate(args: GenerateArgs) -> Result<()> {
} }
} else { } else {
let vec = project let vec = project
.objects .units
.as_deref_mut()
.unwrap_or_default()
.par_iter_mut() .par_iter_mut()
.map(|object| { .map(|object| {
report_object( report_object(
@@ -132,7 +134,7 @@ fn generate(args: GenerateArgs) -> Result<()> {
} }
let measures = units.iter().flat_map(|u| u.measures.into_iter()).collect(); let measures = units.iter().flat_map(|u| u.measures.into_iter()).collect();
let mut categories = Vec::new(); let mut categories = Vec::new();
for category in &project.progress_categories { for category in project.progress_categories() {
categories.push(ReportCategory { categories.push(ReportCategory {
id: category.id.clone(), id: category.id.clone(),
name: category.name.clone(), name: category.name.clone(),
+23 -22
View File
@@ -17,35 +17,36 @@ crate-type = ["cdylib", "rlib"]
[features] [features]
all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "bindings"] all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "bindings"]
any-arch = [] # Implicit, used to check if any arch is enabled 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 = ["globset", "semver", "serde_json", "serde_yaml"] config = ["dep:bimap", "dep:globset", "dep:semver", "dep:serde_json", "dep:serde_yaml", "dep:serde", "dep:filetime"]
dwarf = ["gimli"] dwarf = ["dep:gimli"]
mips = ["any-arch", "rabbitizer"] mips = ["any-arch", "dep:rabbitizer"]
ppc = ["any-arch", "cwdemangle", "cwextab", "ppc750cl"] ppc = ["any-arch", "dep:cwdemangle", "dep:cwextab", "dep:ppc750cl"]
x86 = ["any-arch", "cpp_demangle", "iced-x86", "msvc-demangler"] x86 = ["any-arch", "dep:cpp_demangle", "dep:iced-x86", "dep:msvc-demangler"]
arm = ["any-arch", "cpp_demangle", "unarm", "arm-attr"] arm = ["any-arch", "dep:cpp_demangle", "dep:unarm", "dep:arm-attr"]
bindings = ["serde_json", "prost", "pbjson"] bindings = ["dep:serde_json", "dep:prost", "dep:pbjson", "dep:serde", "dep:prost-build", "dep:pbjson-build"]
wasm = ["bindings", "console_error_panic_hook", "console_log"] wasm = ["bindings", "any-arch", "dep:console_error_panic_hook", "dep:console_log", "dep:wasm-bindgen", "dep:tsify-next", "dep:log"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["all"] features = ["all"]
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
byteorder = "1.5" bimap = { version = "0.6", features = ["serde"], optional = true }
filetime = "0.2" byteorder = { version = "1.5", optional = true }
flagset = "0.4" filetime = { version = "0.2", optional = true }
log = "0.4" flagset = { version = "0.4", optional = true }
memmap2 = "0.9" log = { version = "0.4", optional = true }
num-traits = "0.2" 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 } object = { version = "0.36", features = ["read_core", "std", "elf", "pe"], default-features = false }
pbjson = { version = "0.7", optional = true } pbjson = { version = "0.7", optional = true }
prost = { version = "0.13", optional = true } prost = { version = "0.13", optional = true }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"], optional = true }
similar = { version = "2.6", default-features = false } similar = { version = "2.6", default-features = false, optional = true }
strum = { version = "0.26", features = ["derive"] } strum = { version = "0.26", features = ["derive"], optional = true }
wasm-bindgen = "0.2" wasm-bindgen = { version = "0.2", optional = true }
tsify-next = { version = "0.5", default-features = false, features = ["js"] } tsify-next = { version = "0.5", default-features = false, features = ["js"], optional = true }
console_log = { version = "1.0", optional = true } console_log = { version = "1.0", optional = true }
console_error_panic_hook = { version = "0.1", optional = true } console_error_panic_hook = { version = "0.1", optional = true }
@@ -76,5 +77,5 @@ unarm = { version = "1.6", optional = true }
arm-attr = { version = "0.1", optional = true } arm-attr = { version = "0.1", optional = true }
[build-dependencies] [build-dependencies]
prost-build = "0.13" prost-build = { version = "0.13", optional = true }
pbjson-build = "0.7" pbjson-build = { version = "0.7", optional = true }
+7 -2
View File
@@ -1,6 +1,11 @@
use std::path::{Path, PathBuf};
fn main() { 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 root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("protos");
let descriptor_path = root.join("proto_descriptor.bin"); let descriptor_path = root.join("proto_descriptor.bin");
println!("cargo:rerun-if-changed={}", descriptor_path.display()); println!("cargo:rerun-if-changed={}", descriptor_path.display());
+12 -5
View File
@@ -83,7 +83,7 @@ impl ObjArch for ObjArchMips {
&self, &self,
address: u64, address: u64,
code: &[u8], code: &[u8],
_section_index: usize, section_index: usize,
relocations: &[ObjReloc], relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>, line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig, config: &DiffObjConfig,
@@ -140,11 +140,18 @@ impl ObjArch for ObjArchMips {
| OperandType::cpu_label | OperandType::cpu_label
| OperandType::cpu_branch_target_label => { | OperandType::cpu_branch_target_label => {
if let Some(reloc) = reloc { if let Some(reloc) = reloc {
if matches!(&reloc.target_section, Some(s) if s == ".text") // If the relocation target is within the current function, we can
&& reloc.target.address > start_address // convert it into a relative branch target. Note that we check
&& reloc.target.address < end_address // 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 { } else {
push_reloc(&mut args, reloc)?; push_reloc(&mut args, reloc)?;
branch_dest = None; branch_dest = None;
+5 -1
View File
@@ -34,7 +34,11 @@ pub enum DataType {
impl DataType { impl DataType {
pub fn display_bytes<Endian: ByteOrder>(&self, bytes: &[u8]) -> Option<String> { 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; return None;
} }
+46 -45
View File
@@ -70,9 +70,13 @@ impl FunctionDiff {
// let (_section, symbol) = object.section_symbol(symbol_ref); // let (_section, symbol) = object.section_symbol(symbol_ref);
// Symbol::from(symbol) // 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 { Self {
symbol: Some(Symbol::from(symbol)), symbol: Some(Symbol::new(symbol)),
// diff_symbol, // diff_symbol,
instructions, instructions,
match_percent: symbol_diff.match_percent, match_percent: symbol_diff.match_percent,
@@ -90,8 +94,8 @@ impl DataDiff {
} }
} }
impl<'a> From<&'a ObjSymbol> for Symbol { impl Symbol {
fn from(value: &'a ObjSymbol) -> Self { pub fn new(value: &ObjSymbol) -> Self {
Self { Self {
name: value.name.to_string(), name: value.name.to_string(),
demangled_name: value.demangled_name.clone(), demangled_name: value.demangled_name.clone(),
@@ -122,29 +126,29 @@ fn symbol_flags(value: ObjSymbolFlagSet) -> u32 {
flags flags
} }
impl<'a> From<&'a ObjIns> for Instruction { impl Instruction {
fn from(value: &'a ObjIns) -> Self { pub fn new(object: &ObjInfo, instruction: &ObjIns) -> Self {
Self { Self {
address: value.address, address: instruction.address,
size: value.size as u32, size: instruction.size as u32,
opcode: value.op as u32, opcode: instruction.op as u32,
mnemonic: value.mnemonic.clone(), mnemonic: instruction.mnemonic.clone(),
formatted: value.formatted.clone(), formatted: instruction.formatted.clone(),
arguments: value.args.iter().map(Argument::from).collect(), arguments: instruction.args.iter().map(Argument::new).collect(),
relocation: value.reloc.as_ref().map(Relocation::from), relocation: instruction.reloc.as_ref().map(|reloc| Relocation::new(object, reloc)),
branch_dest: value.branch_dest, branch_dest: instruction.branch_dest,
line_number: value.line, line_number: instruction.line,
original: value.orig.clone(), original: instruction.orig.clone(),
} }
} }
} }
impl<'a> From<&'a ObjInsArg> for Argument { impl Argument {
fn from(value: &'a ObjInsArg) -> Self { pub fn new(value: &ObjInsArg) -> Self {
Self { Self {
value: Some(match value { value: Some(match value {
ObjInsArg::PlainText(s) => argument::Value::PlainText(s.to_string()), 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::Reloc => argument::Value::Relocation(ArgumentRelocation {}),
ObjInsArg::BranchDest(dest) => argument::Value::BranchDest(*dest), ObjInsArg::BranchDest(dest) => argument::Value::BranchDest(*dest),
}), }),
@@ -152,8 +156,8 @@ impl<'a> From<&'a ObjInsArg> for Argument {
} }
} }
impl From<&ObjInsArgValue> for ArgumentValue { impl ArgumentValue {
fn from(value: &ObjInsArgValue) -> Self { pub fn new(value: &ObjInsArgValue) -> Self {
Self { Self {
value: Some(match value { value: Some(match value {
ObjInsArgValue::Signed(v) => argument_value::Value::Signed(*v), ObjInsArgValue::Signed(v) => argument_value::Value::Signed(*v),
@@ -164,42 +168,39 @@ impl From<&ObjInsArgValue> for ArgumentValue {
} }
} }
impl<'a> From<&'a ObjReloc> for Relocation { impl Relocation {
fn from(value: &ObjReloc) -> Self { pub fn new(object: &ObjInfo, reloc: &ObjReloc) -> Self {
Self { Self {
r#type: match value.flags { r#type: match reloc.flags {
object::RelocationFlags::Elf { r_type } => r_type, object::RelocationFlags::Elf { r_type } => r_type,
object::RelocationFlags::MachO { r_type, .. } => r_type as u32, object::RelocationFlags::MachO { r_type, .. } => r_type as u32,
object::RelocationFlags::Coff { typ } => typ as u32, object::RelocationFlags::Coff { typ } => typ as u32,
object::RelocationFlags::Xcoff { r_rtype, .. } => r_rtype as u32, object::RelocationFlags::Xcoff { r_rtype, .. } => r_rtype as u32,
_ => unreachable!(), _ => unreachable!(),
}, },
type_name: String::new(), // TODO type_name: object.arch.display_reloc(reloc.flags).into_owned(),
target: Some(RelocationTarget::from(&value.target)), target: Some(RelocationTarget {
symbol: Some(Symbol::new(&reloc.target)),
addend: reloc.addend,
}),
} }
} }
} }
impl<'a> From<&'a ObjSymbol> for RelocationTarget { impl InstructionDiff {
fn from(value: &'a ObjSymbol) -> Self { pub fn new(object: &ObjInfo, instruction_diff: &ObjInsDiff) -> Self {
Self { symbol: Some(Symbol::from(value)), addend: value.addend }
}
}
impl<'a> From<&'a ObjInsDiff> for InstructionDiff {
fn from(value: &'a ObjInsDiff) -> Self {
Self { Self {
instruction: value.ins.as_ref().map(Instruction::from), instruction: instruction_diff.ins.as_ref().map(|ins| Instruction::new(object, ins)),
diff_kind: DiffKind::from(value.kind) as i32, diff_kind: DiffKind::from(instruction_diff.kind) as i32,
branch_from: value.branch_from.as_ref().map(InstructionBranchFrom::from), branch_from: instruction_diff.branch_from.as_ref().map(InstructionBranchFrom::new),
branch_to: value.branch_to.as_ref().map(InstructionBranchTo::from), branch_to: instruction_diff.branch_to.as_ref().map(InstructionBranchTo::new),
arg_diff: value.arg_diff.iter().map(ArgumentDiff::from).collect(), arg_diff: instruction_diff.arg_diff.iter().map(ArgumentDiff::new).collect(),
} }
} }
} }
impl From<&Option<ObjInsArgDiff>> for ArgumentDiff { impl ArgumentDiff {
fn from(value: &Option<ObjInsArgDiff>) -> Self { pub fn new(value: &Option<ObjInsArgDiff>) -> Self {
Self { diff_index: value.as_ref().map(|v| v.idx as u32) } Self { diff_index: value.as_ref().map(|v| v.idx as u32) }
} }
} }
@@ -228,8 +229,8 @@ impl From<ObjDataDiffKind> for DiffKind {
} }
} }
impl<'a> From<&'a ObjInsBranchFrom> for InstructionBranchFrom { impl InstructionBranchFrom {
fn from(value: &'a ObjInsBranchFrom) -> Self { pub fn new(value: &ObjInsBranchFrom) -> Self {
Self { Self {
instruction_index: value.ins_idx.iter().map(|&x| x as u32).collect(), instruction_index: value.ins_idx.iter().map(|&x| x as u32).collect(),
branch_index: value.branch_idx as u32, branch_index: value.branch_idx as u32,
@@ -237,8 +238,8 @@ impl<'a> From<&'a ObjInsBranchFrom> for InstructionBranchFrom {
} }
} }
impl<'a> From<&'a ObjInsBranchTo> for InstructionBranchTo { impl InstructionBranchTo {
fn from(value: &'a ObjInsBranchTo) -> Self { pub fn new(value: &ObjInsBranchTo) -> Self {
Self { instruction_index: value.ins_idx as u32, branch_index: value.branch_idx as u32 } Self { instruction_index: value.ins_idx as u32, branch_index: value.branch_idx as u32 }
} }
} }
+97 -45
View File
@@ -1,77 +1,100 @@
use std::{ use std::{
fs,
fs::File, fs::File,
io::{BufReader, Read}, io::{BufReader, BufWriter, Read},
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use bimap::BiBTreeMap;
use filetime::FileTime; use filetime::FileTime;
use globset::{Glob, GlobSet, GlobSetBuilder}; use globset::{Glob, GlobSet, GlobSetBuilder};
#[inline] #[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
fn bool_true() -> bool { true }
#[derive(Default, Clone, serde::Deserialize)]
pub struct ProjectConfig { pub struct ProjectConfig {
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub min_version: Option<String>, pub min_version: Option<String>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub custom_make: Option<String>, pub custom_make: Option<String>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub custom_args: Option<Vec<String>>, pub custom_args: Option<Vec<String>>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub target_dir: Option<PathBuf>, pub target_dir: Option<PathBuf>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub base_dir: Option<PathBuf>, pub base_dir: Option<PathBuf>,
#[serde(default = "bool_true")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub build_base: bool, pub build_base: Option<bool>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub build_target: bool, pub build_target: Option<bool>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub watch_patterns: Option<Vec<Glob>>, pub watch_patterns: Option<Vec<Glob>>,
#[serde(default, alias = "units")] #[serde(default, alias = "objects", skip_serializing_if = "Option::is_none")]
pub objects: Vec<ProjectObject>, pub units: Option<Vec<ProjectObject>>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub progress_categories: Vec<ProjectProgressCategory>, pub progress_categories: Option<Vec<ProjectProgressCategory>>,
} }
#[derive(Default, Clone, serde::Deserialize)] impl ProjectConfig {
#[inline]
pub fn units(&self) -> &[ProjectObject] { self.units.as_deref().unwrap_or_default() }
#[inline]
pub fn units_mut(&mut self) -> &mut Vec<ProjectObject> {
self.units.get_or_insert_with(Vec::new)
}
#[inline]
pub fn progress_categories(&self) -> &[ProjectProgressCategory] {
self.progress_categories.as_deref().unwrap_or_default()
}
#[inline]
pub fn progress_categories_mut(&mut self) -> &mut Vec<ProjectProgressCategory> {
self.progress_categories.get_or_insert_with(Vec::new)
}
}
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct ProjectObject { pub struct ProjectObject {
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>, pub name: Option<String>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub path: Option<PathBuf>, pub path: Option<PathBuf>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub target_path: Option<PathBuf>, pub target_path: Option<PathBuf>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub base_path: Option<PathBuf>, pub base_path: Option<PathBuf>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
#[deprecated(note = "Use metadata.reverse_fn_order")] #[deprecated(note = "Use metadata.reverse_fn_order")]
pub reverse_fn_order: Option<bool>, pub reverse_fn_order: Option<bool>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
#[deprecated(note = "Use metadata.complete")] #[deprecated(note = "Use metadata.complete")]
pub complete: Option<bool>, pub complete: Option<bool>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub scratch: Option<ScratchConfig>, pub scratch: Option<ScratchConfig>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<ProjectObjectMetadata>, pub metadata: Option<ProjectObjectMetadata>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub symbol_mappings: Option<SymbolMappings>,
} }
#[derive(Default, Clone, serde::Deserialize)] pub type SymbolMappings = BiBTreeMap<String, String>;
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct ProjectObjectMetadata { pub struct ProjectObjectMetadata {
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub complete: Option<bool>, pub complete: Option<bool>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub reverse_fn_order: Option<bool>, pub reverse_fn_order: Option<bool>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub source_path: Option<String>, pub source_path: Option<String>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub progress_categories: Option<Vec<String>>, pub progress_categories: Option<Vec<String>>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_generated: Option<bool>, pub auto_generated: Option<bool>,
} }
#[derive(Default, Clone, serde::Deserialize)] #[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct ProjectProgressCategory { pub struct ProjectProgressCategory {
#[serde(default)] #[serde(default)]
pub id: String, pub id: String,
@@ -112,12 +135,12 @@ impl ProjectObject {
} }
pub fn complete(&self) -> Option<bool> { pub fn complete(&self) -> Option<bool> {
#[allow(deprecated)] #[expect(deprecated)]
self.metadata.as_ref().and_then(|m| m.complete).or(self.complete) self.metadata.as_ref().and_then(|m| m.complete).or(self.complete)
} }
pub fn reverse_fn_order(&self) -> Option<bool> { pub fn reverse_fn_order(&self) -> Option<bool> {
#[allow(deprecated)] #[expect(deprecated)]
self.metadata.as_ref().and_then(|m| m.reverse_fn_order).or(self.reverse_fn_order) self.metadata.as_ref().and_then(|m| m.reverse_fn_order).or(self.reverse_fn_order)
} }
@@ -132,16 +155,16 @@ impl ProjectObject {
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] #[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct ScratchConfig { pub struct ScratchConfig {
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub platform: Option<String>, pub platform: Option<String>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub compiler: Option<String>, pub compiler: Option<String>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub c_flags: Option<String>, pub c_flags: Option<String>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub ctx_path: Option<PathBuf>, pub ctx_path: Option<PathBuf>,
#[serde(default)] #[serde(default, skip_serializing_if = "Option::is_none")]
pub build_ctx: bool, pub build_ctx: Option<bool>,
} }
pub const CONFIG_FILENAMES: [&str; 3] = ["objdiff.json", "objdiff.yml", "objdiff.yaml"]; pub const CONFIG_FILENAMES: [&str; 3] = ["objdiff.json", "objdiff.yml", "objdiff.yaml"];
@@ -154,7 +177,7 @@ pub const DEFAULT_WATCH_PATTERNS: &[&str] = &[
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
pub struct ProjectConfigInfo { pub struct ProjectConfigInfo {
pub path: PathBuf, pub path: PathBuf,
pub timestamp: FileTime, pub timestamp: Option<FileTime>,
} }
pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> { pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> {
@@ -180,12 +203,41 @@ pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectC
result = Err(e); result = Err(e);
} }
} }
return Some((result, ProjectConfigInfo { path: config_path, timestamp: ts })); return Some((result, ProjectConfigInfo { path: config_path, timestamp: Some(ts) }));
} }
} }
None None
} }
pub fn save_project_config(
config: &ProjectConfig,
info: &ProjectConfigInfo,
) -> Result<ProjectConfigInfo> {
if let Some(last_ts) = info.timestamp {
// Check if the file has changed since we last read it
if let Ok(metadata) = fs::metadata(&info.path) {
let ts = FileTime::from_last_modification_time(&metadata);
if ts != last_ts {
return Err(anyhow!("Config file has changed since last read"));
}
}
}
let mut writer =
BufWriter::new(File::create(&info.path).context("Failed to create config file")?);
let ext = info.path.extension().and_then(|ext| ext.to_str()).unwrap_or("json");
match ext {
"json" => serde_json::to_writer_pretty(&mut writer, config).context("Failed to write JSON"),
"yml" | "yaml" => {
serde_yaml::to_writer(&mut writer, config).context("Failed to write YAML")
}
_ => Err(anyhow!("Unknown config file extension: {ext}")),
}?;
let file = writer.into_inner().context("Failed to flush file")?;
let metadata = file.metadata().context("Failed to get file metadata")?;
let ts = FileTime::from_last_modification_time(&metadata);
Ok(ProjectConfigInfo { path: info.path.clone(), timestamp: Some(ts) })
}
fn validate_min_version(config: &ProjectConfig) -> Result<()> { fn validate_min_version(config: &ProjectConfig) -> Result<()> {
let Some(min_version) = &config.min_version else { return Ok(()) }; let Some(min_version) = &config.min_version else { return Ok(()) };
let version = semver::Version::parse(env!("CARGO_PKG_VERSION")) let version = semver::Version::parse(env!("CARGO_PKG_VERSION"))
+46 -16
View File
@@ -9,7 +9,7 @@ use crate::{
DiffObjConfig, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind, DiffObjConfig, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind,
ObjSymbolDiff, ObjSymbolDiff,
}, },
obj::{ObjInfo, ObjInsArg, ObjReloc, ObjSymbol, ObjSymbolFlags, SymbolRef}, obj::{ObjInfo, ObjInsArg, ObjReloc, ObjSymbolFlags, SymbolRef},
}; };
pub fn process_code_symbol( pub fn process_code_symbol(
@@ -41,10 +41,12 @@ pub fn no_diff_code(out: &ProcessCodeResult, symbol_ref: SymbolRef) -> Result<Ob
}); });
} }
resolve_branches(&mut diff); resolve_branches(&mut diff);
Ok(ObjSymbolDiff { symbol_ref, diff_symbol: None, instructions: diff, match_percent: None }) Ok(ObjSymbolDiff { symbol_ref, target_symbol: None, instructions: diff, match_percent: None })
} }
pub fn diff_code( pub fn diff_code(
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left_out: &ProcessCodeResult, left_out: &ProcessCodeResult,
right_out: &ProcessCodeResult, right_out: &ProcessCodeResult,
left_symbol_ref: SymbolRef, left_symbol_ref: SymbolRef,
@@ -60,14 +62,14 @@ pub fn diff_code(
let mut diff_state = InsDiffState::default(); let mut diff_state = InsDiffState::default();
for (left, right) in left_diff.iter_mut().zip(right_diff.iter_mut()) { 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; left.kind = result.kind;
right.kind = result.kind; right.kind = result.kind;
left.arg_diff = result.left_args_diff; left.arg_diff = result.left_args_diff;
right.arg_diff = result.right_args_diff; right.arg_diff = result.right_args_diff;
} }
let total = left_out.insts.len(); let total = left_out.insts.len().max(right_out.insts.len());
let percent = if diff_state.diff_count >= total { let percent = if diff_state.diff_count >= total {
0.0 0.0
} else { } else {
@@ -77,13 +79,13 @@ pub fn diff_code(
Ok(( Ok((
ObjSymbolDiff { ObjSymbolDiff {
symbol_ref: left_symbol_ref, symbol_ref: left_symbol_ref,
diff_symbol: Some(right_symbol_ref), target_symbol: Some(right_symbol_ref),
instructions: left_diff, instructions: left_diff,
match_percent: Some(percent), match_percent: Some(percent),
}, },
ObjSymbolDiff { ObjSymbolDiff {
symbol_ref: right_symbol_ref, symbol_ref: right_symbol_ref,
diff_symbol: Some(left_symbol_ref), target_symbol: Some(left_symbol_ref),
instructions: right_diff, instructions: right_diff,
match_percent: Some(percent), match_percent: Some(percent),
}, },
@@ -170,12 +172,33 @@ fn resolve_branches(vec: &mut [ObjInsDiff]) {
} }
} }
fn address_eq(left: &ObjSymbol, right: &ObjSymbol) -> bool { fn address_eq(left: &ObjReloc, right: &ObjReloc) -> bool {
left.address as i64 + left.addend == right.address as i64 + right.addend 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( fn reloc_eq(
config: &DiffObjConfig, config: &DiffObjConfig,
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left_reloc: Option<&ObjReloc>, left_reloc: Option<&ObjReloc>,
right_reloc: Option<&ObjReloc>, right_reloc: Option<&ObjReloc>,
) -> bool { ) -> bool {
@@ -189,29 +212,32 @@ fn reloc_eq(
return true; return true;
} }
let name_matches = left.target.name == right.target.name; let symbol_name_matches = left.target.name == right.target.name;
match (&left.target_section, &right.target_section) { match (&left.target.orig_section_index, &right.target.orig_section_index) {
(Some(sl), Some(sr)) => { (Some(sl), Some(sr)) => {
// Match if section and name or address match // 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, (Some(_), None) => false,
(None, Some(_)) => { (None, Some(_)) => {
// Match if possibly stripped weak symbol // 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( fn arg_eq(
config: &DiffObjConfig, config: &DiffObjConfig,
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left: &ObjInsArg, left: &ObjInsArg,
right: &ObjInsArg, right: &ObjInsArg,
left_diff: &ObjInsDiff, left_diff: &ObjInsDiff,
right_diff: &ObjInsDiff, right_diff: &ObjInsDiff,
) -> bool { ) -> bool {
return match left { match left {
ObjInsArg::PlainText(l) => match right { ObjInsArg::PlainText(l) => match right {
ObjInsArg::PlainText(r) => l == r, ObjInsArg::PlainText(r) => l == r,
_ => false, _ => false,
@@ -227,6 +253,8 @@ fn arg_eq(
matches!(right, ObjInsArg::Reloc) matches!(right, ObjInsArg::Reloc)
&& reloc_eq( && reloc_eq(
config, config,
left_obj,
right_obj,
left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()), left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()), right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
) )
@@ -236,7 +264,7 @@ fn arg_eq(
left_diff.branch_to.as_ref().map(|b| b.ins_idx) left_diff.branch_to.as_ref().map(|b| b.ins_idx)
== right_diff.branch_to.as_ref().map(|b| b.ins_idx) == right_diff.branch_to.as_ref().map(|b| b.ins_idx)
} }
}; }
} }
#[derive(Default)] #[derive(Default)]
@@ -257,6 +285,8 @@ struct InsDiffResult {
fn compare_ins( fn compare_ins(
config: &DiffObjConfig, config: &DiffObjConfig,
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left: &ObjInsDiff, left: &ObjInsDiff,
right: &ObjInsDiff, right: &ObjInsDiff,
state: &mut InsDiffState, state: &mut InsDiffState,
@@ -283,7 +313,7 @@ fn compare_ins(
state.diff_count += 1; state.diff_count += 1;
} }
for (a, b) in left_ins.args.iter().zip(&right_ins.args) { for (a, b) in left_ins.args.iter().zip(&right_ins.args) {
if arg_eq(config, a, b, left, right) { if arg_eq(config, left_obj, right_obj, a, b, left, right) {
result.left_args_diff.push(None); result.left_args_diff.push(None);
result.right_args_diff.push(None); result.right_args_diff.push(None);
} else { } else {
+5 -5
View File
@@ -20,13 +20,13 @@ pub fn diff_bss_symbol(
Ok(( Ok((
ObjSymbolDiff { ObjSymbolDiff {
symbol_ref: left_symbol_ref, symbol_ref: left_symbol_ref,
diff_symbol: Some(right_symbol_ref), target_symbol: Some(right_symbol_ref),
instructions: vec![], instructions: vec![],
match_percent: Some(percent), match_percent: Some(percent),
}, },
ObjSymbolDiff { ObjSymbolDiff {
symbol_ref: right_symbol_ref, symbol_ref: right_symbol_ref,
diff_symbol: Some(left_symbol_ref), target_symbol: Some(left_symbol_ref),
instructions: vec![], instructions: vec![],
match_percent: Some(percent), match_percent: Some(percent),
}, },
@@ -34,7 +34,7 @@ pub fn diff_bss_symbol(
} }
pub fn no_diff_symbol(_obj: &ObjInfo, symbol_ref: SymbolRef) -> ObjSymbolDiff { pub fn no_diff_symbol(_obj: &ObjInfo, symbol_ref: SymbolRef) -> ObjSymbolDiff {
ObjSymbolDiff { symbol_ref, diff_symbol: None, instructions: vec![], match_percent: None } ObjSymbolDiff { symbol_ref, target_symbol: None, instructions: vec![], match_percent: None }
} }
/// Compare the data sections of two object files. /// Compare the data sections of two object files.
@@ -158,13 +158,13 @@ pub fn diff_data_symbol(
Ok(( Ok((
ObjSymbolDiff { ObjSymbolDiff {
symbol_ref: left_symbol_ref, symbol_ref: left_symbol_ref,
diff_symbol: Some(right_symbol_ref), target_symbol: Some(right_symbol_ref),
instructions: vec![], instructions: vec![],
match_percent: Some(match_percent), match_percent: Some(match_percent),
}, },
ObjSymbolDiff { ObjSymbolDiff {
symbol_ref: right_symbol_ref, symbol_ref: right_symbol_ref,
diff_symbol: Some(left_symbol_ref), target_symbol: Some(left_symbol_ref),
instructions: vec![], instructions: vec![],
match_percent: Some(match_percent), match_percent: Some(match_percent),
}, },
+4 -4
View File
@@ -29,7 +29,7 @@ pub enum DiffText<'a> {
Eol, Eol,
} }
#[derive(Default, Clone, PartialEq, Eq)] #[derive(Debug, Default, Clone, PartialEq, Eq)]
pub enum HighlightKind { pub enum HighlightKind {
#[default] #[default]
None, None,
@@ -94,9 +94,9 @@ fn display_reloc_name<E>(
mut cb: impl FnMut(DiffText) -> Result<(), E>, mut cb: impl FnMut(DiffText) -> Result<(), E>,
) -> Result<(), E> { ) -> Result<(), E> {
cb(DiffText::Symbol(&reloc.target))?; cb(DiffText::Symbol(&reloc.target))?;
match reloc.target.addend.cmp(&0i64) { match reloc.addend.cmp(&0i64) {
Ordering::Greater => cb(DiffText::Basic(&format!("+{:#x}", reloc.target.addend))), Ordering::Greater => cb(DiffText::Basic(&format!("+{:#x}", reloc.addend))),
Ordering::Less => cb(DiffText::Basic(&format!("-{:#x}", -reloc.target.addend))), Ordering::Less => cb(DiffText::Basic(&format!("-{:#x}", -reloc.addend))),
_ => Ok(()), _ => Ok(()),
} }
} }
+200 -13
View File
@@ -3,6 +3,7 @@ use std::collections::HashSet;
use anyhow::Result; use anyhow::Result;
use crate::{ use crate::{
config::SymbolMappings,
diff::{ diff::{
code::{diff_code, no_diff_code, process_code_symbol}, code::{diff_code, no_diff_code, process_code_symbol},
data::{ data::{
@@ -28,8 +29,8 @@ pub mod display;
serde::Serialize, serde::Serialize,
strum::VariantArray, strum::VariantArray,
strum::EnumMessage, strum::EnumMessage,
tsify_next::Tsify,
)] )]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
pub enum X86Formatter { pub enum X86Formatter {
#[default] #[default]
#[strum(message = "Intel (default)")] #[strum(message = "Intel (default)")]
@@ -53,8 +54,8 @@ pub enum X86Formatter {
serde::Serialize, serde::Serialize,
strum::VariantArray, strum::VariantArray,
strum::EnumMessage, strum::EnumMessage,
tsify_next::Tsify,
)] )]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
pub enum MipsAbi { pub enum MipsAbi {
#[default] #[default]
#[strum(message = "Auto (default)")] #[strum(message = "Auto (default)")]
@@ -78,8 +79,8 @@ pub enum MipsAbi {
serde::Serialize, serde::Serialize,
strum::VariantArray, strum::VariantArray,
strum::EnumMessage, strum::EnumMessage,
tsify_next::Tsify,
)] )]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
pub enum MipsInstrCategory { pub enum MipsInstrCategory {
#[default] #[default]
#[strum(message = "Auto (default)")] #[strum(message = "Auto (default)")]
@@ -107,8 +108,8 @@ pub enum MipsInstrCategory {
serde::Serialize, serde::Serialize,
strum::VariantArray, strum::VariantArray,
strum::EnumMessage, strum::EnumMessage,
tsify_next::Tsify,
)] )]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
pub enum ArmArchVersion { pub enum ArmArchVersion {
#[default] #[default]
#[strum(message = "Auto (default)")] #[strum(message = "Auto (default)")]
@@ -132,8 +133,8 @@ pub enum ArmArchVersion {
serde::Serialize, serde::Serialize,
strum::VariantArray, strum::VariantArray,
strum::EnumMessage, strum::EnumMessage,
tsify_next::Tsify,
)] )]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
pub enum ArmR9Usage { pub enum ArmR9Usage {
#[default] #[default]
#[strum( #[strum(
@@ -153,14 +154,17 @@ pub enum ArmR9Usage {
#[inline] #[inline]
const fn default_true() -> bool { true } const fn default_true() -> bool { true }
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, tsify_next::Tsify)] #[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[tsify(from_wasm_abi)] #[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
#[cfg_attr(feature = "wasm", tsify(from_wasm_abi))]
#[serde(default)] #[serde(default)]
pub struct DiffObjConfig { pub struct DiffObjConfig {
pub relax_reloc_diffs: bool, pub relax_reloc_diffs: bool,
#[serde(default = "default_true")] #[serde(default = "default_true")]
pub space_between_args: bool, pub space_between_args: bool,
pub combine_data_sections: bool, pub combine_data_sections: bool,
#[serde(default)]
pub symbol_mappings: MappingConfig,
// x86 // x86
pub x86_formatter: X86Formatter, pub x86_formatter: X86Formatter,
// MIPS // MIPS
@@ -182,6 +186,7 @@ impl Default for DiffObjConfig {
relax_reloc_diffs: false, relax_reloc_diffs: false,
space_between_args: true, space_between_args: true,
combine_data_sections: false, combine_data_sections: false,
symbol_mappings: Default::default(),
x86_formatter: Default::default(), x86_formatter: Default::default(),
mips_abi: Default::default(), mips_abi: Default::default(),
mips_instr_category: Default::default(), mips_instr_category: Default::default(),
@@ -223,8 +228,10 @@ impl ObjSectionDiff {
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct ObjSymbolDiff { pub struct ObjSymbolDiff {
/// The symbol ref this object
pub symbol_ref: SymbolRef, pub symbol_ref: SymbolRef,
pub diff_symbol: Option<SymbolRef>, /// The symbol ref in the _other_ object that this symbol was diffed against
pub target_symbol: Option<SymbolRef>,
pub instructions: Vec<ObjInsDiff>, pub instructions: Vec<ObjInsDiff>,
pub match_percent: Option<f32>, pub match_percent: Option<f32>,
} }
@@ -294,8 +301,13 @@ pub struct ObjInsBranchTo {
#[derive(Default)] #[derive(Default)]
pub struct ObjDiff { pub struct ObjDiff {
/// A list of all section diffs in the object.
pub sections: Vec<ObjSectionDiff>, pub sections: Vec<ObjSectionDiff>,
/// Common BSS symbols don't live in a section, so they're stored separately.
pub common: Vec<ObjSymbolDiff>, pub common: Vec<ObjSymbolDiff>,
/// If `selecting_left` or `selecting_right` is set, this is the list of symbols
/// that are being mapped to the other object.
pub mapping_symbols: Vec<ObjSymbolDiff>,
} }
impl ObjDiff { impl ObjDiff {
@@ -303,13 +315,14 @@ impl ObjDiff {
let mut result = Self { let mut result = Self {
sections: Vec::with_capacity(obj.sections.len()), sections: Vec::with_capacity(obj.sections.len()),
common: Vec::with_capacity(obj.common.len()), common: Vec::with_capacity(obj.common.len()),
mapping_symbols: vec![],
}; };
for (section_idx, section) in obj.sections.iter().enumerate() { for (section_idx, section) in obj.sections.iter().enumerate() {
let mut symbols = Vec::with_capacity(section.symbols.len()); let mut symbols = Vec::with_capacity(section.symbols.len());
for (symbol_idx, _) in section.symbols.iter().enumerate() { for (symbol_idx, _) in section.symbols.iter().enumerate() {
symbols.push(ObjSymbolDiff { symbols.push(ObjSymbolDiff {
symbol_ref: SymbolRef { section_idx, symbol_idx }, symbol_ref: SymbolRef { section_idx, symbol_idx },
diff_symbol: None, target_symbol: None,
instructions: vec![], instructions: vec![],
match_percent: None, match_percent: None,
}); });
@@ -328,7 +341,7 @@ impl ObjDiff {
for (symbol_idx, _) in obj.common.iter().enumerate() { for (symbol_idx, _) in obj.common.iter().enumerate() {
result.common.push(ObjSymbolDiff { result.common.push(ObjSymbolDiff {
symbol_ref: SymbolRef { section_idx: obj.sections.len(), symbol_idx }, symbol_ref: SymbolRef { section_idx: obj.sections.len(), symbol_idx },
diff_symbol: None, target_symbol: None,
instructions: vec![], instructions: vec![],
match_percent: None, match_percent: None,
}); });
@@ -378,7 +391,7 @@ pub fn diff_objs(
right: Option<&ObjInfo>, right: Option<&ObjInfo>,
prev: Option<&ObjInfo>, prev: Option<&ObjInfo>,
) -> Result<DiffObjsResult> { ) -> Result<DiffObjsResult> {
let symbol_matches = matching_symbols(left, right, prev)?; let symbol_matches = matching_symbols(left, right, prev, &config.symbol_mappings)?;
let section_matches = matching_sections(left, right)?; let section_matches = matching_sections(left, right)?;
let mut left = left.map(|p| (p, ObjDiff::new_from_obj(p))); let mut left = left.map(|p| (p, ObjDiff::new_from_obj(p)));
let mut right = right.map(|p| (p, ObjDiff::new_from_obj(p))); let mut right = right.map(|p| (p, ObjDiff::new_from_obj(p)));
@@ -399,6 +412,8 @@ pub fn diff_objs(
let left_code = process_code_symbol(left_obj, left_symbol_ref, config)?; 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 right_code = process_code_symbol(right_obj, right_symbol_ref, config)?;
let (left_diff, right_diff) = diff_code( let (left_diff, right_diff) = diff_code(
left_obj,
right_obj,
&left_code, &left_code,
&right_code, &right_code,
left_symbol_ref, left_symbol_ref,
@@ -412,6 +427,8 @@ pub fn diff_objs(
let (prev_obj, prev_out) = prev.as_mut().unwrap(); let (prev_obj, prev_out) = prev.as_mut().unwrap();
let prev_code = process_code_symbol(prev_obj, prev_symbol_ref, config)?; let prev_code = process_code_symbol(prev_obj, prev_symbol_ref, config)?;
let (_, prev_diff) = diff_code( let (_, prev_diff) = diff_code(
left_obj,
right_obj,
&right_code, &right_code,
&prev_code, &prev_code,
right_symbol_ref, right_symbol_ref,
@@ -529,6 +546,17 @@ pub fn diff_objs(
} }
} }
if let (Some((right_obj, right_out)), Some((left_obj, left_out))) =
(right.as_mut(), left.as_mut())
{
if let Some(right_name) = &config.symbol_mappings.selecting_left {
generate_mapping_symbols(right_obj, right_name, left_obj, left_out, config)?;
}
if let Some(left_name) = &config.symbol_mappings.selecting_right {
generate_mapping_symbols(left_obj, left_name, right_obj, right_out, config)?;
}
}
Ok(DiffObjsResult { Ok(DiffObjsResult {
left: left.map(|(_, o)| o), left: left.map(|(_, o)| o),
right: right.map(|(_, o)| o), right: right.map(|(_, o)| o),
@@ -536,6 +564,65 @@ pub fn diff_objs(
}) })
} }
/// When we're selecting a symbol to use as a comparison, we'll create comparisons for all
/// symbols in the other object that match the selected symbol's section and kind. This allows
/// us to display match percentages for all symbols in the other object that could be selected.
fn generate_mapping_symbols(
base_obj: &ObjInfo,
base_name: &str,
target_obj: &ObjInfo,
target_out: &mut ObjDiff,
config: &DiffObjConfig,
) -> Result<()> {
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 Some(base_section) = base_section else {
return Ok(());
};
let base_code = match base_section.kind {
ObjSectionKind::Code => Some(process_code_symbol(base_obj, base_symbol_ref, config)?),
_ => None,
};
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)
{
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,
base_symbol_ref,
config,
)?;
target_out.mapping_symbols.push(left_diff);
}
ObjSectionKind::Data => {
let (left_diff, _right_diff) =
diff_data_symbol(target_obj, base_obj, target_symbol_ref, base_symbol_ref)?;
target_out.mapping_symbols.push(left_diff);
}
ObjSectionKind::Bss => {
let (left_diff, _right_diff) =
diff_bss_symbol(target_obj, base_obj, target_symbol_ref, base_symbol_ref)?;
target_out.mapping_symbols.push(left_diff);
}
}
}
}
Ok(())
}
#[derive(Copy, Clone, Eq, PartialEq)] #[derive(Copy, Clone, Eq, PartialEq)]
struct SymbolMatch { struct SymbolMatch {
left: Option<SymbolRef>, left: Option<SymbolRef>,
@@ -551,19 +638,115 @@ struct SectionMatch {
section_kind: ObjSectionKind, section_kind: ObjSectionKind,
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, serde::Deserialize, serde::Serialize)]
pub struct MappingConfig {
/// Manual symbol mappings
pub mappings: SymbolMappings,
/// The right object symbol name that we're selecting a left symbol for
pub selecting_left: Option<String>,
/// The left object symbol name that we're selecting a right symbol for
pub selecting_right: Option<String>,
}
fn symbol_ref_by_name(obj: &ObjInfo, name: &str) -> Option<SymbolRef> {
for (section_idx, section) in obj.sections.iter().enumerate() {
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
if symbol.name == name {
return Some(SymbolRef { section_idx, symbol_idx });
}
}
}
None
}
fn apply_symbol_mappings(
left: &ObjInfo,
right: &ObjInfo,
mapping_config: &MappingConfig,
left_used: &mut HashSet<SymbolRef>,
right_used: &mut HashSet<SymbolRef>,
matches: &mut Vec<SymbolMatch>,
) -> Result<()> {
// If we're selecting a symbol to use as a comparison, mark it as used
// This ensures that we don't match it to another symbol at any point
if let Some(left_name) = &mapping_config.selecting_left {
if let Some(left_symbol) = symbol_ref_by_name(left, left_name) {
left_used.insert(left_symbol);
}
}
if let Some(right_name) = &mapping_config.selecting_right {
if let Some(right_symbol) = symbol_ref_by_name(right, right_name) {
right_used.insert(right_symbol);
}
}
// Apply manual symbol mappings
for (left_name, right_name) in &mapping_config.mappings {
let Some(left_symbol) = symbol_ref_by_name(left, left_name) else {
continue;
};
if left_used.contains(&left_symbol) {
continue;
}
let Some(right_symbol) = symbol_ref_by_name(right, right_name) else {
continue;
};
if right_used.contains(&right_symbol) {
continue;
}
let left_section = &left.sections[left_symbol.section_idx];
let right_section = &right.sections[right_symbol.section_idx];
if left_section.kind != right_section.kind {
log::warn!(
"Symbol section kind mismatch: {} ({:?}) vs {} ({:?})",
left_name,
left_section.kind,
right_name,
right_section.kind
);
continue;
}
matches.push(SymbolMatch {
left: Some(left_symbol),
right: Some(right_symbol),
prev: None, // TODO
section_kind: left_section.kind,
});
left_used.insert(left_symbol);
right_used.insert(right_symbol);
}
Ok(())
}
/// Find matching symbols between each object. /// Find matching symbols between each object.
fn matching_symbols( fn matching_symbols(
left: Option<&ObjInfo>, left: Option<&ObjInfo>,
right: Option<&ObjInfo>, right: Option<&ObjInfo>,
prev: Option<&ObjInfo>, prev: Option<&ObjInfo>,
mappings: &MappingConfig,
) -> Result<Vec<SymbolMatch>> { ) -> Result<Vec<SymbolMatch>> {
let mut matches = Vec::new(); let mut matches = Vec::new();
let mut left_used = HashSet::new();
let mut right_used = HashSet::new(); let mut right_used = HashSet::new();
if let Some(left) = left { if let Some(left) = left {
if let Some(right) = right {
apply_symbol_mappings(
left,
right,
mappings,
&mut left_used,
&mut right_used,
&mut matches,
)?;
}
for (section_idx, section) in left.sections.iter().enumerate() { for (section_idx, section) in left.sections.iter().enumerate() {
for (symbol_idx, symbol) in section.symbols.iter().enumerate() { for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
let symbol_ref = SymbolRef { section_idx, symbol_idx };
if left_used.contains(&symbol_ref) {
continue;
}
let symbol_match = SymbolMatch { let symbol_match = SymbolMatch {
left: Some(SymbolRef { section_idx, symbol_idx }), left: Some(symbol_ref),
right: find_symbol(right, symbol, section, Some(&right_used)), right: find_symbol(right, symbol, section, Some(&right_used)),
prev: find_symbol(prev, symbol, section, None), prev: find_symbol(prev, symbol, section, None),
section_kind: section.kind, section_kind: section.kind,
@@ -575,8 +758,12 @@ fn matching_symbols(
} }
} }
for (symbol_idx, symbol) in left.common.iter().enumerate() { for (symbol_idx, symbol) in left.common.iter().enumerate() {
let symbol_ref = SymbolRef { section_idx: left.sections.len(), symbol_idx };
if left_used.contains(&symbol_ref) {
continue;
}
let symbol_match = SymbolMatch { let symbol_match = SymbolMatch {
left: Some(SymbolRef { section_idx: left.sections.len(), symbol_idx }), left: Some(symbol_ref),
right: find_common_symbol(right, symbol), right: find_common_symbol(right, symbol),
prev: find_common_symbol(prev, symbol), prev: find_common_symbol(prev, symbol),
section_kind: ObjSectionKind::Bss, section_kind: ObjSectionKind::Bss,
+1
View File
@@ -8,4 +8,5 @@ pub mod config;
pub mod diff; pub mod diff;
#[cfg(feature = "any-arch")] #[cfg(feature = "any-arch")]
pub mod obj; pub mod obj;
#[cfg(feature = "any-arch")]
pub mod util; pub mod util;

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