Compare commits

..

56 Commits

Author SHA1 Message Date
Luke Street 737b3782db ci: Add id-token write permission 2025-05-06 22:51:11 -06:00
sozud 1d782243e0 Add data info for superh (#198)
* Add data info for superh

* fmt

* Fetch symbol data dynamically from resolved.section

* Remove unused var

---------

Co-authored-by: Luke Street <luke@street.dev>
2025-05-06 22:30:53 -06:00
Luke Street a1499f475d Update test snapshot 2025-05-06 22:07:21 -06:00
LagoLunatic f263e490e3 Combine data/text sections: Pad sections to alignment (#197)
* Combine data/text sections: Pad all sections to 4-byte minimum alignment

* Update x86 test snapshot

* Read and store object section alignment

* Combine data/text sections: Pad sections to more than 4-byte alignment if they have alignment specified
2025-05-06 21:47:08 -06:00
Aetias d0e6c5c057 Display branch destinations after PC-relative loads (#194) 2025-05-06 21:45:03 -06:00
LagoLunatic e1c51ac297 ARM: Fix "Combine text sections" confusing code and data (#195)
* ARM: Fix parsing of mapping symbols when "Combine text sections" is enabled

* Add test
2025-04-26 11:14:16 -06:00
Luke Street 39b1b49985 cli: Add --config arg to report generate 2025-04-22 18:13:12 -06:00
Luke Street 6c7160ab7e Use rabbitizer 2.0.0-alpha.1 2025-04-22 18:13:12 -06:00
sozud 644d4762f0 Preliminary SuperH support (#186)
* Preliminary SuperH support

* fixes

* clippy

* clippy
2025-04-17 14:20:30 -06:00
LagoLunatic b40fae5140 Allow copying Shift JIS encoded string literals (#189)
* Update openssl and tokio for Cargo deny

* Allow copying Shift JIS encoded strings

* Override data type label for Shift JIS strings
2025-04-17 10:07:05 -06:00
LagoLunatic fbf85632ab PPC: Detect unpooled string literal references (#188)
* Update openssl and tokio for Cargo deny

* PPC: Detect unpooled string literal references
2025-04-16 23:33:55 -06:00
Luke Street 73a89d2768 Version v3.0.0-beta.6 2025-04-02 11:03:40 -06:00
Luke Street a162c2f840 Fix to_string_in_format_args warning 2025-04-01 23:36:47 -06:00
Luke Street a474b27d55 Update dependencies 2025-04-01 23:33:17 -06:00
Luke Street 3d7f2b70dc Replace some git dependencies with cargo registry 2025-04-01 22:56:16 -06:00
Luke Street fe886f862d ci: Auto cargo publish / npm publish 2025-04-01 21:49:01 -06:00
Luke Street 2bcbc34850 wasm: Improve API error handling 2025-04-01 21:45:13 -06:00
Steven Casper 9b557e4c8e Limit left-panel scrollview to the file tree (#185)
* Limit left-panel scrollview to the file tree

Removes the redundant build button

* Expand ScrollArea to full side panel width

* Use auto_shrink(false) instead of set_width

---------

Co-authored-by: Luke Street <luke@street.dev>
2025-04-01 12:35:17 -06:00
LagoLunatic b9ba5796ed PPC: Fix pooled relocation addends being added twice sometimes (#184) 2025-03-30 22:00:36 -06:00
LagoLunatic e101610416 ARM: Fix subtract with overflow error when no mapping symbol at address 0 (#183) 2025-03-30 22:00:06 -06:00
LagoLunatic 196c003a92 Reimplement colorized data relocation hover diffs (#182)
* Reimplement colorized data relocation hover diffs

* Fix objdiff-wasm build

Data diffing doesn't seem to be fully implemented in objdiff-wasm yet, so just putting placeholders in so it compiles.

* Reloc hover: Add separators, override special color too
2025-03-28 21:48:14 -06:00
Luke Street 7b00a9e9f2 wasm: Cache objects via data hash (XXH3) 2025-03-21 08:27:19 -06:00
Luke Street 311de887ec Update test snapshot 2025-03-19 19:00:57 -06:00
Luke Street 485b259c32 Apply clippy suggestion 2025-03-19 19:00:01 -06:00
Luke Street d8fdfaa2c0 Fix no_std build 2025-03-19 18:57:41 -06:00
Luke Street 2612cda1fb objdiff-cli report: Skip unknown sections
Regression in v3.0.0-alpha.1

Fixes #171
2025-03-19 18:54:34 -06:00
Luke Street bc46e17824 x86_64: Fix relocation placement in instruction 2025-03-19 18:54:34 -06:00
Luke Street e735adbd3d x86: Support inline data for jumptables 2025-03-19 18:54:34 -06:00
Luke Street 6768df9d80 Version v3.0.0-beta.4 2025-03-18 21:36:37 -06:00
Luke Street eaba2391e0 arm: Fix thumb & data decoding, add tests 2025-03-18 21:26:28 -06:00
Luke Street bd8f5ede23 Add pre-commit hooks 2025-03-18 21:26:28 -06:00
Luke Street acf46c6b54 Fix no_std build 2025-03-14 09:49:31 -06:00
Luke Street 9358d8ec60 mips: Add C++ symbol demangling (CW & modern GCC) 2025-03-14 09:44:15 -06:00
Luke Street 809e2ffddc cargo fmt 2025-03-11 21:43:32 -06:00
Luke Street 87fa29e8b0 objdiff-wasm: Fix symbol filtering
regex crate needed the `unicode-case` feature
2025-03-11 21:42:14 -06:00
Luke Street 42d4c38079 mips: Ignore .NON_MATCHING functions from INLINE_ASM macros 2025-03-11 21:39:17 -06:00
Luke Street fa26200ed7 Silence warning 2025-03-10 22:22:05 -06:00
Luke Street a0e7f9bc37 Fix x86 mov relocations with uint32 2025-03-10 22:09:58 -06:00
Luke Street 5898d7aebf Fix section ordering with many same-named sections
Before, this was comparing, for example, `.text-2`
with `.text-10` with standard string comparison,
yielding `.text-10` before `.text-2`.

Instead, this simply uses a stable sort by name,
which preserves the relative ordering of sections.
2025-03-10 21:51:54 -06:00
Luke Street ffb38d1bb0 Fix cargo deny advisories 2025-03-09 22:59:42 -06:00
Luke Street d56dda72f0 Version v3.0.0-beta.2 2025-03-09 22:56:28 -06:00
Luke Street c6971f3f2d Add initial support for x86-64 relocations 2025-03-09 22:51:43 -06:00
Luke Street 3965a035fa objdiff-cli diff: Show build errors/log 2025-03-09 22:51:43 -06:00
Luke Street f1fc29f77e Split report changes into separate proto 2025-03-08 14:39:15 -07:00
Luke Street 7c4f1c5d13 Fix left/right arch mismatches in diff code 2025-03-08 10:44:44 -07:00
Luke Street fa4a6cadbb Downgrade objdiff-wasm Rust edition temporarily 2025-03-04 23:27:17 -07:00
Luke Street 799971d54e Migrate to Rust edition 2024 2025-03-04 22:31:38 -07:00
Luke Street 8eef37e8df Version v3.0.0-beta.1 2025-03-04 22:24:55 -07:00
Luke Street 5f36916087 Fix unintended unwrap in load_font_if_needed 2025-03-04 22:24:20 -07:00
Luke Street ee667a2dde Update config-schema.json: PPC -> PowerPC 2025-03-04 22:23:45 -07:00
LagoLunatic cf5fc54cfa PPC: Reimplement pooled data reference calculation (#167)
* PPC: Calculate pooled relocations

Reimplements #140

The relocations are now generated when the object is first read in `parse`, right after the real relocations are read.

`resolve_relocation` was changed to take `obj.symbols` instead of `obj` as an argument, because `obj` itself doesn't exist yet at the time the relocations are being read.

* Improve readability of PPC pool relocs code

* Fix regression causing extern pool relocs to be ignored

* Fix showing incorrect diff when diffing weak stripped symbol with an addend

This is a regression that was introduced by #158 diffing addends in addition to symbol names. But it's not really a bug in that PR, rather it seems like I simply never added the offset into the addend when creating a fake pool relocation for an extern symbol. So this commit fixes that root issue instead.

* Add PPC "Calculate pooled data references" option

* Fix objdiff-wasm compilation errors

* Update PPC test snapshots
2025-03-04 20:40:34 -07:00
Luke Street 1cdfa1e857 Update rabbitizer & utilize use_dollar option 2025-03-04 09:38:59 -07:00
Luke Street 3f157f33a5 Reorder tooltip/context items slightly 2025-03-04 09:10:54 -07:00
LagoLunatic a1ea2919f8 Reimplement function data value tooltips, function data value diffing, and data relocation diffing (#166)
* Show data literal values on instruction hover

Reimplements #108

* Show reloc diffs in func view when data's content differs

Reimplements #153

* Data diff view: Show relocs on hover and in context menu

This reimplements #154

Note that colorizing the text depending on the kind of diff has still not been reimplemented yet

* Fix up some comments
2025-03-04 08:56:46 -07:00
Luke Street 9c31c82a37 cargo fmt 2025-03-03 21:08:36 -07:00
Luke Street 4f34dfa194 Don't infer sizes for labels within another symbol 2025-03-03 21:01:22 -07:00
106 changed files with 17865 additions and 1494 deletions
+2
View File
@@ -0,0 +1,2 @@
[*.md]
trim_trailing_whitespace = false
+90 -13
View File
@@ -4,10 +4,14 @@ on:
pull_request:
push:
paths-ignore:
- '*.md'
- 'LICENSE*'
- "*.md"
- "LICENSE*"
workflow_dispatch:
permissions:
# For npm publish provenance
id-token: write
env:
BUILD_PROFILE: release-lto
CARGO_INCREMENTAL: 0
@@ -32,9 +36,9 @@ jobs:
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Cargo check
run: cargo check --features all
run: cargo check --all-targets --all-features
- name: Cargo clippy
run: cargo clippy --features all
run: cargo clippy --all-targets --all-features
fmt:
name: Format
@@ -159,7 +163,7 @@ jobs:
key: ${{ matrix.target }}
- name: Cargo build
run: >
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
@@ -226,7 +230,7 @@ jobs:
key: ${{ matrix.target }}
- name: Cargo build
run: >
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
@@ -249,22 +253,30 @@ jobs:
components: rust-src
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm -C objdiff-wasm install
run: npm -C objdiff-wasm ci
- name: Build
run: npm -C objdiff-wasm run build
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: wasm
path: objdiff-wasm/dist/
if-no-files-found: error
release:
name: Release
check-version:
name: Check package versions
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [ build-cli, build-gui ]
permissions:
contents: write
needs: [ build-cli, build-gui, build-wasm ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check git tag against Cargo version
- name: Check git tag against package versions
shell: bash
run: |
set -eou pipefail
@@ -276,9 +288,24 @@ jobs:
echo "::error::Git tag doesn't match the Cargo version! ($tag != $version)"
exit 1
fi
version="v$(jq -r .version objdiff-wasm/package.json)"
if [ "$tag" != "$version" ]; then
echo "::error::Git tag doesn't match the npm version! ($tag != $version)"
exit 1
fi
release-github:
name: Release (GitHub)
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [ check-version ]
permissions:
contents: write
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
pattern: objdiff-*
path: artifacts
- name: Rename artifacts
working-directory: artifacts
@@ -308,3 +335,53 @@ jobs:
files: out/*
draft: true
generate_release_notes: true
release-cargo:
name: Release (Cargo)
if: 'false' # TODO re-enable when all dependencies are published
runs-on: ubuntu-latest
needs: [ check-version ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish -p objdiff-core
release-npm:
name: Release (npm)
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [ check-version ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: lts/*
registry-url: 'https://registry.npmjs.org'
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: wasm
path: objdiff-wasm/dist
- name: Publish
working-directory: objdiff-wasm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -euo pipefail
version=$(jq -r '.version' package.json)
tag="latest"
# Check for prerelease by looking for a dash
case "$version" in
*-*)
tag=$(echo "$version" | sed -e 's/^[^-]*-//' -e 's/\..*$//')
;;
esac
echo "Publishing version $version with tag '$tag'..."
npm publish --provenance --access public --tag "$tag"
+36
View File
@@ -0,0 +1,36 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
- id: end-of-file-fixer
- id: fix-byte-order-marker
- id: check-yaml
- id: check-added-large-files
- repo: local
hooks:
- id: cargo-fmt
name: cargo fmt
description: Run cargo fmt on all project files.
language: system
entry: cargo
args: ["+nightly", "fmt", "--all"]
pass_filenames: false
- id: cargo clippy
name: cargo clippy
description: Run cargo clippy on all project files.
language: system
entry: cargo
args: ["+nightly", "clippy", "--all-targets", "--all-features"]
pass_filenames: false
- id: cargo-deny
name: cargo deny
description: Run cargo deny on all project files.
language: system
entry: cargo
args: ["deny", "check"]
pass_filenames: false
always_run: true
Generated
+561 -340
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -5,7 +5,7 @@ members = [
"objdiff-gui",
"objdiff-wasm",
]
resolver = "2"
resolver = "3"
[profile.release-lto]
inherits = "release"
@@ -14,9 +14,9 @@ strip = "debuginfo"
codegen-units = 1
[workspace.package]
version = "3.0.0-alpha.2"
version = "3.0.0-beta.6"
authors = ["Luke Street <luke@street.dev>"]
edition = "2021"
edition = "2024"
license = "MIT OR Apache-2.0"
repository = "https://github.com/encounter/objdiff"
rust-version = "1.82"
rust-version = "1.85"
+19 -4
View File
@@ -143,15 +143,15 @@ If not specified, objdiff will use the default patterns listed above.
`units` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
> `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.
>
>
> `target_path`: Path to the "target" or "expected" object from the project root.
> This object is the **intended result** of the match.
>
>
> `base_path`: Path to the "base" or "actual" object from the project root.
> This object is built from the **current source code**.
>
>
> `metadata.auto_generated` _(optional)_: Hides the object from the object list, but still includes it in reports.
>
>
> `metadata.complete` _(optional)_: Marks the object as "complete" (or "linked") in the object list.
> This is useful for marking objects that are fully decompiled. A value of `false` will mark the object as "incomplete".
@@ -173,6 +173,21 @@ $ cargo install --locked --git https://github.com/encounter/objdiff.git objdiff-
The binaries will be installed to `~/.cargo/bin` as `objdiff` and `objdiff-cli`.
## Installing `pre-commit`
When contributing, it's recommended to install `pre-commit` to automatically run the linter and formatter before a commit.
[`uv`](https://github.com/astral-sh/uv#installation) is recommended to manage Python version and tools.
Rust nightly is required for `cargo +nightly fmt` and `cargo +nightly clippy`.
```shell
$ cargo install --locked cargo-deny
$ rustup toolchain install nightly
$ uv tool install pre-commit
$ pre-commit install
```
## License
Licensed under either of
+1 -2
View File
@@ -73,6 +73,7 @@ ignore = [
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
{ id = "RUSTSEC-2024-0436", reason = "Unmaintained paste 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.
@@ -239,8 +240,6 @@ allow-git = []
[sources.allow-org]
# github.com organizations to allow git sources for
github = [
"CelestialAmber", # cwextab, rlwinmdec
"Decompollaborate", # rabbitizer
"enarx", # flagset
"encounter",
]
+1 -1
View File
@@ -4,7 +4,7 @@
//! For now, this only adds a --version/-V option which causes early-exit.
use std::ffi::OsStr;
use argp::{parser::ParseGlobalOptions, EarlyExit, FromArgs, TopLevelCommand};
use argp::{EarlyExit, FromArgs, TopLevelCommand, parser::ParseGlobalOptions};
struct ArgsOrVersion<T>(T)
where T: FromArgs;
+28 -45
View File
@@ -1,42 +1,37 @@
use std::{
io::stdout,
mem,
str::FromStr,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
atomic::{AtomicBool, Ordering},
},
task::{Wake, Waker},
time::Duration,
};
use anyhow::{anyhow, bail, Context, Result};
use anyhow::{Context, Result, anyhow, bail};
use argp::FromArgs;
use crossterm::{
event,
event::{DisableMouseCapture, EnableMouseCapture},
terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, SetTitle,
EnterAlternateScreen, LeaveAlternateScreen, SetTitle, disable_raw_mode, enable_raw_mode,
},
};
use objdiff_core::{
bindings::diff::DiffResult,
build::{
watcher::{create_watcher, Watcher},
BuildConfig,
BuildConfig, BuildStatus,
watcher::{Watcher, create_watcher},
},
config::{
build_globset,
ProjectConfig, ProjectObject, ProjectObjectMetadata, build_globset,
path::{check_path_buf, platform_path, platform_path_serde_option},
ProjectConfig, ProjectObject, ProjectObjectMetadata,
},
diff::{
self, ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig, MappingConfig,
ObjectDiff,
},
diff::{self, DiffObjConfig, MappingConfig, ObjectDiff},
jobs::{
objdiff::{start_build, ObjDiffConfig},
Job, JobQueue, JobResult,
objdiff::{ObjDiffConfig, start_build},
},
obj::{self, Object},
};
@@ -44,11 +39,12 @@ use ratatui::prelude::*;
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
use crate::{
cmd::apply_config_args,
util::{
output::{write_output, OutputFormat},
output::{OutputFormat, write_output},
term::crossterm_panic_handler,
},
views::{function_diff::FunctionDiffUi, EventControlFlow, EventResult, UiView},
views::{EventControlFlow, EventResult, UiView, function_diff::FunctionDiffUi},
};
#[derive(FromArgs, PartialEq, Debug)]
@@ -184,28 +180,7 @@ pub fn run(args: Args) -> Result<()> {
fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)> {
let mut diff_config = DiffObjConfig::default();
for config in &args.config {
let (key, value) = config.split_once('=').context("--config expects \"key=value\"")?;
let property_id = ConfigPropertyId::from_str(key)
.map_err(|()| anyhow!("Invalid configuration property: {}", key))?;
diff_config.set_property_value_str(property_id, value).map_err(|()| {
let mut options = String::new();
match property_id.kind() {
ConfigPropertyKind::Boolean => {
options = "true, false".to_string();
}
ConfigPropertyKind::Choice(variants) => {
for (i, variant) in variants.iter().enumerate() {
if i > 0 {
options.push_str(", ");
}
options.push_str(variant.value);
}
}
}
anyhow!("Invalid value for {}. Expected one of: {}", property_id.name(), options)
})?;
}
apply_config_args(&mut diff_config, &args.config)?;
let mut mapping_config = MappingConfig {
mappings: Default::default(),
selecting_left: args.selecting_left.clone(),
@@ -252,6 +227,8 @@ pub struct AppState {
pub project_config: Option<ProjectConfig>,
pub target_path: Option<Utf8PlatformPathBuf>,
pub base_path: Option<Utf8PlatformPathBuf>,
pub left_status: Option<BuildStatus>,
pub right_status: Option<BuildStatus>,
pub left_obj: Option<(Object, ObjectDiff)>,
pub right_obj: Option<(Object, ObjectDiff)>,
pub prev_obj: Option<(Object, ObjectDiff)>,
@@ -349,6 +326,8 @@ impl AppState {
JobResult::None => unreachable!("Unexpected JobResult::None"),
JobResult::ObjDiff(result) => {
let result = result.unwrap();
self.left_status = Some(result.first_status);
self.right_status = Some(result.second_status);
self.left_obj = result.first_obj;
self.right_obj = result.second_obj;
self.reload_time = Some(result.time);
@@ -389,6 +368,8 @@ fn run_interactive(
project_config,
target_path,
base_path,
left_status: None,
right_status: None,
left_obj: None,
right_obj: None,
prev_obj: None,
@@ -426,15 +407,17 @@ fn run_interactive(
let mut result = EventResult { redraw: true, ..Default::default() };
'outer: loop {
if result.redraw {
terminal.draw(|f| loop {
result.redraw = false;
view.draw(&state, f, &mut result);
result.click_xy = None;
if !result.redraw {
break;
terminal.draw(|f| {
loop {
result.redraw = false;
view.draw(&state, f, &mut result);
result.click_xy = None;
if !result.redraw {
break;
}
// Clear buffer on redraw
f.buffer_mut().reset();
}
// Clear buffer on redraw
f.buffer_mut().reset();
})?;
}
loop {
+31
View File
@@ -1,2 +1,33 @@
pub mod diff;
pub mod report;
use std::str::FromStr;
use anyhow::{Context, Result, anyhow};
use objdiff_core::diff::{ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig};
pub fn apply_config_args(diff_config: &mut DiffObjConfig, args: &[String]) -> Result<()> {
for config in args {
let (key, value) = config.split_once('=').context("--config expects \"key=value\"")?;
let property_id = ConfigPropertyId::from_str(key)
.map_err(|()| anyhow!("Invalid configuration property: {}", key))?;
diff_config.set_property_value_str(property_id, value).map_err(|()| {
let mut options = String::new();
match property_id.kind() {
ConfigPropertyKind::Boolean => {
options = "true, false".to_string();
}
ConfigPropertyKind::Choice(variants) => {
for (i, variant) in variants.iter().enumerate() {
if i > 0 {
options.push_str(", ");
}
options.push_str(variant.value);
}
}
}
anyhow!("Invalid value for {}. Expected one of: {}", property_id.name(), options)
})?;
}
Ok(())
}
+27 -25
View File
@@ -1,12 +1,11 @@
use std::{collections::HashSet, fs::File, io::Read, time::Instant};
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use argp::FromArgs;
use objdiff_core::{
bindings::report::{
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, Report,
ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
REPORT_VERSION,
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, REPORT_VERSION,
Report, ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
},
config::path::platform_path,
diff, obj,
@@ -18,8 +17,8 @@ use tracing::{info, warn};
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
use crate::{
cmd::diff::ObjectConfig,
util::output::{write_output, OutputFormat},
cmd::{apply_config_args, diff::ObjectConfig},
util::output::{OutputFormat, write_output},
};
#[derive(FromArgs, PartialEq, Debug)]
@@ -53,6 +52,9 @@ pub struct GenerateArgs {
#[argp(option, short = 'f')]
/// Output format (json, json-pretty, proto) (default: json)
format: Option<String>,
#[argp(option, short = 'c')]
/// Configuration property (key=value)
config: Vec<String>,
}
#[derive(FromArgs, PartialEq, Debug)]
@@ -81,6 +83,12 @@ pub fn run(args: Args) -> Result<()> {
}
fn generate(args: GenerateArgs) -> Result<()> {
let mut diff_config = diff::DiffObjConfig {
function_reloc_diffs: diff::FunctionRelocDiffs::None,
..Default::default()
};
apply_config_args(&mut diff_config, &args.config)?;
let output_format = OutputFormat::from_option(args.format.as_deref())?;
let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new("."));
info!("Loading project {}", project_dir);
@@ -115,14 +123,15 @@ fn generate(args: GenerateArgs) -> Result<()> {
if args.deduplicate {
// If deduplicating, we need to run single-threaded
for object in &objects {
if let Some(unit) = report_object(object, Some(&mut existing_functions))? {
if let Some(unit) = report_object(object, &diff_config, Some(&mut existing_functions))?
{
units.push(unit);
}
}
} else {
let vec = objects
.par_iter()
.map(|object| report_object(object, None))
.map(|object| report_object(object, &diff_config, None))
.collect::<Result<Vec<Option<ReportUnit>>>>()?;
units = vec.into_iter().flatten().collect();
}
@@ -146,6 +155,7 @@ fn generate(args: GenerateArgs) -> Result<()> {
fn report_object(
object: &ObjectConfig,
diff_config: &diff::DiffObjConfig,
mut existing_functions: Option<&mut HashSet<String>>,
) -> Result<Option<ReportUnit>> {
match (&object.target_path, &object.base_path) {
@@ -159,16 +169,12 @@ fn report_object(
}
_ => {}
}
let diff_config = diff::DiffObjConfig {
function_reloc_diffs: diff::FunctionRelocDiffs::None,
..Default::default()
};
let mapping_config = diff::MappingConfig::default();
let target = object
.target_path
.as_ref()
.map(|p| {
obj::read::read(p.as_ref(), &diff_config)
obj::read::read(p.as_ref(), diff_config)
.with_context(|| format!("Failed to open {}", p))
})
.transpose()?;
@@ -176,12 +182,12 @@ fn report_object(
.base_path
.as_ref()
.map(|p| {
obj::read::read(p.as_ref(), &diff_config)
obj::read::read(p.as_ref(), diff_config)
.with_context(|| format!("Failed to open {}", p))
})
.transpose()?;
let result =
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
diff::diff_objs(target.as_ref(), base.as_ref(), None, diff_config, &mapping_config)?;
let metadata = ReportUnitMetadata {
complete: object.metadata.complete,
@@ -203,14 +209,13 @@ fn report_object(
for ((section_idx, section), section_diff) in
obj.sections.iter().enumerate().zip(&obj_diff.sections)
{
if section.kind == SectionKind::Unknown {
continue;
}
let section_match_percent = section_diff.match_percent.unwrap_or_else(|| {
// Support cases where we don't have a target object,
// assume complete means 100% match
if object.complete.unwrap_or(false) {
100.0
} else {
0.0
}
if object.complete.unwrap_or(false) { 100.0 } else { 0.0 }
});
sections.push(ReportItem {
name: section.name.clone(),
@@ -237,6 +242,7 @@ fn report_object(
if symbol.section != Some(section_idx)
|| symbol.size == 0
|| symbol.flags.contains(SymbolFlag::Hidden)
|| symbol.flags.contains(SymbolFlag::Ignored)
{
continue;
}
@@ -251,11 +257,7 @@ fn report_object(
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
// Support cases where we don't have a target object,
// assume complete means 100% match
if object.complete.unwrap_or(false) {
100.0
} else {
0.0
}
if object.complete.unwrap_or(false) { 100.0 } else { 0.0 }
});
measures.fuzzy_match_percent += match_percent * symbol.size as f32;
measures.total_code += symbol.size;
+1 -1
View File
@@ -17,7 +17,7 @@ use anyhow::{Error, Result};
use argp::{FromArgValue, FromArgs};
use enable_ansi_support::enable_ansi_support;
use supports_color::Stream;
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
use tracing_subscriber::{EnvFilter, filter::LevelFilter};
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
enum LogLevel {
+1 -1
View File
@@ -5,7 +5,7 @@ use std::{
path::Path,
};
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use tracing::info;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
+1 -1
View File
@@ -3,7 +3,7 @@ use std::{io::stdout, panic};
use crossterm::{
cursor::Show,
event::DisableMouseCapture,
terminal::{disable_raw_mode, LeaveAlternateScreen},
terminal::{LeaveAlternateScreen, disable_raw_mode},
};
pub fn crossterm_panic_handler() {
+27 -4
View File
@@ -1,18 +1,19 @@
use core::cmp::Ordering;
use anyhow::{bail, Result};
use anyhow::Result;
use crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers, MouseButton, MouseEventKind};
use objdiff_core::{
build::BuildStatus,
diff::{
display::{display_row, DiffText, DiffTextColor, HighlightKind},
DiffObjConfig, FunctionRelocDiffs, InstructionDiffKind, ObjectDiff, SymbolDiff,
display::{DiffText, DiffTextColor, HighlightKind, display_row},
},
obj::Object,
};
use ratatui::{
Frame,
prelude::*,
widgets::{Block, Borders, Clear, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState},
Frame,
};
use super::{EventControlFlow, EventResult, UiView};
@@ -126,6 +127,11 @@ impl UiView for FunctionDiffUi {
);
max_width = max_width.max(text.width());
left_text = Some(text);
} else if let Some(status) = &state.left_status {
let mut text = Text::default();
self.print_build_status(&mut text, status);
max_width = max_width.max(text.width());
left_text = Some(text);
}
let mut right_text = None;
@@ -155,6 +161,11 @@ impl UiView for FunctionDiffUi {
let rect = content_chunks[1].inner(Margin::new(1, 1));
self.print_margin(&mut text, symbol_diff, rect);
margin_text = Some(text);
} else if let Some(status) = &state.right_status {
let mut text = Text::default();
self.print_build_status(&mut text, status);
max_width = max_width.max(text.width());
right_text = Some(text);
}
let mut prev_text = None;
@@ -453,7 +464,7 @@ impl UiView for FunctionDiffUi {
}
(Some((_l, _ls, ld)), None) => ld.instruction_rows.len(),
(None, Some((_r, _rs, rd))) => rd.instruction_rows.len(),
(None, None) => bail!("Symbol not found: {}", self.symbol_name),
(None, None) => 0,
};
self.left_sym = left_sym;
self.right_sym = right_sym;
@@ -596,6 +607,18 @@ impl FunctionDiffUi {
}
}
}
fn print_build_status<'a>(&self, out: &mut Text<'a>, status: &'a BuildStatus) {
if !status.cmdline.is_empty() {
out.lines.push(Line::styled(status.cmdline.clone(), Style::new().fg(Color::LightBlue)));
}
for line in status.stdout.lines() {
out.lines.push(Line::styled(line, Style::new().fg(Color::White)));
}
for line in status.stderr.lines() {
out.lines.push(Line::styled(line, Style::new().fg(Color::Red)));
}
}
}
pub const COLOR_ROTATION: [Color; 7] = [
+14 -6
View File
@@ -27,6 +27,7 @@ all = [
"mips",
"ppc",
"x86",
"superh"
]
# Implicit, used to check if any arch is enabled
any-arch = [
@@ -75,12 +76,15 @@ std = [
"object/std",
"prost?/std",
"serde?/std",
"similar?/std",
"typed-path?/std",
"dep:filetime",
"dep:memmap2",
]
mips = [
"any-arch",
"dep:cpp_demangle",
"dep:cwdemangle",
"dep:rabbitizer",
]
ppc = [
@@ -108,6 +112,9 @@ arm64 = [
"dep:yaxpeax-arch",
"dep:yaxpeax-arm",
]
superh = [
"any-arch",
]
[package.metadata.docs.rs]
features = ["all"]
@@ -120,12 +127,12 @@ itertools = { version = "0.14", default-features = false, features = ["use_alloc
log = { version = "0.4", default-features = false, optional = true }
memmap2 = { version = "0.9", optional = true }
num-traits = { version = "0.2", default-features = false, optional = true }
object = { version = "0.36", default-features = false, features = ["read_core", "elf", "pe"] }
object = { git = "https://github.com/gimli-rs/object", rev = "a74579249e21ab8fcd3a86be588de336f18297cb", default-features = false, features = ["read_core", "elf", "pe"] }
pbjson = { version = "0.7", default-features = false, optional = true }
prost = { version = "0.13", default-features = false, features = ["prost-derive"], optional = true }
regex = { version = "1.11", default-features = false, features = [], optional = true }
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
similar = { version = "2.7", default-features = false, optional = true, git = "https://github.com/encounter/similar.git", branch = "no_std" }
similar = { version = "2.7", default-features = false, features = ["hashbrown"], optional = true, git = "https://github.com/encounter/similar.git", branch = "no_std" }
typed-path = { version = "0.10", default-features = false, optional = true }
# config
@@ -138,12 +145,12 @@ gimli = { version = "0.31", default-features = false, features = ["read"], optio
# ppc
cwdemangle = { version = "1.0", optional = true }
cwextab = { version = "1.0", optional = true, git = "https://github.com/CelestialAmber/cwextab.git" }
cwextab = { version = "1.0", optional = true }
ppc750cl = { version = "0.3", optional = true }
rlwinmdec = { version = "1.1", optional = true, git = "https://github.com/CelestialAmber/rlwinmdec.git" }
rlwinmdec = { version = "1.1", optional = true }
# mips
rabbitizer = { git = "https://github.com/Decompollaborate/rabbitizer.git", branch = "🦀", default-features = false, features = ["all_extensions"], optional = true }
rabbitizer = { version = "2.0.0-alpha.1", default-features = false, features = ["all_extensions"], optional = true }
# x86
cpp_demangle = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
@@ -162,8 +169,9 @@ yaxpeax-arm = { version = "0.3", default-features = false, optional = true }
notify = { version = "8.0.0", optional = true }
notify-debouncer-full = { version = "0.5.0", optional = true }
shell-escape = { version = "0.1", optional = true }
tempfile = { version = "3.17", optional = true }
tempfile = { version = "3.19", optional = true }
time = { version = "0.3", optional = true }
encoding_rs = "0.8.35"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", optional = true }
+9 -1
View File
@@ -19,7 +19,15 @@ fn compile_protos() {
.map(|m| m.modified().unwrap())
.unwrap_or(std::time::SystemTime::UNIX_EPOCH);
let mut run_protoc = false;
let proto_files = vec![root.join("report.proto")];
let proto_files = root
.read_dir()
.unwrap()
.filter_map(|e| {
let e = e.unwrap();
let path = e.path();
(path.extension() == Some(std::ffi::OsStr::new("proto"))).then_some(path)
})
.collect::<Vec<_>>();
for proto_file in &proto_files {
println!("cargo:rerun-if-changed={}", proto_file.display());
let mtime = match std::fs::metadata(proto_file) {
+15 -1
View File
@@ -194,6 +194,13 @@
"name": "Register '$' prefix",
"description": "Display MIPS register names with a '$' prefix."
},
{
"id": "ppc.calculatePoolRelocations",
"type": "boolean",
"default": true,
"name": "Calculate pooled data references",
"description": "Display pooled data references in functions as fake relocations."
},
{
"id": "x86.formatter",
"type": "choice",
@@ -253,6 +260,13 @@
"mips.registerPrefix"
]
},
{
"id": "ppc",
"name": "PowerPC",
"properties": [
"ppc.calculatePoolRelocations"
]
},
{
"id": "x86",
"name": "x86",
@@ -261,4 +275,4 @@
]
}
]
}
}
+59
View File
@@ -0,0 +1,59 @@
syntax = "proto3";
import "report.proto";
package objdiff.report;
// A pair of reports to compare and generate changes
message ChangesInput {
// The previous report
Report from = 1;
// The current report
Report to = 2;
}
// Changes between two reports
message Changes {
// The progress info for the previous report
Measures from = 1;
// The progress info for the current report
Measures to = 2;
// Units that changed
repeated ChangeUnit units = 3;
}
// A changed unit
message ChangeUnit {
// The name of the unit
string name = 1;
// The previous progress info (omitted if new)
optional Measures from = 2;
// The current progress info (omitted if removed)
optional Measures to = 3;
// Sections that changed
repeated ChangeItem sections = 4;
// Functions that changed
repeated ChangeItem functions = 5;
// Extra metadata for this unit
optional ReportUnitMetadata metadata = 6;
}
// A changed section or function
message ChangeItem {
// The name of the item
string name = 1;
// The previous progress info (omitted if new)
optional ChangeItemInfo from = 2;
// The current progress info (omitted if removed)
optional ChangeItemInfo to = 3;
// Extra metadata for this item
optional ReportItemMetadata metadata = 4;
}
// Progress info for a section or function
message ChangeItemInfo {
// The overall match percent for this item
float fuzzy_match_percent = 1;
// The size of the item in bytes
uint64 size = 2;
}
Binary file not shown.

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