mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fa4a6cadbb | |||
| 799971d54e | |||
| 8eef37e8df | |||
| 5f36916087 | |||
| ee667a2dde | |||
| cf5fc54cfa | |||
| 1cdfa1e857 | |||
| 3f157f33a5 | |||
| a1ea2919f8 | |||
| 9c31c82a37 | |||
| 4f34dfa194 | |||
| ae6d37a10b | |||
| 06e54f3456 | |||
| 6ed07bfaf1 | |||
| 80e939653a | |||
| 48305e0380 | |||
| 2eafbb218b | |||
| a1f2a535e5 | |||
| 8461b35cd7 | |||
| 95868f1d19 | |||
| 506c251d68 | |||
| f3c157ff06 | |||
| 6d3c63ccd8 | |||
| bbd8d9714f | |||
| 3c66ac3d54 | |||
| 561a9107e2 | |||
| e8de35b78e | |||
| d938988d43 | |||
| 3e6efb7736 | |||
| 674c942d7d | |||
| 6b7dcabbed | |||
| e202c3ef95 | |||
| b7730b3d00 | |||
| a4fdb61f04 | |||
| 2876be37a3 | |||
| 11171763eb | |||
| 6037a79ba2 |
@@ -10,7 +10,6 @@ on:
|
||||
|
||||
env:
|
||||
BUILD_PROFILE: release-lto
|
||||
CARGO_TARGET_DIR: target
|
||||
CARGO_INCREMENTAL: 0
|
||||
|
||||
jobs:
|
||||
@@ -33,9 +32,9 @@ jobs:
|
||||
- name: Cache Rust workspace
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Cargo check
|
||||
run: cargo check --all-features --all-targets
|
||||
run: cargo check --features all
|
||||
- name: Cargo clippy
|
||||
run: cargo clippy --all-features --all-targets
|
||||
run: cargo clippy --features all
|
||||
|
||||
fmt:
|
||||
name: Format
|
||||
@@ -65,13 +64,12 @@ jobs:
|
||||
continue-on-error: ${{ matrix.checks == 'advisories' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
with:
|
||||
command: check ${{ matrix.checks }}
|
||||
|
||||
test:
|
||||
name: Test
|
||||
if: 'false' # No tests yet
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||
@@ -90,7 +88,7 @@ jobs:
|
||||
- name: Cache Rust workspace
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Cargo test
|
||||
run: cargo test --release --all-features
|
||||
run: cargo test --release --features all
|
||||
|
||||
build-cli:
|
||||
name: Build objdiff-cli
|
||||
@@ -150,7 +148,7 @@ jobs:
|
||||
python3 -m venv .venv
|
||||
. .venv/bin/activate
|
||||
echo PATH=$PATH >> $GITHUB_ENV
|
||||
pip install ziglang==0.13.0 cargo-zigbuild==0.19.1
|
||||
pip install ziglang==0.13.0.post1 cargo-zigbuild==0.19.8
|
||||
- name: Setup Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -168,8 +166,8 @@ jobs:
|
||||
with:
|
||||
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
|
||||
path: |
|
||||
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
|
||||
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
|
||||
target/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
|
||||
target/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
|
||||
if-no-files-found: error
|
||||
|
||||
build-gui:
|
||||
@@ -180,21 +178,26 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- platform: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
target: x86_64-unknown-linux-gnu.2.31
|
||||
target_base: x86_64-unknown-linux-gnu
|
||||
name: linux-x86_64
|
||||
packages: libgtk-3-dev
|
||||
build: zigbuild
|
||||
features: default
|
||||
- platform: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
name: windows-x86_64
|
||||
build: build
|
||||
features: default
|
||||
- platform: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
name: macos-x86_64
|
||||
build: build
|
||||
features: default
|
||||
- platform: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
name: macos-arm64
|
||||
build: build
|
||||
features: default
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.platform }}
|
||||
@@ -206,27 +209,51 @@ jobs:
|
||||
sudo apt-get -y install ${{ matrix.packages }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install cargo-zigbuild
|
||||
if: matrix.build == 'zigbuild'
|
||||
run: |
|
||||
python3 -m venv .venv
|
||||
. .venv/bin/activate
|
||||
echo PATH=$PATH >> $GITHUB_ENV
|
||||
pip install ziglang==0.13.0.post1 cargo-zigbuild==0.19.8
|
||||
- name: Setup Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
targets: ${{ matrix.target_base || matrix.target }}
|
||||
- name: Cache Rust workspace
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
key: ${{ matrix.target }}
|
||||
- name: Cargo build
|
||||
run: >
|
||||
cargo build --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
||||
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
|
||||
with:
|
||||
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
|
||||
path: |
|
||||
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
|
||||
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
|
||||
target/${{ matrix.target_base || matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
|
||||
target/${{ matrix.target_base || matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
|
||||
if-no-files-found: error
|
||||
|
||||
build-wasm:
|
||||
name: Build objdiff-wasm
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rust-src
|
||||
- name: Cache Rust workspace
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Install dependencies
|
||||
run: npm -C objdiff-wasm install
|
||||
- name: Build
|
||||
run: npm -C objdiff-wasm run build
|
||||
|
||||
release:
|
||||
name: Release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
|
||||
Generated
+1350
-700
File diff suppressed because it is too large
Load Diff
+5
-4
@@ -3,8 +3,9 @@ members = [
|
||||
"objdiff-cli",
|
||||
"objdiff-core",
|
||||
"objdiff-gui",
|
||||
"objdiff-wasm",
|
||||
]
|
||||
resolver = "2"
|
||||
resolver = "3"
|
||||
|
||||
[profile.release-lto]
|
||||
inherits = "release"
|
||||
@@ -13,9 +14,9 @@ strip = "debuginfo"
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.package]
|
||||
version = "2.6.0"
|
||||
version = "3.0.0-beta.1"
|
||||
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.74"
|
||||
rust-version = "1.85"
|
||||
|
||||
@@ -73,7 +73,6 @@ 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-0384", reason = "Unmaintained indirect dependency" },
|
||||
]
|
||||
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
||||
# If this is false, then it uses a built-in git library.
|
||||
@@ -102,8 +101,7 @@ allow = [
|
||||
"Zlib",
|
||||
"0BSD",
|
||||
"OFL-1.1",
|
||||
"LicenseRef-UFL-1.0",
|
||||
"OpenSSL",
|
||||
"Ubuntu-font-1.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
|
||||
@@ -240,7 +238,12 @@ allow-git = []
|
||||
|
||||
[sources.allow-org]
|
||||
# github.com organizations to allow git sources for
|
||||
github = ["notify-rs"]
|
||||
github = [
|
||||
"CelestialAmber", # cwextab, rlwinmdec
|
||||
"Decompollaborate", # rabbitizer
|
||||
"enarx", # flagset
|
||||
"encounter",
|
||||
]
|
||||
# gitlab.com organizations to allow git sources for
|
||||
gitlab = []
|
||||
# bitbucket.org organizations to allow git sources for
|
||||
|
||||
@@ -28,6 +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"
|
||||
|
||||
[target.'cfg(target_env = "musl")'.dependencies]
|
||||
mimalloc = "0.1"
|
||||
|
||||
@@ -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;
|
||||
|
||||
+146
-126
@@ -1,73 +1,74 @@
|
||||
use std::{
|
||||
fs,
|
||||
io::stdout,
|
||||
mem,
|
||||
path::{Path, PathBuf},
|
||||
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,
|
||||
watcher::{Watcher, create_watcher},
|
||||
},
|
||||
config::{
|
||||
ProjectConfig, ProjectObject, ProjectObjectMetadata, build_globset,
|
||||
path::{check_path_buf, platform_path, platform_path_serde_option},
|
||||
},
|
||||
config::{build_globset, ProjectConfig, ProjectObject},
|
||||
diff,
|
||||
diff::{
|
||||
ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig, MappingConfig, ObjDiff,
|
||||
self, ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig, MappingConfig,
|
||||
ObjectDiff,
|
||||
},
|
||||
jobs::{
|
||||
objdiff::{start_build, ObjDiffConfig},
|
||||
Job, JobQueue, JobResult,
|
||||
objdiff::{ObjDiffConfig, start_build},
|
||||
},
|
||||
obj,
|
||||
obj::ObjInfo,
|
||||
obj::{self, Object},
|
||||
};
|
||||
use ratatui::prelude::*;
|
||||
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
||||
|
||||
use crate::{
|
||||
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)]
|
||||
/// Diff two object files. (Interactive or one-shot mode)
|
||||
#[argp(subcommand, name = "diff")]
|
||||
pub struct Args {
|
||||
#[argp(option, short = '1')]
|
||||
#[argp(option, short = '1', from_str_fn(platform_path))]
|
||||
/// Target object file
|
||||
target: Option<PathBuf>,
|
||||
#[argp(option, short = '2')]
|
||||
target: Option<Utf8PlatformPathBuf>,
|
||||
#[argp(option, short = '2', from_str_fn(platform_path))]
|
||||
/// Base object file
|
||||
base: Option<PathBuf>,
|
||||
#[argp(option, short = 'p')]
|
||||
base: Option<Utf8PlatformPathBuf>,
|
||||
#[argp(option, short = 'p', from_str_fn(platform_path))]
|
||||
/// Project directory
|
||||
project: Option<PathBuf>,
|
||||
project: Option<Utf8PlatformPathBuf>,
|
||||
#[argp(option, short = 'u')]
|
||||
/// Unit name within project
|
||||
unit: Option<String>,
|
||||
#[argp(option, short = 'o')]
|
||||
#[argp(option, short = 'o', from_str_fn(platform_path))]
|
||||
/// Output file (one-shot mode) ("-" for stdout)
|
||||
output: Option<PathBuf>,
|
||||
output: Option<Utf8PlatformPathBuf>,
|
||||
#[argp(option)]
|
||||
/// Output format (json, json-pretty, proto) (default: json)
|
||||
format: Option<String>,
|
||||
@@ -89,86 +90,61 @@ pub struct Args {
|
||||
}
|
||||
|
||||
pub fn run(args: Args) -> Result<()> {
|
||||
let (target_path, base_path, project_config) = match (
|
||||
&args.target,
|
||||
&args.base,
|
||||
&args.project,
|
||||
&args.unit,
|
||||
) {
|
||||
(Some(_), Some(_), None, None)
|
||||
| (Some(_), None, None, None)
|
||||
| (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None),
|
||||
(None, None, p, u) => {
|
||||
let project = match p {
|
||||
Some(project) => project.clone(),
|
||||
_ => std::env::current_dir().context("Failed to get the current directory")?,
|
||||
};
|
||||
let Some((project_config, project_config_info)) =
|
||||
objdiff_core::config::try_project_config(&project)
|
||||
else {
|
||||
bail!("Project config not found in {}", &project.display())
|
||||
};
|
||||
let mut project_config = project_config.with_context(|| {
|
||||
format!("Reading project config {}", project_config_info.path.display())
|
||||
})?;
|
||||
let object = {
|
||||
let resolve_paths = |o: &mut ProjectObject| {
|
||||
o.resolve_paths(
|
||||
&project,
|
||||
project_config.target_dir.as_deref(),
|
||||
project_config.base_dir.as_deref(),
|
||||
let (target_path, base_path, project_config) =
|
||||
match (&args.target, &args.base, &args.project, &args.unit) {
|
||||
(Some(_), Some(_), None, None)
|
||||
| (Some(_), None, None, None)
|
||||
| (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None),
|
||||
(None, None, p, u) => {
|
||||
let project = match p {
|
||||
Some(project) => project.clone(),
|
||||
_ => check_path_buf(
|
||||
std::env::current_dir().context("Failed to get the current directory")?,
|
||||
)
|
||||
.context("Current directory is not valid UTF-8")?,
|
||||
};
|
||||
if let Some(u) = u {
|
||||
let unit_path =
|
||||
PathBuf::from_str(u).ok().and_then(|p| fs::canonicalize(p).ok());
|
||||
|
||||
let Some(object) = project_config
|
||||
.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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
else {
|
||||
bail!("Unit not found: {}", u)
|
||||
};
|
||||
|
||||
object
|
||||
let Some((project_config, project_config_info)) =
|
||||
objdiff_core::config::try_project_config(project.as_ref())
|
||||
else {
|
||||
bail!("Project config not found in {}", &project)
|
||||
};
|
||||
let project_config = project_config.with_context(|| {
|
||||
format!("Reading project config {}", project_config_info.path.display())
|
||||
})?;
|
||||
let target_obj_dir = project_config
|
||||
.target_dir
|
||||
.as_ref()
|
||||
.map(|p| project.join(p.with_platform_encoding()));
|
||||
let base_obj_dir = project_config
|
||||
.base_dir
|
||||
.as_ref()
|
||||
.map(|p| project.join(p.with_platform_encoding()));
|
||||
let objects = project_config
|
||||
.units
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|o| {
|
||||
ObjectConfig::new(
|
||||
o,
|
||||
&project,
|
||||
target_obj_dir.as_deref(),
|
||||
base_obj_dir.as_deref(),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let object = if let Some(u) = u {
|
||||
objects
|
||||
.iter()
|
||||
.find(|obj| obj.name == *u)
|
||||
.ok_or_else(|| anyhow!("Unit not found: {}", u))?
|
||||
} else if let Some(symbol_name) = &args.symbol {
|
||||
let mut idx = None;
|
||||
let mut count = 0usize;
|
||||
for (i, obj) in project_config
|
||||
.units
|
||||
.as_deref_mut()
|
||||
.unwrap_or_default()
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
{
|
||||
resolve_paths(obj);
|
||||
|
||||
for (i, obj) in objects.iter().enumerate() {
|
||||
if obj
|
||||
.target_path
|
||||
.as_deref()
|
||||
.map(|o| obj::read::has_function(o, symbol_name))
|
||||
.map(|o| obj::read::has_function(o.as_ref(), symbol_name))
|
||||
.transpose()?
|
||||
.unwrap_or(false)
|
||||
{
|
||||
@@ -181,7 +157,7 @@ pub fn run(args: Args) -> Result<()> {
|
||||
}
|
||||
match (count, idx) {
|
||||
(0, None) => bail!("Symbol not found: {}", symbol_name),
|
||||
(1, Some(i)) => &mut project_config.units_mut()[i],
|
||||
(1, Some(i)) => &objects[i],
|
||||
(2.., Some(_)) => bail!(
|
||||
"Multiple instances of {} were found, try specifying a unit",
|
||||
symbol_name
|
||||
@@ -190,14 +166,13 @@ pub fn run(args: Args) -> Result<()> {
|
||||
}
|
||||
} else {
|
||||
bail!("Must specify one of: symbol, project and unit, target and base objects")
|
||||
}
|
||||
};
|
||||
let target_path = object.target_path.clone();
|
||||
let base_path = object.base_path.clone();
|
||||
(target_path, base_path, Some(project_config))
|
||||
}
|
||||
_ => bail!("Either target and base or project and unit must be specified"),
|
||||
};
|
||||
};
|
||||
let target_path = object.target_path.clone();
|
||||
let base_path = object.base_path.clone();
|
||||
(target_path, base_path, Some(project_config))
|
||||
}
|
||||
_ => bail!("Either target and base or project and unit must be specified"),
|
||||
};
|
||||
|
||||
if let Some(output) = &args.output {
|
||||
run_oneshot(&args, output, target_path.as_deref(), base_path.as_deref())
|
||||
@@ -245,24 +220,24 @@ fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)>
|
||||
|
||||
fn run_oneshot(
|
||||
args: &Args,
|
||||
output: &Path,
|
||||
target_path: Option<&Path>,
|
||||
base_path: Option<&Path>,
|
||||
output: &Utf8PlatformPath,
|
||||
target_path: Option<&Utf8PlatformPath>,
|
||||
base_path: Option<&Utf8PlatformPath>,
|
||||
) -> Result<()> {
|
||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||
let (diff_config, mapping_config) = build_config_from_args(args)?;
|
||||
let target = target_path
|
||||
.map(|p| {
|
||||
obj::read::read(p, &diff_config).with_context(|| format!("Loading {}", p.display()))
|
||||
obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {}", p))
|
||||
})
|
||||
.transpose()?;
|
||||
let base = base_path
|
||||
.map(|p| {
|
||||
obj::read::read(p, &diff_config).with_context(|| format!("Loading {}", p.display()))
|
||||
obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {}", p))
|
||||
})
|
||||
.transpose()?;
|
||||
let result =
|
||||
diff::diff_objs(&diff_config, &mapping_config, target.as_ref(), base.as_ref(), None)?;
|
||||
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
|
||||
let left = target.as_ref().and_then(|o| result.left.as_ref().map(|d| (o, d)));
|
||||
let right = base.as_ref().and_then(|o| result.right.as_ref().map(|d| (o, d)));
|
||||
write_output(&DiffResult::new(left, right), Some(output), output_format)?;
|
||||
@@ -272,13 +247,13 @@ fn run_oneshot(
|
||||
pub struct AppState {
|
||||
pub jobs: JobQueue,
|
||||
pub waker: Arc<TermWaker>,
|
||||
pub project_dir: Option<PathBuf>,
|
||||
pub project_dir: Option<Utf8PlatformPathBuf>,
|
||||
pub project_config: Option<ProjectConfig>,
|
||||
pub target_path: Option<PathBuf>,
|
||||
pub base_path: Option<PathBuf>,
|
||||
pub left_obj: Option<(ObjInfo, ObjDiff)>,
|
||||
pub right_obj: Option<(ObjInfo, ObjDiff)>,
|
||||
pub prev_obj: Option<(ObjInfo, ObjDiff)>,
|
||||
pub target_path: Option<Utf8PlatformPathBuf>,
|
||||
pub base_path: Option<Utf8PlatformPathBuf>,
|
||||
pub left_obj: Option<(Object, ObjectDiff)>,
|
||||
pub right_obj: Option<(Object, ObjectDiff)>,
|
||||
pub prev_obj: Option<(Object, ObjectDiff)>,
|
||||
pub reload_time: Option<time::OffsetDateTime>,
|
||||
pub time_format: Vec<time::format_description::FormatItem<'static>>,
|
||||
pub watcher: Option<Watcher>,
|
||||
@@ -315,6 +290,49 @@ fn create_objdiff_config(state: &AppState) -> ObjDiffConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// The configuration for a single object file.
|
||||
#[derive(Default, Clone, serde::Deserialize, serde::Serialize)]
|
||||
pub struct ObjectConfig {
|
||||
pub name: String,
|
||||
#[serde(default, with = "platform_path_serde_option")]
|
||||
pub target_path: Option<Utf8PlatformPathBuf>,
|
||||
#[serde(default, with = "platform_path_serde_option")]
|
||||
pub base_path: Option<Utf8PlatformPathBuf>,
|
||||
pub metadata: ProjectObjectMetadata,
|
||||
pub complete: Option<bool>,
|
||||
}
|
||||
|
||||
impl ObjectConfig {
|
||||
pub fn new(
|
||||
object: &ProjectObject,
|
||||
project_dir: &Utf8PlatformPath,
|
||||
target_obj_dir: Option<&Utf8PlatformPath>,
|
||||
base_obj_dir: Option<&Utf8PlatformPath>,
|
||||
) -> Self {
|
||||
let target_path = if let (Some(target_obj_dir), Some(path), None) =
|
||||
(target_obj_dir, &object.path, &object.target_path)
|
||||
{
|
||||
Some(target_obj_dir.join(path.with_platform_encoding()))
|
||||
} else {
|
||||
object.target_path.as_ref().map(|path| project_dir.join(path.with_platform_encoding()))
|
||||
};
|
||||
let base_path = if let (Some(base_obj_dir), Some(path), None) =
|
||||
(base_obj_dir, &object.path, &object.base_path)
|
||||
{
|
||||
Some(base_obj_dir.join(path.with_platform_encoding()))
|
||||
} else {
|
||||
object.base_path.as_ref().map(|path| project_dir.join(path.with_platform_encoding()))
|
||||
};
|
||||
Self {
|
||||
name: object.name().to_string(),
|
||||
target_path,
|
||||
base_path,
|
||||
metadata: object.metadata.clone().unwrap_or_default(),
|
||||
complete: object.complete(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
fn reload(&mut self) -> Result<()> {
|
||||
let config = create_objdiff_config(self);
|
||||
@@ -355,8 +373,8 @@ impl Wake for TermWaker {
|
||||
|
||||
fn run_interactive(
|
||||
args: Args,
|
||||
target_path: Option<PathBuf>,
|
||||
base_path: Option<PathBuf>,
|
||||
target_path: Option<Utf8PlatformPathBuf>,
|
||||
base_path: Option<Utf8PlatformPathBuf>,
|
||||
project_config: Option<ProjectConfig>,
|
||||
) -> Result<()> {
|
||||
let Some(symbol_name) = &args.symbol else { bail!("Interactive mode requires a symbol name") };
|
||||
@@ -384,7 +402,7 @@ fn run_interactive(
|
||||
let watch_patterns = project_config.build_watch_patterns()?;
|
||||
state.watcher = Some(create_watcher(
|
||||
state.modified.clone(),
|
||||
project_dir,
|
||||
project_dir.as_ref(),
|
||||
build_globset(&watch_patterns)?,
|
||||
Waker::from(state.waker.clone()),
|
||||
)?);
|
||||
@@ -407,15 +425,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 {
|
||||
|
||||
@@ -1,28 +1,25 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs::File,
|
||||
io::Read,
|
||||
path::{Path, PathBuf},
|
||||
time::Instant,
|
||||
};
|
||||
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::ProjectObject,
|
||||
config::path::platform_path,
|
||||
diff, obj,
|
||||
obj::{ObjSectionKind, ObjSymbolFlags},
|
||||
obj::{SectionKind, SymbolFlag},
|
||||
};
|
||||
use prost::Message;
|
||||
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use tracing::{info, warn};
|
||||
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
||||
|
||||
use crate::util::output::{write_output, OutputFormat};
|
||||
use crate::{
|
||||
cmd::diff::ObjectConfig,
|
||||
util::output::{OutputFormat, write_output},
|
||||
};
|
||||
|
||||
#[derive(FromArgs, PartialEq, Debug)]
|
||||
/// Generate a progress report for a project.
|
||||
@@ -43,12 +40,12 @@ pub enum SubCommand {
|
||||
/// Generate a progress report for a project.
|
||||
#[argp(subcommand, name = "generate")]
|
||||
pub struct GenerateArgs {
|
||||
#[argp(option, short = 'p')]
|
||||
#[argp(option, short = 'p', from_str_fn(platform_path))]
|
||||
/// Project directory
|
||||
project: Option<PathBuf>,
|
||||
#[argp(option, short = 'o')]
|
||||
project: Option<Utf8PlatformPathBuf>,
|
||||
#[argp(option, short = 'o', from_str_fn(platform_path))]
|
||||
/// Output file
|
||||
output: Option<PathBuf>,
|
||||
output: Option<Utf8PlatformPathBuf>,
|
||||
#[argp(switch, short = 'd')]
|
||||
/// Deduplicate global and weak symbols (runs single-threaded)
|
||||
deduplicate: bool,
|
||||
@@ -61,15 +58,15 @@ pub struct GenerateArgs {
|
||||
/// List any changes from a previous report.
|
||||
#[argp(subcommand, name = "changes")]
|
||||
pub struct ChangesArgs {
|
||||
#[argp(positional)]
|
||||
#[argp(positional, from_str_fn(platform_path))]
|
||||
/// Previous report file
|
||||
previous: PathBuf,
|
||||
#[argp(positional)]
|
||||
previous: Utf8PlatformPathBuf,
|
||||
#[argp(positional, from_str_fn(platform_path))]
|
||||
/// Current report file
|
||||
current: PathBuf,
|
||||
#[argp(option, short = 'o')]
|
||||
current: Utf8PlatformPathBuf,
|
||||
#[argp(option, short = 'o', from_str_fn(platform_path))]
|
||||
/// Output file
|
||||
output: Option<PathBuf>,
|
||||
output: Option<Utf8PlatformPathBuf>,
|
||||
#[argp(option, short = 'f')]
|
||||
/// Output format (json, json-pretty, proto) (default: json)
|
||||
format: Option<String>,
|
||||
@@ -84,10 +81,10 @@ pub fn run(args: Args) -> Result<()> {
|
||||
|
||||
fn generate(args: GenerateArgs) -> Result<()> {
|
||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||
let project_dir = args.project.as_deref().unwrap_or_else(|| Path::new("."));
|
||||
info!("Loading project {}", project_dir.display());
|
||||
let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new("."));
|
||||
info!("Loading project {}", project_dir);
|
||||
|
||||
let mut project = match objdiff_core::config::try_project_config(project_dir) {
|
||||
let project = match objdiff_core::config::try_project_config(project_dir.as_ref()) {
|
||||
Some((Ok(config), _)) => config,
|
||||
Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err),
|
||||
None => bail!("No project configuration found"),
|
||||
@@ -98,37 +95,33 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
||||
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
||||
);
|
||||
|
||||
let target_obj_dir =
|
||||
project.target_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
||||
let base_obj_dir =
|
||||
project.base_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
||||
let objects = project
|
||||
.units
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|o| {
|
||||
ObjectConfig::new(o, project_dir, target_obj_dir.as_deref(), base_obj_dir.as_deref())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let start = Instant::now();
|
||||
let mut units = vec![];
|
||||
let mut existing_functions: HashSet<String> = HashSet::new();
|
||||
if args.deduplicate {
|
||||
// If deduplicating, we need to run single-threaded
|
||||
for object in project.units.as_deref_mut().unwrap_or_default() {
|
||||
if let Some(unit) = report_object(
|
||||
object,
|
||||
project_dir,
|
||||
project.target_dir.as_deref(),
|
||||
project.base_dir.as_deref(),
|
||||
Some(&mut existing_functions),
|
||||
)? {
|
||||
for object in &objects {
|
||||
if let Some(unit) = report_object(object, Some(&mut existing_functions))? {
|
||||
units.push(unit);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let vec = project
|
||||
.units
|
||||
.as_deref_mut()
|
||||
.unwrap_or_default()
|
||||
.par_iter_mut()
|
||||
.map(|object| {
|
||||
report_object(
|
||||
object,
|
||||
project_dir,
|
||||
project.target_dir.as_deref(),
|
||||
project.base_dir.as_deref(),
|
||||
None,
|
||||
)
|
||||
})
|
||||
let vec = objects
|
||||
.par_iter()
|
||||
.map(|object| report_object(object, None))
|
||||
.collect::<Result<Vec<Option<ReportUnit>>>>()?;
|
||||
units = vec.into_iter().flatten().collect();
|
||||
}
|
||||
@@ -151,59 +144,54 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
||||
}
|
||||
|
||||
fn report_object(
|
||||
object: &mut ProjectObject,
|
||||
project_dir: &Path,
|
||||
target_dir: Option<&Path>,
|
||||
base_dir: Option<&Path>,
|
||||
object: &ObjectConfig,
|
||||
mut existing_functions: Option<&mut HashSet<String>>,
|
||||
) -> Result<Option<ReportUnit>> {
|
||||
object.resolve_paths(project_dir, target_dir, base_dir);
|
||||
match (&object.target_path, &object.base_path) {
|
||||
(None, Some(_)) if !object.complete().unwrap_or(false) => {
|
||||
warn!("Skipping object without target: {}", object.name());
|
||||
(None, Some(_)) if !object.complete.unwrap_or(false) => {
|
||||
warn!("Skipping object without target: {}", object.name);
|
||||
return Ok(None);
|
||||
}
|
||||
(None, None) => {
|
||||
warn!("Skipping object without target or base: {}", object.name());
|
||||
warn!("Skipping object without target or base: {}", object.name);
|
||||
return Ok(None);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let diff_config = diff::DiffObjConfig { relax_reloc_diffs: true, ..Default::default() };
|
||||
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, &diff_config)
|
||||
.with_context(|| format!("Failed to open {}", p.display()))
|
||||
obj::read::read(p.as_ref(), &diff_config)
|
||||
.with_context(|| format!("Failed to open {}", p))
|
||||
})
|
||||
.transpose()?;
|
||||
let base = object
|
||||
.base_path
|
||||
.as_ref()
|
||||
.map(|p| {
|
||||
obj::read::read(p, &diff_config)
|
||||
.with_context(|| format!("Failed to open {}", p.display()))
|
||||
obj::read::read(p.as_ref(), &diff_config)
|
||||
.with_context(|| format!("Failed to open {}", p))
|
||||
})
|
||||
.transpose()?;
|
||||
let result =
|
||||
diff::diff_objs(&diff_config, &mapping_config, target.as_ref(), base.as_ref(), None)?;
|
||||
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
|
||||
|
||||
let metadata = ReportUnitMetadata {
|
||||
complete: object.complete(),
|
||||
complete: object.metadata.complete,
|
||||
module_name: target
|
||||
.as_ref()
|
||||
.and_then(|o| o.split_meta.as_ref())
|
||||
.and_then(|m| m.module_name.clone()),
|
||||
module_id: target.as_ref().and_then(|o| o.split_meta.as_ref()).and_then(|m| m.module_id),
|
||||
source_path: object.metadata.as_ref().and_then(|m| m.source_path.clone()),
|
||||
progress_categories: object
|
||||
.metadata
|
||||
.as_ref()
|
||||
.and_then(|m| m.progress_categories.clone())
|
||||
.unwrap_or_default(),
|
||||
auto_generated: object.metadata.as_ref().and_then(|m| m.auto_generated),
|
||||
source_path: object.metadata.source_path.as_ref().map(|p| p.to_string()),
|
||||
progress_categories: object.metadata.progress_categories.clone().unwrap_or_default(),
|
||||
auto_generated: object.metadata.auto_generated,
|
||||
};
|
||||
let mut measures = Measures { total_units: 1, ..Default::default() };
|
||||
let mut sections = vec![];
|
||||
@@ -211,15 +199,13 @@ fn report_object(
|
||||
|
||||
let obj = target.as_ref().or(base.as_ref()).unwrap();
|
||||
let obj_diff = result.left.as_ref().or(result.right.as_ref()).unwrap();
|
||||
for (section, section_diff) in obj.sections.iter().zip(&obj_diff.sections) {
|
||||
for ((section_idx, section), section_diff) in
|
||||
obj.sections.iter().enumerate().zip(&obj_diff.sections)
|
||||
{
|
||||
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(),
|
||||
@@ -232,23 +218,26 @@ fn report_object(
|
||||
});
|
||||
|
||||
match section.kind {
|
||||
ObjSectionKind::Data | ObjSectionKind::Bss => {
|
||||
SectionKind::Data | SectionKind::Bss => {
|
||||
measures.total_data += section.size;
|
||||
if section_match_percent == 100.0 {
|
||||
measures.matched_data += section.size;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ObjSectionKind::Code => (),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
for (symbol, symbol_diff) in section.symbols.iter().zip(§ion_diff.symbols) {
|
||||
if symbol.size == 0 || symbol.flags.0.contains(ObjSymbolFlags::Hidden) {
|
||||
for (symbol, symbol_diff) in obj.symbols.iter().zip(&obj_diff.symbols) {
|
||||
if symbol.section != Some(section_idx)
|
||||
|| symbol.size == 0
|
||||
|| symbol.flags.contains(SymbolFlag::Hidden)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Some(existing_functions) = &mut existing_functions {
|
||||
if (symbol.flags.0.contains(ObjSymbolFlags::Global)
|
||||
|| symbol.flags.0.contains(ObjSymbolFlags::Weak))
|
||||
if (symbol.flags.contains(SymbolFlag::Global)
|
||||
|| symbol.flags.contains(SymbolFlag::Weak))
|
||||
&& !existing_functions.insert(symbol.name.clone())
|
||||
{
|
||||
continue;
|
||||
@@ -257,11 +246,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;
|
||||
@@ -291,7 +276,7 @@ fn report_object(
|
||||
measures.calc_fuzzy_match_percent();
|
||||
measures.calc_matched_percent();
|
||||
Ok(Some(ReportUnit {
|
||||
name: object.name().to_string(),
|
||||
name: object.name.clone(),
|
||||
measures: Some(measures),
|
||||
sections,
|
||||
functions,
|
||||
@@ -301,7 +286,7 @@ fn report_object(
|
||||
|
||||
fn changes(args: ChangesArgs) -> Result<()> {
|
||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||
let (previous, current) = if args.previous == Path::new("-") && args.current == Path::new("-") {
|
||||
let (previous, current) = if args.previous == "-" && args.current == "-" {
|
||||
// Special case for comparing two reports from stdin
|
||||
let mut data = vec![];
|
||||
std::io::stdin().read_to_end(&mut data)?;
|
||||
@@ -416,15 +401,14 @@ fn process_new_items(items: &[ReportItem]) -> Vec<ChangeItem> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn read_report(path: &Path) -> Result<Report> {
|
||||
if path == Path::new("-") {
|
||||
fn read_report(path: &Utf8PlatformPath) -> Result<Report> {
|
||||
if path == Utf8PlatformPath::new("-") {
|
||||
let mut data = vec![];
|
||||
std::io::stdin().read_to_end(&mut data)?;
|
||||
return Report::parse(&data).with_context(|| "Failed to load report from stdin");
|
||||
}
|
||||
let file = File::open(path).with_context(|| format!("Failed to open {}", path.display()))?;
|
||||
let mmap = unsafe { memmap2::Mmap::map(&file) }
|
||||
.with_context(|| format!("Failed to map {}", path.display()))?;
|
||||
Report::parse(mmap.as_ref())
|
||||
.with_context(|| format!("Failed to load report {}", path.display()))
|
||||
let file = File::open(path).with_context(|| format!("Failed to open {}", path))?;
|
||||
let mmap =
|
||||
unsafe { memmap2::Mmap::map(&file) }.with_context(|| format!("Failed to map {}", path))?;
|
||||
Report::parse(mmap.as_ref()).with_context(|| format!("Failed to load report {}", path))
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
mod argp_version;
|
||||
mod cmd;
|
||||
mod util;
|
||||
@@ -15,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 {
|
||||
|
||||
@@ -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)]
|
||||
@@ -34,9 +34,12 @@ impl OutputFormat {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_output<T>(input: &T, output: Option<&Path>, format: OutputFormat) -> Result<()>
|
||||
where T: serde::Serialize + prost::Message {
|
||||
match output {
|
||||
pub fn write_output<T, P>(input: &T, output: Option<P>, format: OutputFormat) -> Result<()>
|
||||
where
|
||||
T: serde::Serialize + prost::Message,
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
match output.as_ref().map(|p| p.as_ref()) {
|
||||
Some(output) if output != Path::new("-") => {
|
||||
info!("Writing to {}", output.display());
|
||||
let file = File::options()
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
use anyhow::{bail, Result};
|
||||
use core::cmp::Ordering;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers, MouseButton, MouseEventKind};
|
||||
use objdiff_core::{
|
||||
diff::{
|
||||
display::{display_diff, DiffText, HighlightKind},
|
||||
ObjDiff, ObjInsDiffKind, ObjSymbolDiff,
|
||||
DiffObjConfig, FunctionRelocDiffs, InstructionDiffKind, ObjectDiff, SymbolDiff,
|
||||
display::{DiffText, DiffTextColor, HighlightKind, display_row},
|
||||
},
|
||||
obj::{ObjInfo, ObjSectionKind, ObjSymbol, SymbolRef},
|
||||
obj::Object,
|
||||
};
|
||||
use ratatui::{
|
||||
Frame,
|
||||
prelude::*,
|
||||
widgets::{Block, Borders, Clear, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState},
|
||||
Frame,
|
||||
};
|
||||
|
||||
use super::{EventControlFlow, EventResult, UiView};
|
||||
@@ -28,9 +30,9 @@ pub struct FunctionDiffUi {
|
||||
pub scroll_state_y: ScrollbarState,
|
||||
pub per_page: usize,
|
||||
pub num_rows: usize,
|
||||
pub left_sym: Option<SymbolRef>,
|
||||
pub right_sym: Option<SymbolRef>,
|
||||
pub prev_sym: Option<SymbolRef>,
|
||||
pub left_sym: Option<usize>,
|
||||
pub right_sym: Option<usize>,
|
||||
pub prev_sym: Option<usize>,
|
||||
pub open_options: bool,
|
||||
pub three_way: bool,
|
||||
}
|
||||
@@ -80,8 +82,8 @@ impl UiView for FunctionDiffUi {
|
||||
f.render_widget(line_l, header_chunks[0]);
|
||||
|
||||
let mut line_r = Line::default();
|
||||
if let Some(percent) =
|
||||
get_symbol(state.right_obj.as_ref(), self.right_sym).and_then(|(_, d)| d.match_percent)
|
||||
if let Some(percent) = get_symbol(state.right_obj.as_ref(), self.right_sym)
|
||||
.and_then(|(_, _, d)| d.match_percent)
|
||||
{
|
||||
line_r.spans.push(Span::styled(
|
||||
format!("{:.2}% ", percent),
|
||||
@@ -106,13 +108,17 @@ impl UiView for FunctionDiffUi {
|
||||
let mut left_text = None;
|
||||
let mut left_highlight = None;
|
||||
let mut max_width = 0;
|
||||
if let Some((symbol, symbol_diff)) = get_symbol(state.left_obj.as_ref(), self.left_sym) {
|
||||
if let Some((obj, symbol_idx, symbol_diff)) =
|
||||
get_symbol(state.left_obj.as_ref(), self.left_sym)
|
||||
{
|
||||
let mut text = Text::default();
|
||||
let rect = content_chunks[0].inner(Margin::new(0, 1));
|
||||
left_highlight = self.print_sym(
|
||||
&mut text,
|
||||
symbol,
|
||||
obj,
|
||||
symbol_idx,
|
||||
symbol_diff,
|
||||
&state.diff_obj_config,
|
||||
rect,
|
||||
&self.left_highlight,
|
||||
result,
|
||||
@@ -125,13 +131,17 @@ impl UiView for FunctionDiffUi {
|
||||
let mut right_text = None;
|
||||
let mut right_highlight = None;
|
||||
let mut margin_text = None;
|
||||
if let Some((symbol, symbol_diff)) = get_symbol(state.right_obj.as_ref(), self.right_sym) {
|
||||
if let Some((obj, symbol_idx, symbol_diff)) =
|
||||
get_symbol(state.right_obj.as_ref(), self.right_sym)
|
||||
{
|
||||
let mut text = Text::default();
|
||||
let rect = content_chunks[2].inner(Margin::new(0, 1));
|
||||
right_highlight = self.print_sym(
|
||||
&mut text,
|
||||
symbol,
|
||||
obj,
|
||||
symbol_idx,
|
||||
symbol_diff,
|
||||
&state.diff_obj_config,
|
||||
rect,
|
||||
&self.right_highlight,
|
||||
result,
|
||||
@@ -150,14 +160,17 @@ impl UiView for FunctionDiffUi {
|
||||
let mut prev_text = None;
|
||||
let mut prev_margin_text = None;
|
||||
if self.three_way {
|
||||
if let Some((symbol, symbol_diff)) = get_symbol(state.prev_obj.as_ref(), self.prev_sym)
|
||||
if let Some((obj, symbol_idx, symbol_diff)) =
|
||||
get_symbol(state.prev_obj.as_ref(), self.prev_sym)
|
||||
{
|
||||
let mut text = Text::default();
|
||||
let rect = content_chunks[4].inner(Margin::new(0, 1));
|
||||
self.print_sym(
|
||||
&mut text,
|
||||
symbol,
|
||||
obj,
|
||||
symbol_idx,
|
||||
symbol_diff,
|
||||
&state.diff_obj_config,
|
||||
rect,
|
||||
&self.right_highlight,
|
||||
result,
|
||||
@@ -368,10 +381,15 @@ impl UiView for FunctionDiffUi {
|
||||
self.scroll_x = self.scroll_x.saturating_sub(1);
|
||||
result.redraw = true;
|
||||
}
|
||||
// Toggle relax relocation diffs
|
||||
// Cycle through function relocation diff mode
|
||||
KeyCode::Char('x') => {
|
||||
state.diff_obj_config.relax_reloc_diffs =
|
||||
!state.diff_obj_config.relax_reloc_diffs;
|
||||
state.diff_obj_config.function_reloc_diffs =
|
||||
match state.diff_obj_config.function_reloc_diffs {
|
||||
FunctionRelocDiffs::None => FunctionRelocDiffs::NameAddress,
|
||||
FunctionRelocDiffs::NameAddress => FunctionRelocDiffs::DataValue,
|
||||
FunctionRelocDiffs::DataValue => FunctionRelocDiffs::All,
|
||||
FunctionRelocDiffs::All => FunctionRelocDiffs::None,
|
||||
};
|
||||
result.redraw = true;
|
||||
return EventControlFlow::Reload;
|
||||
}
|
||||
@@ -430,9 +448,11 @@ impl UiView for FunctionDiffUi {
|
||||
get_symbol(state.left_obj.as_ref(), left_sym),
|
||||
get_symbol(state.right_obj.as_ref(), right_sym),
|
||||
) {
|
||||
(Some((_l, ld)), Some((_r, rd))) => ld.instructions.len().max(rd.instructions.len()),
|
||||
(Some((_l, ld)), None) => ld.instructions.len(),
|
||||
(None, Some((_r, rd))) => rd.instructions.len(),
|
||||
(Some((_l, _ls, ld)), Some((_r, _rs, rd))) => {
|
||||
ld.instruction_rows.len().max(rd.instruction_rows.len())
|
||||
}
|
||||
(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),
|
||||
};
|
||||
self.left_sym = left_sym;
|
||||
@@ -475,113 +495,82 @@ impl FunctionDiffUi {
|
||||
self.scroll_y += self.per_page / if half { 2 } else { 1 };
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn print_sym(
|
||||
&self,
|
||||
out: &mut Text<'static>,
|
||||
symbol: &ObjSymbol,
|
||||
symbol_diff: &ObjSymbolDiff,
|
||||
obj: &Object,
|
||||
symbol_index: usize,
|
||||
symbol_diff: &SymbolDiff,
|
||||
diff_config: &DiffObjConfig,
|
||||
rect: Rect,
|
||||
highlight: &HighlightKind,
|
||||
result: &EventResult,
|
||||
only_changed: bool,
|
||||
) -> Option<HighlightKind> {
|
||||
let base_addr = symbol.address;
|
||||
let mut new_highlight = None;
|
||||
for (y, ins_diff) in symbol_diff
|
||||
.instructions
|
||||
for (y, ins_row) in symbol_diff
|
||||
.instruction_rows
|
||||
.iter()
|
||||
.skip(self.scroll_y)
|
||||
.take(rect.height as usize)
|
||||
.enumerate()
|
||||
{
|
||||
if only_changed && ins_diff.kind == ObjInsDiffKind::None {
|
||||
if only_changed && ins_row.kind == InstructionDiffKind::None {
|
||||
out.lines.push(Line::default());
|
||||
continue;
|
||||
}
|
||||
let mut sx = rect.x;
|
||||
let sy = rect.y + y as u16;
|
||||
let mut line = Line::default();
|
||||
display_diff(ins_diff, base_addr, |text| -> Result<()> {
|
||||
let label_text;
|
||||
let mut base_color = match ins_diff.kind {
|
||||
ObjInsDiffKind::None
|
||||
| ObjInsDiffKind::OpMismatch
|
||||
| ObjInsDiffKind::ArgMismatch => Color::Gray,
|
||||
ObjInsDiffKind::Replace => Color::Cyan,
|
||||
ObjInsDiffKind::Delete => Color::Red,
|
||||
ObjInsDiffKind::Insert => Color::Green,
|
||||
};
|
||||
let mut pad_to = 0;
|
||||
match text {
|
||||
DiffText::Basic(text) => {
|
||||
label_text = text.to_string();
|
||||
}
|
||||
DiffText::BasicColor(s, idx) => {
|
||||
label_text = s.to_string();
|
||||
base_color = COLOR_ROTATION[idx % COLOR_ROTATION.len()];
|
||||
}
|
||||
DiffText::Line(num) => {
|
||||
label_text = format!("{num} ");
|
||||
base_color = Color::DarkGray;
|
||||
pad_to = 5;
|
||||
}
|
||||
DiffText::Address(addr) => {
|
||||
label_text = format!("{:x}:", addr);
|
||||
pad_to = 5;
|
||||
}
|
||||
DiffText::Opcode(mnemonic, _op) => {
|
||||
label_text = mnemonic.to_string();
|
||||
if ins_diff.kind == ObjInsDiffKind::OpMismatch {
|
||||
base_color = Color::Blue;
|
||||
}
|
||||
pad_to = 8;
|
||||
}
|
||||
DiffText::Argument(arg, diff) => {
|
||||
label_text = arg.to_string();
|
||||
if let Some(diff) = diff {
|
||||
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
|
||||
}
|
||||
}
|
||||
DiffText::BranchDest(addr, diff) => {
|
||||
label_text = format!("{addr:x}");
|
||||
if let Some(diff) = diff {
|
||||
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
|
||||
}
|
||||
}
|
||||
DiffText::Symbol(sym, diff) => {
|
||||
let name = sym.demangled_name.as_ref().unwrap_or(&sym.name);
|
||||
label_text = name.clone();
|
||||
if let Some(diff) = diff {
|
||||
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
|
||||
} else {
|
||||
base_color = Color::White;
|
||||
}
|
||||
display_row(obj, symbol_index, ins_row, diff_config, |segment| {
|
||||
let highlight_kind = HighlightKind::from(&segment.text);
|
||||
let label_text = match segment.text {
|
||||
DiffText::Basic(text) => text.to_string(),
|
||||
DiffText::Line(num) => format!("{num} "),
|
||||
DiffText::Address(addr) => format!("{:x}:", addr),
|
||||
DiffText::Opcode(mnemonic, _op) => format!("{mnemonic} "),
|
||||
DiffText::Argument(arg) => arg.to_string(),
|
||||
DiffText::BranchDest(addr) => format!("{addr:x}"),
|
||||
DiffText::Symbol(sym) => {
|
||||
sym.demangled_name.as_ref().unwrap_or(&sym.name).clone()
|
||||
}
|
||||
DiffText::Addend(addend) => match addend.cmp(&0i64) {
|
||||
Ordering::Greater => format!("+{:#x}", addend),
|
||||
Ordering::Less => format!("-{:#x}", -addend),
|
||||
_ => String::new(),
|
||||
},
|
||||
DiffText::Spacing(n) => {
|
||||
line.spans.push(Span::raw(" ".repeat(n)));
|
||||
line.spans.push(Span::raw(" ".repeat(n as usize)));
|
||||
sx += n as u16;
|
||||
return Ok(());
|
||||
}
|
||||
DiffText::Eol => {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
DiffText::Eol => return Ok(()),
|
||||
};
|
||||
|
||||
let len = label_text.len();
|
||||
let highlighted = *highlight == text;
|
||||
let highlighted =
|
||||
highlight_kind != HighlightKind::None && *highlight == highlight_kind;
|
||||
if let Some((cx, cy)) = result.click_xy {
|
||||
if cx >= sx && cx < sx + len as u16 && cy == sy {
|
||||
new_highlight = Some(text.into());
|
||||
new_highlight = Some(highlight_kind);
|
||||
}
|
||||
}
|
||||
let mut style = Style::new().fg(base_color);
|
||||
let mut style = Style::new().fg(match segment.color {
|
||||
DiffTextColor::Normal => Color::Gray,
|
||||
DiffTextColor::Dim => Color::DarkGray,
|
||||
DiffTextColor::Bright => Color::White,
|
||||
DiffTextColor::Replace => Color::Cyan,
|
||||
DiffTextColor::Delete => Color::Red,
|
||||
DiffTextColor::Insert => Color::Green,
|
||||
DiffTextColor::Rotating(i) => COLOR_ROTATION[i as usize % COLOR_ROTATION.len()],
|
||||
});
|
||||
if highlighted {
|
||||
style = style.bg(Color::DarkGray);
|
||||
}
|
||||
line.spans.push(Span::styled(label_text, style));
|
||||
sx += len as u16;
|
||||
if pad_to > len {
|
||||
let pad = (pad_to - len) as u16;
|
||||
if segment.pad_to as usize > len {
|
||||
let pad = (segment.pad_to as usize - len) as u16;
|
||||
line.spans.push(Span::raw(" ".repeat(pad as usize)));
|
||||
sx += pad;
|
||||
}
|
||||
@@ -593,12 +582,13 @@ impl FunctionDiffUi {
|
||||
new_highlight
|
||||
}
|
||||
|
||||
fn print_margin(&self, out: &mut Text, symbol: &ObjSymbolDiff, rect: Rect) {
|
||||
for ins_diff in symbol.instructions.iter().skip(self.scroll_y).take(rect.height as usize) {
|
||||
if ins_diff.kind != ObjInsDiffKind::None {
|
||||
out.lines.push(Line::raw(match ins_diff.kind {
|
||||
ObjInsDiffKind::Delete => "<",
|
||||
ObjInsDiffKind::Insert => ">",
|
||||
fn print_margin(&self, out: &mut Text, symbol: &SymbolDiff, rect: Rect) {
|
||||
for ins_row in symbol.instruction_rows.iter().skip(self.scroll_y).take(rect.height as usize)
|
||||
{
|
||||
if ins_row.kind != InstructionDiffKind::None {
|
||||
out.lines.push(Line::raw(match ins_row.kind {
|
||||
InstructionDiffKind::Delete => "<",
|
||||
InstructionDiffKind::Insert => ">",
|
||||
_ => "|",
|
||||
}));
|
||||
} else {
|
||||
@@ -630,23 +620,18 @@ pub fn match_percent_color(match_percent: f32) -> Color {
|
||||
|
||||
#[inline]
|
||||
fn get_symbol(
|
||||
obj: Option<&(ObjInfo, ObjDiff)>,
|
||||
sym: Option<SymbolRef>,
|
||||
) -> Option<(&ObjSymbol, &ObjSymbolDiff)> {
|
||||
obj: Option<&(Object, ObjectDiff)>,
|
||||
sym: Option<usize>,
|
||||
) -> Option<(&Object, usize, &SymbolDiff)> {
|
||||
let (obj, diff) = obj?;
|
||||
let sym = sym?;
|
||||
Some((obj.section_symbol(sym).1, diff.symbol_diff(sym)))
|
||||
Some((obj, sym, &diff.symbols[sym]))
|
||||
}
|
||||
|
||||
fn find_function(obj: &ObjInfo, name: &str) -> Option<SymbolRef> {
|
||||
for (section_idx, section) in obj.sections.iter().enumerate() {
|
||||
if section.kind != ObjSectionKind::Code {
|
||||
continue;
|
||||
}
|
||||
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
|
||||
if symbol.name == name {
|
||||
return Some(SymbolRef { section_idx, symbol_idx });
|
||||
}
|
||||
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
|
||||
|
||||
+61
-67
@@ -12,54 +12,42 @@ A local diffing tool for decompilation projects.
|
||||
"""
|
||||
documentation = "https://docs.rs/objdiff-core"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
all = [
|
||||
# Features
|
||||
"bindings",
|
||||
"build",
|
||||
"config",
|
||||
"dwarf",
|
||||
"serde",
|
||||
# Architectures
|
||||
"arm",
|
||||
"arm64",
|
||||
"mips",
|
||||
"ppc",
|
||||
"x86",
|
||||
"arm",
|
||||
"arm64",
|
||||
]
|
||||
# Implicit, used to check if any arch is enabled
|
||||
any-arch = [
|
||||
"config",
|
||||
"dep:bimap",
|
||||
"dep:byteorder",
|
||||
"dep:flagset",
|
||||
"dep:heck",
|
||||
"dep:log",
|
||||
"dep:memmap2",
|
||||
"dep:num-traits",
|
||||
"dep:prettyplease",
|
||||
"dep:proc-macro2",
|
||||
"dep:quote",
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
"dep:regex",
|
||||
"dep:similar",
|
||||
"dep:strum",
|
||||
"dep:syn",
|
||||
]
|
||||
bindings = [
|
||||
"dep:pbjson",
|
||||
"dep:pbjson-build",
|
||||
"dep:prost",
|
||||
"dep:prost-build",
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
]
|
||||
build = [
|
||||
"dep:notify",
|
||||
"dep:notify-debouncer-full",
|
||||
"dep:path-slash",
|
||||
"dep:reqwest",
|
||||
"dep:self_update",
|
||||
"dep:shell-escape",
|
||||
@@ -68,15 +56,29 @@ build = [
|
||||
"dep:winapi",
|
||||
]
|
||||
config = [
|
||||
"dep:bimap",
|
||||
"dep:filetime",
|
||||
"dep:globset",
|
||||
"dep:semver",
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
"dep:serde_yaml",
|
||||
"dep:typed-path",
|
||||
]
|
||||
dwarf = ["dep:gimli"]
|
||||
serde = [
|
||||
"dep:pbjson",
|
||||
"dep:pbjson-build",
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
]
|
||||
std = [
|
||||
"anyhow/std",
|
||||
"flagset?/std",
|
||||
"log?/std",
|
||||
"num-traits?/std",
|
||||
"object/std",
|
||||
"prost?/std",
|
||||
"serde?/std",
|
||||
"typed-path?/std",
|
||||
"dep:filetime",
|
||||
"dep:memmap2",
|
||||
]
|
||||
mips = [
|
||||
"any-arch",
|
||||
"dep:rabbitizer",
|
||||
@@ -86,6 +88,7 @@ ppc = [
|
||||
"dep:cwdemangle",
|
||||
"dep:cwextab",
|
||||
"dep:ppc750cl",
|
||||
"dep:rlwinmdec",
|
||||
]
|
||||
x86 = [
|
||||
"any-arch",
|
||||
@@ -105,78 +108,64 @@ arm64 = [
|
||||
"dep:yaxpeax-arch",
|
||||
"dep:yaxpeax-arm",
|
||||
]
|
||||
wasm = [
|
||||
"any-arch",
|
||||
"bindings",
|
||||
"dep:console_error_panic_hook",
|
||||
"dep:console_log",
|
||||
"dep:log",
|
||||
"dep:tsify-next",
|
||||
"dep:wasm-bindgen",
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["all"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
bimap = { version = "0.6", features = ["serde"], optional = true }
|
||||
byteorder = { version = "1.5", optional = true }
|
||||
anyhow = { version = "1.0", default-features = false }
|
||||
filetime = { version = "0.2", optional = true }
|
||||
flagset = { version = "0.4", optional = true }
|
||||
log = { version = "0.4", optional = true }
|
||||
flagset = { version = "0.4", default-features = false, optional = true, git = "https://github.com/enarx/flagset.git", rev = "a1fe9369b3741e43fec45da1998e83b9d78966a2" }
|
||||
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", optional = true }
|
||||
object = { version = "0.36", features = ["read_core", "std", "elf", "pe"], default-features = false }
|
||||
pbjson = { version = "0.7", optional = true }
|
||||
prost = { version = "0.13", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
similar = { version = "2.6", default-features = false, optional = true }
|
||||
strum = { version = "0.26", features = ["derive"], optional = true }
|
||||
wasm-bindgen = { version = "0.2", optional = true }
|
||||
tsify-next = { version = "0.5", default-features = false, features = ["js"], optional = true }
|
||||
console_log = { version = "1.0", optional = true }
|
||||
console_error_panic_hook = { version = "0.1", optional = true }
|
||||
num-traits = { version = "0.2", default-features = false, optional = true }
|
||||
object = { version = "0.36", 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 }
|
||||
|
||||
# config
|
||||
globset = { version = "0.4", features = ["serde1"], optional = true }
|
||||
semver = { version = "1.0", optional = true }
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
serde_yaml = { version = "0.9", optional = true }
|
||||
globset = { version = "0.4", default-features = false, optional = true }
|
||||
semver = { version = "1.0", default-features = false, optional = true }
|
||||
serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true }
|
||||
|
||||
# dwarf
|
||||
gimli = { version = "0.31", default-features = false, features = ["read-all"], optional = true }
|
||||
gimli = { version = "0.31", default-features = false, features = ["read"], optional = true }
|
||||
|
||||
# ppc
|
||||
cwdemangle = { version = "1.0", optional = true }
|
||||
cwextab = { version = "1.0.2", optional = true }
|
||||
cwextab = { version = "1.0", optional = true, git = "https://github.com/CelestialAmber/cwextab.git" }
|
||||
ppc750cl = { version = "0.3", optional = true }
|
||||
rlwinmdec = { version = "1.1", optional = true, git = "https://github.com/CelestialAmber/rlwinmdec.git" }
|
||||
|
||||
# mips
|
||||
rabbitizer = { version = "1.12", optional = true }
|
||||
rabbitizer = { git = "https://github.com/Decompollaborate/rabbitizer.git", branch = "🦀", default-features = false, features = ["all_extensions"], optional = true }
|
||||
|
||||
# x86
|
||||
cpp_demangle = { version = "0.4", optional = true }
|
||||
iced-x86 = { version = "1.21", default-features = false, features = ["std", "decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums"], optional = true }
|
||||
msvc-demangler = { version = "0.10", optional = true }
|
||||
cpp_demangle = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
|
||||
iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true }
|
||||
msvc-demangler = { version = "0.11", optional = true }
|
||||
|
||||
# arm
|
||||
unarm = { version = "1.6", optional = true }
|
||||
arm-attr = { version = "0.1", optional = true }
|
||||
unarm = { version = "1.7", optional = true }
|
||||
arm-attr = { version = "0.2", optional = true }
|
||||
|
||||
# arm64
|
||||
yaxpeax-arch = { version = "0.3", default-features = false, features = ["std"], optional = true }
|
||||
yaxpeax-arm = { version = "0.3", default-features = false, features = ["std"], optional = true }
|
||||
yaxpeax-arch = { version = "0.3", default-features = false, optional = true }
|
||||
yaxpeax-arm = { version = "0.3", default-features = false, optional = true }
|
||||
|
||||
# build
|
||||
notify = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39dbb7f301ff7b20e594e34c3a2", version = "6.1.1", optional = true }
|
||||
notify-debouncer-full = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39dbb7f301ff7b20e594e34c3a2", version = "0.4.0", 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.14", optional = true }
|
||||
tempfile = { version = "3.17", optional = true }
|
||||
time = { version = "0.3", optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
path-slash = { version = "0.2", optional = true }
|
||||
winapi = { version = "0.3", optional = true }
|
||||
|
||||
# For Linux static binaries, use rustls
|
||||
@@ -196,6 +185,11 @@ prettyplease = { version = "0.2", optional = true }
|
||||
proc-macro2 = { version = "1.0", optional = true }
|
||||
prost-build = { version = "0.13", optional = true }
|
||||
quote = { version = "1.0", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = { version = "1.0" }
|
||||
syn = { version = "2.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
# Enable all features for tests
|
||||
objdiff-core = { path = ".", features = ["all"] }
|
||||
insta = "1.42"
|
||||
|
||||
+13
-9
@@ -1,11 +1,12 @@
|
||||
#[cfg(feature = "any-arch")]
|
||||
mod config_gen;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
#[cfg(feature = "bindings")]
|
||||
compile_protos();
|
||||
#[cfg(feature = "any-arch")]
|
||||
config_gen::generate_diff_config();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "bindings")]
|
||||
@@ -18,7 +19,7 @@ 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("diff.proto"), root.join("report.proto")];
|
||||
let proto_files = vec![root.join("report.proto")];
|
||||
for proto_file in &proto_files {
|
||||
println!("cargo:rerun-if-changed={}", proto_file.display());
|
||||
let mtime = match std::fs::metadata(proto_file) {
|
||||
@@ -54,11 +55,14 @@ fn compile_protos() {
|
||||
}
|
||||
}
|
||||
|
||||
let descriptor_set = std::fs::read(descriptor_path).expect("Failed to read descriptor set");
|
||||
pbjson_build::Builder::new()
|
||||
.register_descriptors(&descriptor_set)
|
||||
.expect("Failed to register descriptors")
|
||||
.preserve_proto_field_names()
|
||||
.build(&[".objdiff"])
|
||||
.expect("Failed to build pbjson");
|
||||
#[cfg(feature = "serde")]
|
||||
{
|
||||
let descriptor_set = std::fs::read(descriptor_path).expect("Failed to read descriptor set");
|
||||
pbjson_build::Builder::new()
|
||||
.register_descriptors(&descriptor_set)
|
||||
.expect("Failed to register descriptors")
|
||||
.preserve_proto_field_names()
|
||||
.build(&[".objdiff"])
|
||||
.expect("Failed to build pbjson");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,29 @@
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"id": "relaxRelocDiffs",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Relax relocation diffs",
|
||||
"description": "Ignores differences in relocation targets. (Address, name, etc)"
|
||||
"id": "functionRelocDiffs",
|
||||
"type": "choice",
|
||||
"default": "name_address",
|
||||
"name": "Function relocation diffs",
|
||||
"description": "How relocation targets will be diffed in the function view.",
|
||||
"items": [
|
||||
{
|
||||
"value": "none",
|
||||
"name": "None"
|
||||
},
|
||||
{
|
||||
"value": "name_address",
|
||||
"name": "Name or address"
|
||||
},
|
||||
{
|
||||
"value": "data_value",
|
||||
"name": "Data value"
|
||||
},
|
||||
{
|
||||
"value": "all",
|
||||
"name": "Name or address, data value"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "spaceBetweenArgs",
|
||||
@@ -21,6 +39,13 @@
|
||||
"name": "Combine data sections",
|
||||
"description": "Combines data sections with equal names."
|
||||
},
|
||||
{
|
||||
"id": "combineTextSections",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Combine text sections",
|
||||
"description": "Combines all text sections into one."
|
||||
},
|
||||
{
|
||||
"id": "arm.archVersion",
|
||||
"type": "choice",
|
||||
@@ -162,6 +187,20 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mips.registerPrefix",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"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",
|
||||
@@ -193,9 +232,10 @@
|
||||
"id": "general",
|
||||
"name": "General",
|
||||
"properties": [
|
||||
"relaxRelocDiffs",
|
||||
"functionRelocDiffs",
|
||||
"spaceBetweenArgs",
|
||||
"combineDataSections"
|
||||
"combineDataSections",
|
||||
"combineTextSections"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -216,7 +256,15 @@
|
||||
"name": "MIPS",
|
||||
"properties": [
|
||||
"mips.abi",
|
||||
"mips.instrCategory"
|
||||
"mips.instrCategory",
|
||||
"mips.registerPrefix"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "ppc",
|
||||
"name": "PowerPC",
|
||||
"properties": [
|
||||
"ppc.calculatePoolRelocations"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -100,7 +100,7 @@ pub fn generate_diff_config() {
|
||||
}
|
||||
let value = &item.value;
|
||||
variants.extend(quote! {
|
||||
#[serde(rename = #value, alias = #variant_name)]
|
||||
#[cfg_attr(feature = "serde", serde(rename = #value, alias = #variant_name))]
|
||||
#variant_ident,
|
||||
});
|
||||
full_variants.extend(quote! { #enum_ident::#variant_ident, });
|
||||
@@ -134,8 +134,8 @@ pub fn generate_diff_config() {
|
||||
});
|
||||
}
|
||||
enums.extend(quote! {
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum #enum_ident {
|
||||
#variants
|
||||
}
|
||||
@@ -168,7 +168,7 @@ pub fn generate_diff_config() {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::str::FromStr for #enum_ident {
|
||||
impl core::str::FromStr for #enum_ident {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
#variant_from_str
|
||||
@@ -244,7 +244,7 @@ pub fn generate_diff_config() {
|
||||
let default = b.default;
|
||||
if default {
|
||||
property_fields.extend(quote! {
|
||||
#[serde(default = "default_true")]
|
||||
#[cfg_attr(feature = "serde", serde(default = "default_true"))]
|
||||
});
|
||||
}
|
||||
property_fields.extend(quote! {
|
||||
@@ -412,7 +412,7 @@ pub fn generate_diff_config() {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::str::FromStr for ConfigPropertyId {
|
||||
impl core::str::FromStr for ConfigPropertyId {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
#config_property_id_from_str
|
||||
@@ -433,6 +433,7 @@ pub fn generate_diff_config() {
|
||||
Choice(&'static str),
|
||||
}
|
||||
impl ConfigPropertyValue {
|
||||
#[cfg(feature = "serde")]
|
||||
pub fn to_json(&self) -> serde_json::Value {
|
||||
match self {
|
||||
ConfigPropertyValue::Boolean(value) => serde_json::Value::Bool(*value),
|
||||
@@ -440,17 +441,25 @@ pub fn generate_diff_config() {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl core::fmt::Display for ConfigPropertyValue {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
ConfigPropertyValue::Boolean(value) => write!(f, "{}", value),
|
||||
ConfigPropertyValue::Choice(value) => write!(f, "{}", value),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ConfigPropertyKind {
|
||||
Boolean,
|
||||
Choice(&'static [ConfigEnumVariantInfo]),
|
||||
}
|
||||
#enums
|
||||
#[cfg(feature = "serde")]
|
||||
#[inline(always)]
|
||||
fn default_true() -> bool { true }
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
#[serde(default)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(default))]
|
||||
pub struct DiffObjConfig {
|
||||
#property_fields
|
||||
}
|
||||
|
||||
@@ -87,11 +87,11 @@ message Relocation {
|
||||
}
|
||||
|
||||
message RelocationTarget {
|
||||
Symbol symbol = 1;
|
||||
uint32 symbol_index = 1;
|
||||
int64 addend = 2;
|
||||
}
|
||||
|
||||
message InstructionDiff {
|
||||
message InstructionDiffRow {
|
||||
DiffKind diff_kind = 1;
|
||||
optional Instruction instruction = 2;
|
||||
optional InstructionBranchFrom branch_from = 3;
|
||||
@@ -122,17 +122,12 @@ message InstructionBranchTo {
|
||||
uint32 branch_index = 2;
|
||||
}
|
||||
|
||||
message SymbolRef {
|
||||
optional uint32 section_index = 1;
|
||||
uint32 symbol_index = 2;
|
||||
}
|
||||
|
||||
message SymbolDiff {
|
||||
Symbol symbol = 1;
|
||||
repeated InstructionDiff instructions = 2;
|
||||
repeated InstructionDiffRow instruction_rows = 2;
|
||||
optional float match_percent = 3;
|
||||
// The symbol ref in the _other_ object that this symbol was diffed against
|
||||
optional SymbolRef target = 5;
|
||||
// The symbol index in the _other_ object that this symbol was diffed against
|
||||
optional uint32 target_symbol = 5;
|
||||
}
|
||||
|
||||
message DataDiff {
|
||||
@@ -147,7 +142,7 @@ message SectionDiff {
|
||||
SectionKind kind = 2;
|
||||
uint64 size = 3;
|
||||
uint64 address = 4;
|
||||
repeated SymbolDiff symbols = 5;
|
||||
reserved 5;
|
||||
repeated DataDiff data = 6;
|
||||
optional float match_percent = 7;
|
||||
}
|
||||
@@ -157,11 +152,11 @@ enum SectionKind {
|
||||
SECTION_TEXT = 1;
|
||||
SECTION_DATA = 2;
|
||||
SECTION_BSS = 3;
|
||||
SECTION_COMMON = 4;
|
||||
}
|
||||
|
||||
message ObjectDiff {
|
||||
repeated SectionDiff sections = 1;
|
||||
repeated SymbolDiff symbols = 2;
|
||||
}
|
||||
|
||||
message DiffResult {
|
||||
|
||||
Binary file not shown.
+344
-229
File diff suppressed because it is too large
Load Diff
+328
-318
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user