mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f7c3501eae | |||
| 07ef93f16a | |||
| 8e8ab6bef8 | |||
| e865f3d598 | |||
| 2b13e9886a | |||
| 1750af736a | |||
| 731b604c24 | |||
| 2d643eb071 | |||
| 0c48d711c7 | |||
| 34220a8e26 | |||
| 0c9e5526d4 | |||
| 3db0727469 | |||
| 8b5bf21f38 | |||
| b77df77000 | |||
| 7e08f9715b | |||
| 3c05852d00 | |||
| a51ff44be1 | |||
| d225cac205 | |||
| 737b3782db | |||
| 1d782243e0 | |||
| a1499f475d | |||
| f263e490e3 | |||
| d0e6c5c057 | |||
| e1c51ac297 | |||
| 39b1b49985 | |||
| 6c7160ab7e | |||
| 644d4762f0 | |||
| b40fae5140 | |||
| fbf85632ab | |||
| 73a89d2768 | |||
| a162c2f840 | |||
| a474b27d55 | |||
| 3d7f2b70dc | |||
| fe886f862d | |||
| 2bcbc34850 | |||
| 9b557e4c8e | |||
| b9ba5796ed | |||
| e101610416 | |||
| 196c003a92 | |||
| 7b00a9e9f2 | |||
| 311de887ec | |||
| 485b259c32 | |||
| d8fdfaa2c0 | |||
| 2612cda1fb | |||
| bc46e17824 | |||
| e735adbd3d | |||
| 6768df9d80 | |||
| eaba2391e0 | |||
| bd8f5ede23 | |||
| acf46c6b54 | |||
| 9358d8ec60 | |||
| 809e2ffddc | |||
| 87fa29e8b0 | |||
| 42d4c38079 | |||
| fa26200ed7 | |||
| a0e7f9bc37 | |||
| 5898d7aebf | |||
| ffb38d1bb0 | |||
| d56dda72f0 | |||
| c6971f3f2d | |||
| 3965a035fa | |||
| f1fc29f77e | |||
| 7c4f1c5d13 |
+4
-5
@@ -1,5 +1,4 @@
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
linker = "rust-lld"
|
||||
|
||||
[target.aarch64-pc-windows-msvc]
|
||||
linker = "rust-lld"
|
||||
# statically link the C runtime so the executable does not depend on
|
||||
# that shared/dynamic library.
|
||||
[target.'cfg(all(target_env = "msvc", target_os = "windows"))']
|
||||
rustflags = ["-C", "target-feature=+crt-static"]
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -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"
|
||||
|
||||
@@ -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
+799
-496
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -14,7 +14,7 @@ strip = "debuginfo"
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.package]
|
||||
version = "3.0.0-beta.1"
|
||||
version = "3.0.0-beta.8"
|
||||
authors = ["Luke Street <luke@street.dev>"]
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@@ -16,11 +16,12 @@ Features:
|
||||
|
||||
Supports:
|
||||
|
||||
- PowerPC 750CL (GameCube, Wii)
|
||||
- MIPS (N64, PS1, PS2, PSP)
|
||||
- x86 (COFF only at the moment)
|
||||
- ARM (GBA, DS, 3DS)
|
||||
- ARM64 (Switch, experimental)
|
||||
- ARM64 (Switch)
|
||||
- MIPS (N64, PS1, PS2, PSP)
|
||||
- PowerPC (GameCube, Wii)
|
||||
- SuperH (Saturn, Dreamcast)
|
||||
- x86 (COFF only)
|
||||
|
||||
See [Usage](#usage) for more information.
|
||||
|
||||
@@ -143,15 +144,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 +174,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
|
||||
|
||||
@@ -175,6 +175,10 @@
|
||||
"type": "boolean",
|
||||
"description": "If true, objdiff will run the build command with the context file as an argument to generate it.",
|
||||
"default": false
|
||||
},
|
||||
"preset_id": {
|
||||
"type": "number",
|
||||
"description": "The decomp.me preset ID to use for the scratch.\nCompiler and flags in the config will take precedence over the preset, but the preset is useful for organizational purposes."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -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.
|
||||
@@ -102,6 +103,7 @@ allow = [
|
||||
"0BSD",
|
||||
"OFL-1.1",
|
||||
"Ubuntu-font-1.0",
|
||||
"CDLA-Permissive-2.0",
|
||||
]
|
||||
# The confidence threshold for detecting a license from license text.
|
||||
# The higher the value, the more closely the license text must be to the
|
||||
@@ -239,8 +241,6 @@ allow-git = []
|
||||
[sources.allow-org]
|
||||
# github.com organizations to allow git sources for
|
||||
github = [
|
||||
"CelestialAmber", # cwextab, rlwinmdec
|
||||
"Decompollaborate", # rabbitizer
|
||||
"enarx", # flagset
|
||||
"encounter",
|
||||
]
|
||||
|
||||
@@ -28,7 +28,7 @@ supports-color = "3.0"
|
||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
typed-path = "0.10"
|
||||
typed-path = "0.11"
|
||||
|
||||
[target.'cfg(target_env = "musl")'.dependencies]
|
||||
mimalloc = "0.1"
|
||||
|
||||
+10
-28
@@ -1,7 +1,6 @@
|
||||
use std::{
|
||||
io::stdout,
|
||||
mem,
|
||||
str::FromStr,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
@@ -22,17 +21,14 @@ use crossterm::{
|
||||
use objdiff_core::{
|
||||
bindings::diff::DiffResult,
|
||||
build::{
|
||||
BuildConfig,
|
||||
BuildConfig, BuildStatus,
|
||||
watcher::{Watcher, create_watcher},
|
||||
},
|
||||
config::{
|
||||
ProjectConfig, ProjectObject, ProjectObjectMetadata, build_globset,
|
||||
path::{check_path_buf, platform_path, platform_path_serde_option},
|
||||
},
|
||||
diff::{
|
||||
self, ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig, MappingConfig,
|
||||
ObjectDiff,
|
||||
},
|
||||
diff::{self, DiffObjConfig, MappingConfig, ObjectDiff},
|
||||
jobs::{
|
||||
Job, JobQueue, JobResult,
|
||||
objdiff::{ObjDiffConfig, start_build},
|
||||
@@ -43,6 +39,7 @@ use ratatui::prelude::*;
|
||||
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
||||
|
||||
use crate::{
|
||||
cmd::apply_config_args,
|
||||
util::{
|
||||
output::{OutputFormat, write_output},
|
||||
term::crossterm_panic_handler,
|
||||
@@ -183,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(),
|
||||
@@ -251,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)>,
|
||||
@@ -348,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);
|
||||
@@ -388,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,
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use tracing::{info, warn};
|
||||
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
||||
|
||||
use crate::{
|
||||
cmd::diff::ObjectConfig,
|
||||
cmd::{apply_config_args, diff::ObjectConfig},
|
||||
util::output::{OutputFormat, write_output},
|
||||
};
|
||||
|
||||
@@ -52,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)]
|
||||
@@ -80,6 +83,15 @@ pub fn run(args: Args) -> Result<()> {
|
||||
}
|
||||
|
||||
fn generate(args: GenerateArgs) -> Result<()> {
|
||||
let mut diff_config = diff::DiffObjConfig {
|
||||
function_reloc_diffs: diff::FunctionRelocDiffs::None,
|
||||
combine_data_sections: true,
|
||||
combine_text_sections: true,
|
||||
ppc_calculate_pool_relocations: false,
|
||||
..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);
|
||||
@@ -114,14 +126,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();
|
||||
}
|
||||
@@ -145,6 +158,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) {
|
||||
@@ -158,16 +172,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()?;
|
||||
@@ -175,12 +185,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,
|
||||
@@ -202,6 +212,9 @@ 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
|
||||
@@ -215,6 +228,7 @@ fn report_object(
|
||||
demangled_name: None,
|
||||
virtual_address: section.virtual_address,
|
||||
}),
|
||||
address: None,
|
||||
});
|
||||
|
||||
match section.kind {
|
||||
@@ -232,6 +246,7 @@ fn report_object(
|
||||
if symbol.section != Some(section_idx)
|
||||
|| symbol.size == 0
|
||||
|| symbol.flags.contains(SymbolFlag::Hidden)
|
||||
|| symbol.flags.contains(SymbolFlag::Ignored)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -261,6 +276,7 @@ fn report_object(
|
||||
demangled_name: symbol.demangled_name.clone(),
|
||||
virtual_address: symbol.virtual_address,
|
||||
}),
|
||||
address: symbol.address.checked_sub(section.address),
|
||||
});
|
||||
if match_percent == 100.0 {
|
||||
measures.matched_functions += 1;
|
||||
@@ -268,6 +284,16 @@ fn report_object(
|
||||
measures.total_functions += 1;
|
||||
}
|
||||
}
|
||||
sections.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
let reverse_fn_order = object.metadata.reverse_fn_order.unwrap_or(false);
|
||||
functions.sort_by(|a, b| {
|
||||
if reverse_fn_order {
|
||||
b.address.unwrap_or(0).cmp(&a.address.unwrap_or(0))
|
||||
} else {
|
||||
a.address.unwrap_or(u64::MAX).cmp(&b.address.unwrap_or(u64::MAX))
|
||||
}
|
||||
.then_with(|| a.size.cmp(&b.size))
|
||||
});
|
||||
if metadata.complete.unwrap_or(false) {
|
||||
measures.complete_code = measures.total_code;
|
||||
measures.complete_data = measures.total_data;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use core::cmp::Ordering;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use anyhow::Result;
|
||||
use crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers, MouseButton, MouseEventKind};
|
||||
use objdiff_core::{
|
||||
build::BuildStatus,
|
||||
diff::{
|
||||
DiffObjConfig, FunctionRelocDiffs, InstructionDiffKind, ObjectDiff, SymbolDiff,
|
||||
display::{DiffText, DiffTextColor, HighlightKind, display_row},
|
||||
@@ -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;
|
||||
@@ -439,11 +450,11 @@ impl UiView for FunctionDiffUi {
|
||||
|
||||
fn reload(&mut self, state: &AppState) -> Result<()> {
|
||||
let left_sym =
|
||||
state.left_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name));
|
||||
state.left_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
|
||||
let right_sym =
|
||||
state.right_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name));
|
||||
state.right_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
|
||||
let prev_sym =
|
||||
state.prev_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name));
|
||||
state.prev_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
|
||||
self.num_rows = match (
|
||||
get_symbol(state.left_obj.as_ref(), left_sym),
|
||||
get_symbol(state.right_obj.as_ref(), right_sym),
|
||||
@@ -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] = [
|
||||
@@ -627,12 +650,3 @@ fn get_symbol(
|
||||
let sym = sym?;
|
||||
Some((obj, sym, &diff.symbols[sym]))
|
||||
}
|
||||
|
||||
fn find_function(obj: &Object, name: &str) -> Option<usize> {
|
||||
for (symbol_idx, symbol) in obj.symbols.iter().enumerate() {
|
||||
if symbol.name == name {
|
||||
return Some(symbol_idx);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
+17
-9
@@ -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,13 +127,13 @@ 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" }
|
||||
typed-path = { version = "0.10", default-features = false, optional = true }
|
||||
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.11", default-features = false, optional = true }
|
||||
|
||||
# config
|
||||
globset = { version = "0.4", default-features = false, optional = true }
|
||||
@@ -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 }
|
||||
@@ -151,7 +158,7 @@ iced-x86 = { version = "1.21", default-features = false, features = ["decoder",
|
||||
msvc-demangler = { version = "0.11", optional = true }
|
||||
|
||||
# arm
|
||||
unarm = { version = "1.7", optional = true }
|
||||
unarm = { version = "1.8", optional = true }
|
||||
arm-attr = { version = "0.2", optional = true }
|
||||
|
||||
# arm64
|
||||
@@ -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 }
|
||||
@@ -192,4 +200,4 @@ syn = { version = "2.0", optional = true }
|
||||
[dev-dependencies]
|
||||
# Enable all features for tests
|
||||
objdiff-core = { path = ".", features = ["all"] }
|
||||
insta = "1.42"
|
||||
insta = "1.43"
|
||||
|
||||
@@ -5,11 +5,12 @@ objdiff-core contains the core functionality of [objdiff](https://github.com/enc
|
||||
## Crate feature flags
|
||||
|
||||
- **`all`**: Enables all main features.
|
||||
- **`bindings`**: Enables serialization and deserialization of objdiff data structures.
|
||||
- **`config`**: Enables objdiff configuration file support.
|
||||
- **`dwarf`**: Enables extraction of line number information from DWARF debug sections.
|
||||
- **`mips`**: Enables the MIPS backend powered by [rabbitizer](https://github.com/Decompollaborate/rabbitizer). (Note: C library with Rust bindings)
|
||||
- **`ppc`**: Enables the PowerPC backend powered by [ppc750cl](https://github.com/encounter/ppc750cl).
|
||||
- **`x86`**: Enables the x86 backend powered by [iced-x86](https://crates.io/crates/iced-x86).
|
||||
- **`arm`**: Enables the ARM backend powered by [unarm](https://github.com/AetiasHax/unarm).
|
||||
- **`arm64`**: Enables the ARM64 backend powered by [yaxpeax-arm](https://github.com/iximeow/yaxpeax-arm).
|
||||
- **`bindings`**: Enables serialization and deserialization of objdiff data structures.
|
||||
- **`arm`**: Enables the ARM backend powered by [unarm](https://github.com/AetiasHax/unarm).
|
||||
- **`mips`**: Enables the MIPS backend powered by [rabbitizer](https://github.com/Decompollaborate/rabbitizer).
|
||||
- **`ppc`**: Enables the PowerPC backend powered by [ppc750cl](https://github.com/encounter/ppc750cl).
|
||||
- **`superh`**: Enables the SuperH backend powered by an included disassembler.
|
||||
- **`x86`**: Enables the x86 backend powered by [iced-x86](https://crates.io/crates/iced-x86).
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -275,4 +275,4 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user