mirror of https://github.com/encounter/objdiff.git
Compare commits
7 Commits
7b58f9a269
...
74e89130a8
Author | SHA1 | Date |
---|---|---|
Luke Street | 74e89130a8 | |
Luke Street | 236e4d8d26 | |
Luke Street | b900ae5a00 | |
Luke Street | 261e1b8e07 | |
Luke Street | a29e913b45 | |
Luke Street | 49257dc73c | |
Luke Street | dc9eec66b0 |
|
@ -9,6 +9,7 @@ on:
|
|||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
BUILD_PROFILE: release-lto
|
||||
CARGO_BIN_NAME: objdiff
|
||||
CARGO_TARGET_DIR: target
|
||||
|
||||
|
@ -30,9 +31,25 @@ jobs:
|
|||
with:
|
||||
components: rustfmt, clippy
|
||||
- name: Cargo check
|
||||
run: cargo check --all-features
|
||||
run: cargo check
|
||||
- name: Cargo clippy
|
||||
run: cargo clippy --all-features
|
||||
run: cargo clippy
|
||||
|
||||
fmt:
|
||||
name: Format
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTFLAGS: -D warnings
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Rust toolchain
|
||||
# We use nightly options in rustfmt.toml
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rustfmt
|
||||
- name: Cargo fmt
|
||||
run: cargo fmt --all --check
|
||||
|
||||
deny:
|
||||
name: Deny
|
||||
|
@ -52,6 +69,7 @@ jobs:
|
|||
|
||||
test:
|
||||
name: Test
|
||||
if: 'false' # No tests yet
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||
|
@ -68,7 +86,7 @@ jobs:
|
|||
- name: Setup Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Cargo test
|
||||
run: cargo test --release --all-features
|
||||
run: cargo test --release
|
||||
|
||||
build:
|
||||
name: Build
|
||||
|
@ -103,16 +121,16 @@ jobs:
|
|||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
- name: Cargo build
|
||||
run: cargo build --release --all-features --target ${{ matrix.target }} --bin ${{ env.CARGO_BIN_NAME }}
|
||||
run: cargo build --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }} --bin ${{ env.CARGO_BIN_NAME }}
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.name }}
|
||||
path: |
|
||||
${{ env.CARGO_TARGET_DIR }}/release/${{ env.CARGO_BIN_NAME }}
|
||||
${{ env.CARGO_TARGET_DIR }}/release/${{ env.CARGO_BIN_NAME }}.exe
|
||||
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/release/${{ env.CARGO_BIN_NAME }}
|
||||
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/release/${{ env.CARGO_BIN_NAME }}.exe
|
||||
${{ env.CARGO_TARGET_DIR }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
|
||||
${{ env.CARGO_TARGET_DIR }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
|
||||
${{ 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
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
|
@ -129,8 +147,8 @@ jobs:
|
|||
working-directory: artifacts
|
||||
run: |
|
||||
mkdir ../out
|
||||
for i in */*/release/$CARGO_BIN_NAME*; do
|
||||
mv "$i" "../out/$(sed -E "s/([^/]+)\/[^/]+\/release\/($CARGO_BIN_NAME)/\2-\1/" <<< "$i")"
|
||||
for i in */*/$BUILD_PROFILE/$CARGO_BIN_NAME*; do
|
||||
mv "$i" "../out/$(sed -E "s/([^/]+)\/[^/]+\/$BUILD_PROFILE\/($CARGO_BIN_NAME)/\2-\1/" <<< "$i")"
|
||||
done
|
||||
ls -R ../out
|
||||
- name: Release
|
||||
|
|
File diff suppressed because it is too large
Load Diff
34
Cargo.toml
34
Cargo.toml
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "objdiff"
|
||||
version = "0.5.2"
|
||||
version = "0.6.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
authors = ["Luke Street <luke@street.dev>"]
|
||||
|
@ -13,20 +13,22 @@ A local diffing tool for decompilation projects.
|
|||
publish = false
|
||||
build = "build.rs"
|
||||
|
||||
[profile.release]
|
||||
[profile.release-lto]
|
||||
inherits = "release"
|
||||
lto = "thin"
|
||||
strip = "debuginfo"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
wgpu = ["eframe/wgpu"]
|
||||
wsl = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.75"
|
||||
byteorder = "1.5.0"
|
||||
bytes = "1.5.0"
|
||||
cfg-if = "1.0.0"
|
||||
const_format = "0.2.31"
|
||||
const_format = "0.2.32"
|
||||
cwdemangle = "0.1.6"
|
||||
dirs = "5.0.1"
|
||||
eframe = { version = "0.23.0", features = ["persistence"] }
|
||||
|
@ -40,29 +42,31 @@ memmap2 = "0.9.0"
|
|||
notify = "6.1.1"
|
||||
object = { version = "0.32.1", features = ["read_core", "std", "elf"], default-features = false }
|
||||
png = "0.17.10"
|
||||
pollster = "0.3.0"
|
||||
ppc750cl = { git = "https://github.com/encounter/ppc750cl", rev = "4a2bbbc6f84dcb76255ab6f3595a8d4a0ce96618" }
|
||||
rabbitizer = "1.7.10"
|
||||
rfd = { version = "0.12.0" } #, default-features = false, features = ['xdg-portal']
|
||||
rabbitizer = "1.8.0"
|
||||
rfd = { version = "0.12.1" } #, default-features = false, features = ['xdg-portal']
|
||||
ron = "0.8.1"
|
||||
semver = "1.0.19"
|
||||
semver = "1.0.20"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1.0.107"
|
||||
serde_yaml = "0.9.25"
|
||||
tempfile = "3.8.0"
|
||||
thiserror = "1.0.49"
|
||||
time = { version = "0.3.29", features = ["formatting", "local-offset"] }
|
||||
toml = "0.8.2"
|
||||
serde_json = "1.0.108"
|
||||
serde_yaml = "0.9.27"
|
||||
similar = "2.3.0"
|
||||
tempfile = "3.8.1"
|
||||
thiserror = "1.0.50"
|
||||
time = { version = "0.3.30", features = ["formatting", "local-offset"] }
|
||||
toml = "0.8.8"
|
||||
twox-hash = "1.6.3"
|
||||
|
||||
# For Linux static binaries, use rustls
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
reqwest = { version = "0.11.22", default-features = false, features = ["blocking", "json", "rustls"] }
|
||||
self_update = { version = "0.38.0", default-features = false, features = ["rustls"] }
|
||||
self_update = { version = "0.39.0", default-features = false, features = ["rustls"] }
|
||||
|
||||
# For all other platforms, use native TLS
|
||||
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
||||
reqwest = "0.11.22"
|
||||
self_update = "0.38.0"
|
||||
self_update = "0.39.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
path-slash = "0.2.1"
|
||||
|
@ -85,4 +89,4 @@ tracing-wasm = "0.2"
|
|||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.75"
|
||||
vergen = { version = "8.2.5", features = ["build", "cargo", "git", "gitcl"] }
|
||||
vergen = { version = "8.2.6", features = ["build", "cargo", "git", "gitcl"] }
|
||||
|
|
11
deny.toml
11
deny.toml
|
@ -47,13 +47,7 @@ yanked = "warn"
|
|||
notice = "warn"
|
||||
# A list of advisory IDs to ignore. Note that ignored advisories will still
|
||||
# output a note when they are encountered.
|
||||
ignore = [
|
||||
"RUSTSEC-2023-0022",
|
||||
"RUSTSEC-2023-0023",
|
||||
"RUSTSEC-2023-0024",
|
||||
"RUSTSEC-2023-0034",
|
||||
"RUSTSEC-2023-0044",
|
||||
]
|
||||
ignore = []
|
||||
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
|
||||
# lower than the range specified will be ignored. Note that ignored advisories
|
||||
# will still output a note when they are encountered.
|
||||
|
@ -76,6 +70,7 @@ unlicensed = "deny"
|
|||
allow = [
|
||||
"MIT",
|
||||
"Apache-2.0",
|
||||
"Apache-2.0 WITH LLVM-exception",
|
||||
"ISC",
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
|
@ -159,7 +154,7 @@ registries = [
|
|||
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
|
||||
[bans]
|
||||
# Lint level for when multiple versions of the same crate are detected
|
||||
multiple-versions = "warn"
|
||||
multiple-versions = "allow"
|
||||
# Lint level for when a crate version requirement is `*`
|
||||
wildcards = "allow"
|
||||
# The graph highlighting used when creating dotgraphs for crates
|
||||
|
|
78
src/app.rs
78
src/app.rs
|
@ -7,7 +7,6 @@ use std::{
|
|||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex, RwLock,
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use filetime::FileTime;
|
||||
|
@ -18,15 +17,20 @@ use time::UtcOffset;
|
|||
use crate::{
|
||||
app_config::{deserialize_config, AppConfigVersion},
|
||||
config::{build_globset, load_project_config, ProjectObject, ProjectObjectNode},
|
||||
diff::DiffAlg,
|
||||
jobs::{
|
||||
objdiff::{start_build, ObjDiffConfig},
|
||||
Job, JobQueue, JobResult, JobStatus,
|
||||
},
|
||||
views::{
|
||||
appearance::{appearance_window, Appearance},
|
||||
config::{config_ui, project_window, ConfigViewState, DEFAULT_WATCH_PATTERNS},
|
||||
config::{
|
||||
config_ui, diff_options_window, project_window, ConfigViewState, DEFAULT_WATCH_PATTERNS,
|
||||
},
|
||||
data_diff::data_diff_ui,
|
||||
debug::debug_window,
|
||||
demangle::{demangle_window, DemangleViewState},
|
||||
frame_history::FrameHistory,
|
||||
function_diff::function_diff_ui,
|
||||
jobs::jobs_ui,
|
||||
symbol_diff::{symbol_diff_ui, DiffViewState, View},
|
||||
|
@ -39,9 +43,12 @@ pub struct ViewState {
|
|||
pub config_state: ConfigViewState,
|
||||
pub demangle_state: DemangleViewState,
|
||||
pub diff_state: DiffViewState,
|
||||
pub frame_history: FrameHistory,
|
||||
pub show_appearance_config: bool,
|
||||
pub show_demangle: bool,
|
||||
pub show_project_config: bool,
|
||||
pub show_diff_options: bool,
|
||||
pub show_debug: bool,
|
||||
}
|
||||
|
||||
/// The configuration for a single object file.
|
||||
|
@ -98,6 +105,10 @@ pub struct AppConfig {
|
|||
pub watch_patterns: Vec<Glob>,
|
||||
#[serde(default)]
|
||||
pub recent_projects: Vec<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub code_alg: DiffAlg,
|
||||
#[serde(default)]
|
||||
pub data_alg: DiffAlg,
|
||||
|
||||
#[serde(skip)]
|
||||
pub objects: Vec<ProjectObject>,
|
||||
|
@ -133,6 +144,8 @@ impl Default for AppConfig {
|
|||
auto_update_check: true,
|
||||
watch_patterns: DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect(),
|
||||
recent_projects: vec![],
|
||||
code_alg: Default::default(),
|
||||
data_alg: Default::default(),
|
||||
objects: vec![],
|
||||
object_nodes: vec![],
|
||||
watcher_change: false,
|
||||
|
@ -224,7 +237,9 @@ impl App {
|
|||
if config.project_dir.is_some() {
|
||||
config.config_change = true;
|
||||
config.watcher_change = true;
|
||||
app.modified.store(true, Ordering::Relaxed);
|
||||
}
|
||||
if config.selected_obj.is_some() {
|
||||
config.queue_build = true;
|
||||
}
|
||||
app.view_state.config_state.queue_check_update = config.auto_update_check;
|
||||
app.config = Arc::new(RwLock::new(config));
|
||||
|
@ -245,7 +260,7 @@ impl App {
|
|||
log::info!("Job {} finished", job.id);
|
||||
match result {
|
||||
JobResult::None => {
|
||||
if let Some(err) = &job.status.read().unwrap().error {
|
||||
if let Some(err) = &job.context.status.read().unwrap().error {
|
||||
log::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
|
@ -266,12 +281,12 @@ impl App {
|
|||
} else {
|
||||
anyhow::Error::msg("Thread panicked")
|
||||
};
|
||||
let result = job.status.write();
|
||||
let result = job.context.status.write();
|
||||
if let Ok(mut guard) = result {
|
||||
guard.error = Some(err);
|
||||
} else {
|
||||
drop(result);
|
||||
job.status = Arc::new(RwLock::new(JobStatus {
|
||||
job.context.status = Arc::new(RwLock::new(JobStatus {
|
||||
title: "Error".to_string(),
|
||||
progress_percent: 0.0,
|
||||
progress_items: None,
|
||||
|
@ -286,14 +301,14 @@ impl App {
|
|||
jobs.clear_finished();
|
||||
|
||||
diff_state.pre_update(jobs, &self.config);
|
||||
config_state.pre_update(jobs);
|
||||
config_state.pre_update(jobs, &self.config);
|
||||
debug_assert!(jobs.results.is_empty());
|
||||
}
|
||||
|
||||
fn post_update(&mut self) {
|
||||
fn post_update(&mut self, ctx: &egui::Context) {
|
||||
let ViewState { jobs, diff_state, config_state, .. } = &mut self.view_state;
|
||||
config_state.post_update(jobs, &self.config);
|
||||
diff_state.post_update(jobs, &self.config);
|
||||
config_state.post_update(ctx, jobs, &self.config);
|
||||
diff_state.post_update(&self.config);
|
||||
|
||||
let Ok(mut config) = self.config.write() else {
|
||||
return;
|
||||
|
@ -323,7 +338,7 @@ impl App {
|
|||
if let Some(project_dir) = &config.project_dir {
|
||||
match build_globset(&config.watch_patterns).map_err(anyhow::Error::new).and_then(
|
||||
|globset| {
|
||||
create_watcher(self.modified.clone(), project_dir, globset)
|
||||
create_watcher(ctx.clone(), self.modified.clone(), project_dir, globset)
|
||||
.map_err(anyhow::Error::new)
|
||||
},
|
||||
) {
|
||||
|
@ -362,7 +377,7 @@ impl App {
|
|||
// Don't clear `queue_build` if a build is running. A file may have been modified during
|
||||
// the build, so we'll start another build after the current one finishes.
|
||||
if config.queue_build && config.selected_obj.is_some() && !jobs.is_running(Job::ObjDiff) {
|
||||
jobs.push(start_build(ObjDiffConfig::from_config(config)));
|
||||
jobs.push(start_build(ctx, ObjDiffConfig::from_config(config)));
|
||||
config.queue_build = false;
|
||||
config.queue_reload = false;
|
||||
} else if config.queue_reload && !jobs.is_running(Job::ObjDiff) {
|
||||
|
@ -370,7 +385,7 @@ impl App {
|
|||
// Don't build, just reload the current files
|
||||
diff_config.build_base = false;
|
||||
diff_config.build_target = false;
|
||||
jobs.push(start_build(diff_config));
|
||||
jobs.push(start_build(ctx, diff_config));
|
||||
config.queue_reload = false;
|
||||
}
|
||||
}
|
||||
|
@ -392,17 +407,27 @@ impl eframe::App for App {
|
|||
|
||||
let ViewState {
|
||||
jobs,
|
||||
show_appearance_config,
|
||||
demangle_state,
|
||||
show_demangle,
|
||||
diff_state,
|
||||
config_state,
|
||||
demangle_state,
|
||||
diff_state,
|
||||
frame_history,
|
||||
show_appearance_config,
|
||||
show_demangle,
|
||||
show_project_config,
|
||||
show_diff_options,
|
||||
show_debug,
|
||||
} = view_state;
|
||||
|
||||
frame_history.on_new_frame(ctx.input(|i| i.time), frame.info().cpu_usage);
|
||||
|
||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
ui.menu_button("File", |ui| {
|
||||
#[cfg(debug_assertions)]
|
||||
if ui.button("Debug…").clicked() {
|
||||
*show_debug = !*show_debug;
|
||||
ui.close_menu();
|
||||
}
|
||||
if ui.button("Project…").clicked() {
|
||||
*show_project_config = !*show_project_config;
|
||||
ui.close_menu();
|
||||
|
@ -443,6 +468,10 @@ impl eframe::App for App {
|
|||
}
|
||||
});
|
||||
ui.menu_button("Diff Options", |ui| {
|
||||
if ui.button("Algorithm…").clicked() {
|
||||
*show_diff_options = !*show_diff_options;
|
||||
ui.close_menu();
|
||||
}
|
||||
let mut config = config.write().unwrap();
|
||||
let response = ui
|
||||
.checkbox(&mut config.rebuild_on_changes, "Rebuild on changes")
|
||||
|
@ -493,16 +522,10 @@ impl eframe::App for App {
|
|||
project_window(ctx, config, show_project_config, config_state, appearance);
|
||||
appearance_window(ctx, show_appearance_config, appearance);
|
||||
demangle_window(ctx, show_demangle, demangle_state, appearance);
|
||||
diff_options_window(ctx, config, show_diff_options, appearance);
|
||||
debug_window(ctx, show_debug, frame_history, appearance);
|
||||
|
||||
self.post_update();
|
||||
|
||||
// Windows + request_repaint_after breaks dialogs:
|
||||
// https://github.com/emilk/egui/issues/2003
|
||||
if cfg!(windows) || self.view_state.jobs.any_running() {
|
||||
ctx.request_repaint();
|
||||
} else {
|
||||
ctx.request_repaint_after(Duration::from_millis(100));
|
||||
}
|
||||
self.post_update(ctx);
|
||||
}
|
||||
|
||||
/// Called by the frame work to save state before shutdown.
|
||||
|
@ -515,6 +538,7 @@ impl eframe::App for App {
|
|||
}
|
||||
|
||||
fn create_watcher(
|
||||
ctx: egui::Context,
|
||||
modified: Arc<AtomicBool>,
|
||||
project_dir: &Path,
|
||||
patterns: GlobSet,
|
||||
|
@ -534,7 +558,9 @@ fn create_watcher(
|
|||
continue;
|
||||
};
|
||||
if patterns.is_match(path) {
|
||||
log::info!("File modified: {}", path.display());
|
||||
modified.store(true, Ordering::Relaxed);
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
735
src/diff.rs
735
src/diff.rs
|
@ -1,735 +0,0 @@
|
|||
use std::{collections::BTreeMap, mem::take};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{
|
||||
editops::{editops_find, LevEditType},
|
||||
obj::{
|
||||
mips, ppc, ObjArchitecture, ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjInsArg,
|
||||
ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind, ObjReloc,
|
||||
ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags,
|
||||
},
|
||||
};
|
||||
|
||||
fn no_diff_code(
|
||||
arch: ObjArchitecture,
|
||||
data: &[u8],
|
||||
symbol: &mut ObjSymbol,
|
||||
relocs: &[ObjReloc],
|
||||
line_info: &Option<BTreeMap<u32, u32>>,
|
||||
) -> Result<()> {
|
||||
let code =
|
||||
&data[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
|
||||
let (_, ins) = match arch {
|
||||
ObjArchitecture::PowerPc => ppc::process_code(code, symbol.address, relocs, line_info)?,
|
||||
ObjArchitecture::Mips => mips::process_code(
|
||||
code,
|
||||
symbol.address,
|
||||
symbol.address + symbol.size,
|
||||
relocs,
|
||||
line_info,
|
||||
)?,
|
||||
};
|
||||
|
||||
let mut diff = Vec::<ObjInsDiff>::new();
|
||||
for i in ins {
|
||||
diff.push(ObjInsDiff { ins: Some(i), kind: ObjInsDiffKind::None, ..Default::default() });
|
||||
}
|
||||
resolve_branches(&mut diff);
|
||||
symbol.instructions = diff;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn diff_code(
|
||||
arch: ObjArchitecture,
|
||||
left_data: &[u8],
|
||||
right_data: &[u8],
|
||||
left_symbol: &mut ObjSymbol,
|
||||
right_symbol: &mut ObjSymbol,
|
||||
left_relocs: &[ObjReloc],
|
||||
right_relocs: &[ObjReloc],
|
||||
left_line_info: &Option<BTreeMap<u32, u32>>,
|
||||
right_line_info: &Option<BTreeMap<u32, u32>>,
|
||||
) -> Result<()> {
|
||||
let left_code = &left_data[left_symbol.section_address as usize
|
||||
..(left_symbol.section_address + left_symbol.size) as usize];
|
||||
let right_code = &right_data[right_symbol.section_address as usize
|
||||
..(right_symbol.section_address + right_symbol.size) as usize];
|
||||
let ((left_ops, left_insts), (right_ops, right_insts)) = match arch {
|
||||
ObjArchitecture::PowerPc => (
|
||||
ppc::process_code(left_code, left_symbol.address, left_relocs, left_line_info)?,
|
||||
ppc::process_code(right_code, right_symbol.address, right_relocs, right_line_info)?,
|
||||
),
|
||||
ObjArchitecture::Mips => (
|
||||
mips::process_code(
|
||||
left_code,
|
||||
left_symbol.address,
|
||||
left_symbol.address + left_symbol.size,
|
||||
left_relocs,
|
||||
left_line_info,
|
||||
)?,
|
||||
mips::process_code(
|
||||
right_code,
|
||||
right_symbol.address,
|
||||
left_symbol.address + left_symbol.size,
|
||||
right_relocs,
|
||||
right_line_info,
|
||||
)?,
|
||||
),
|
||||
};
|
||||
|
||||
let mut left_diff = Vec::<ObjInsDiff>::new();
|
||||
let mut right_diff = Vec::<ObjInsDiff>::new();
|
||||
let edit_ops = editops_find(&left_ops, &right_ops);
|
||||
|
||||
{
|
||||
let mut op_iter = edit_ops.iter();
|
||||
let mut left_iter = left_insts.iter();
|
||||
let mut right_iter = right_insts.iter();
|
||||
let mut cur_op = op_iter.next();
|
||||
let mut cur_left = left_iter.next();
|
||||
let mut cur_right = right_iter.next();
|
||||
while let Some(op) = cur_op {
|
||||
let left_addr = op.first_start as u32 * 4;
|
||||
let right_addr = op.second_start as u32 * 4;
|
||||
while let (Some(left), Some(right)) = (cur_left, cur_right) {
|
||||
if (left.address - left_symbol.address as u32) < left_addr {
|
||||
left_diff.push(ObjInsDiff { ins: Some(left.clone()), ..ObjInsDiff::default() });
|
||||
right_diff
|
||||
.push(ObjInsDiff { ins: Some(right.clone()), ..ObjInsDiff::default() });
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
cur_left = left_iter.next();
|
||||
cur_right = right_iter.next();
|
||||
}
|
||||
if let (Some(left), Some(right)) = (cur_left, cur_right) {
|
||||
if (left.address - left_symbol.address as u32) != left_addr {
|
||||
return Err(anyhow::Error::msg("Instruction address mismatch (left)"));
|
||||
}
|
||||
if (right.address - right_symbol.address as u32) != right_addr {
|
||||
return Err(anyhow::Error::msg("Instruction address mismatch (right)"));
|
||||
}
|
||||
match op.op_type {
|
||||
LevEditType::Replace => {
|
||||
left_diff
|
||||
.push(ObjInsDiff { ins: Some(left.clone()), ..ObjInsDiff::default() });
|
||||
right_diff
|
||||
.push(ObjInsDiff { ins: Some(right.clone()), ..ObjInsDiff::default() });
|
||||
cur_left = left_iter.next();
|
||||
cur_right = right_iter.next();
|
||||
}
|
||||
LevEditType::Insert => {
|
||||
left_diff.push(ObjInsDiff::default());
|
||||
right_diff
|
||||
.push(ObjInsDiff { ins: Some(right.clone()), ..ObjInsDiff::default() });
|
||||
cur_right = right_iter.next();
|
||||
}
|
||||
LevEditType::Delete => {
|
||||
left_diff
|
||||
.push(ObjInsDiff { ins: Some(left.clone()), ..ObjInsDiff::default() });
|
||||
right_diff.push(ObjInsDiff::default());
|
||||
cur_left = left_iter.next();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
cur_op = op_iter.next();
|
||||
}
|
||||
// Finalize
|
||||
while cur_left.is_some() || cur_right.is_some() {
|
||||
left_diff.push(ObjInsDiff { ins: cur_left.cloned(), ..ObjInsDiff::default() });
|
||||
right_diff.push(ObjInsDiff { ins: cur_right.cloned(), ..ObjInsDiff::default() });
|
||||
cur_left = left_iter.next();
|
||||
cur_right = right_iter.next();
|
||||
}
|
||||
}
|
||||
|
||||
resolve_branches(&mut left_diff);
|
||||
resolve_branches(&mut right_diff);
|
||||
|
||||
let mut diff_state = InsDiffState::default();
|
||||
for (left, right) in left_diff.iter_mut().zip(right_diff.iter_mut()) {
|
||||
let result = compare_ins(left, right, &mut diff_state)?;
|
||||
left.kind = result.kind;
|
||||
right.kind = result.kind;
|
||||
left.arg_diff = result.left_args_diff;
|
||||
right.arg_diff = result.right_args_diff;
|
||||
}
|
||||
|
||||
let total = left_insts.len();
|
||||
let percent = if diff_state.diff_count >= total {
|
||||
0.0
|
||||
} else {
|
||||
((total - diff_state.diff_count) as f32 / total as f32) * 100.0
|
||||
};
|
||||
left_symbol.match_percent = Some(percent);
|
||||
right_symbol.match_percent = Some(percent);
|
||||
|
||||
left_symbol.instructions = left_diff;
|
||||
right_symbol.instructions = right_diff;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_branches(vec: &mut [ObjInsDiff]) {
|
||||
let mut branch_idx = 0usize;
|
||||
// Map addresses to indices
|
||||
let mut addr_map = BTreeMap::<u32, usize>::new();
|
||||
for (i, ins_diff) in vec.iter().enumerate() {
|
||||
if let Some(ins) = &ins_diff.ins {
|
||||
addr_map.insert(ins.address, i);
|
||||
}
|
||||
}
|
||||
// Generate branches
|
||||
let mut branches = BTreeMap::<usize, ObjInsBranchFrom>::new();
|
||||
for (i, ins_diff) in vec.iter_mut().enumerate() {
|
||||
if let Some(ins) = &ins_diff.ins {
|
||||
// if ins.ins.is_blr() || ins.reloc.is_some() {
|
||||
// continue;
|
||||
// }
|
||||
if let Some(ins_idx) = ins
|
||||
.args
|
||||
.iter()
|
||||
.find_map(|a| if let ObjInsArg::BranchOffset(offs) = a { Some(offs) } else { None })
|
||||
.and_then(|offs| addr_map.get(&((ins.address as i32 + offs) as u32)))
|
||||
{
|
||||
if let Some(branch) = branches.get_mut(ins_idx) {
|
||||
ins_diff.branch_to =
|
||||
Some(ObjInsBranchTo { ins_idx: *ins_idx, branch_idx: branch.branch_idx });
|
||||
branch.ins_idx.push(i);
|
||||
} else {
|
||||
ins_diff.branch_to = Some(ObjInsBranchTo { ins_idx: *ins_idx, branch_idx });
|
||||
branches.insert(*ins_idx, ObjInsBranchFrom { ins_idx: vec![i], branch_idx });
|
||||
branch_idx += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Store branch from
|
||||
for (i, branch) in branches {
|
||||
vec[i].branch_from = Some(branch);
|
||||
}
|
||||
}
|
||||
|
||||
fn address_eq(left: &ObjSymbol, right: &ObjSymbol) -> bool {
|
||||
left.address as i64 + left.addend == right.address as i64 + right.addend
|
||||
}
|
||||
|
||||
fn reloc_eq(left_reloc: Option<&ObjReloc>, right_reloc: Option<&ObjReloc>) -> bool {
|
||||
let (Some(left), Some(right)) = (left_reloc, right_reloc) else {
|
||||
return false;
|
||||
};
|
||||
if left.kind != right.kind {
|
||||
return false;
|
||||
}
|
||||
|
||||
let name_matches = left.target.name == right.target.name;
|
||||
match (&left.target_section, &right.target_section) {
|
||||
(Some(sl), Some(sr)) => {
|
||||
// Match if section and name or address match
|
||||
sl == sr && (name_matches || address_eq(&left.target, &right.target))
|
||||
}
|
||||
(Some(_), None) => false,
|
||||
(None, Some(_)) => {
|
||||
// Match if possibly stripped weak symbol
|
||||
name_matches && right.target.flags.0.contains(ObjSymbolFlags::Weak)
|
||||
}
|
||||
(None, None) => name_matches,
|
||||
}
|
||||
}
|
||||
|
||||
fn arg_eq(
|
||||
left: &ObjInsArg,
|
||||
right: &ObjInsArg,
|
||||
left_diff: &ObjInsDiff,
|
||||
right_diff: &ObjInsDiff,
|
||||
) -> bool {
|
||||
return match left {
|
||||
ObjInsArg::PpcArg(l) => match right {
|
||||
ObjInsArg::PpcArg(r) => format!("{l}") == format!("{r}"),
|
||||
_ => false,
|
||||
},
|
||||
ObjInsArg::Reloc => {
|
||||
matches!(right, ObjInsArg::Reloc)
|
||||
&& reloc_eq(
|
||||
left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
||||
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
||||
)
|
||||
}
|
||||
ObjInsArg::RelocWithBase => {
|
||||
matches!(right, ObjInsArg::RelocWithBase)
|
||||
&& reloc_eq(
|
||||
left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
||||
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
||||
)
|
||||
}
|
||||
ObjInsArg::MipsArg(ls) | ObjInsArg::MipsArgWithBase(ls) => {
|
||||
matches!(right, ObjInsArg::MipsArg(rs) | ObjInsArg::MipsArgWithBase(rs) if ls == rs)
|
||||
}
|
||||
ObjInsArg::BranchOffset(_) => {
|
||||
// Compare dest instruction idx after diffing
|
||||
left_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
||||
== right_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct InsDiffState {
|
||||
diff_count: usize,
|
||||
left_arg_idx: usize,
|
||||
right_arg_idx: usize,
|
||||
left_args_idx: BTreeMap<String, usize>,
|
||||
right_args_idx: BTreeMap<String, usize>,
|
||||
}
|
||||
#[derive(Default)]
|
||||
struct InsDiffResult {
|
||||
kind: ObjInsDiffKind,
|
||||
left_args_diff: Vec<Option<ObjInsArgDiff>>,
|
||||
right_args_diff: Vec<Option<ObjInsArgDiff>>,
|
||||
}
|
||||
|
||||
fn compare_ins(
|
||||
left: &ObjInsDiff,
|
||||
right: &ObjInsDiff,
|
||||
state: &mut InsDiffState,
|
||||
) -> Result<InsDiffResult> {
|
||||
let mut result = InsDiffResult::default();
|
||||
if let (Some(left_ins), Some(right_ins)) = (&left.ins, &right.ins) {
|
||||
if left_ins.args.len() != right_ins.args.len() || left_ins.op != right_ins.op {
|
||||
// Totally different op
|
||||
result.kind = ObjInsDiffKind::Replace;
|
||||
state.diff_count += 1;
|
||||
return Ok(result);
|
||||
}
|
||||
if left_ins.mnemonic != right_ins.mnemonic {
|
||||
// Same op but different mnemonic, still cmp args
|
||||
result.kind = ObjInsDiffKind::OpMismatch;
|
||||
state.diff_count += 1;
|
||||
}
|
||||
for (a, b) in left_ins.args.iter().zip(&right_ins.args) {
|
||||
if arg_eq(a, b, left, right) {
|
||||
result.left_args_diff.push(None);
|
||||
result.right_args_diff.push(None);
|
||||
} else {
|
||||
if result.kind == ObjInsDiffKind::None {
|
||||
result.kind = ObjInsDiffKind::ArgMismatch;
|
||||
state.diff_count += 1;
|
||||
}
|
||||
let a_str = match a {
|
||||
ObjInsArg::PpcArg(arg) => format!("{arg}"),
|
||||
ObjInsArg::Reloc | ObjInsArg::RelocWithBase => String::new(),
|
||||
ObjInsArg::MipsArg(str) | ObjInsArg::MipsArgWithBase(str) => str.clone(),
|
||||
ObjInsArg::BranchOffset(arg) => format!("{arg}"),
|
||||
};
|
||||
let a_diff = if let Some(idx) = state.left_args_idx.get(&a_str) {
|
||||
ObjInsArgDiff { idx: *idx }
|
||||
} else {
|
||||
let idx = state.left_arg_idx;
|
||||
state.left_args_idx.insert(a_str, idx);
|
||||
state.left_arg_idx += 1;
|
||||
ObjInsArgDiff { idx }
|
||||
};
|
||||
let b_str = match b {
|
||||
ObjInsArg::PpcArg(arg) => format!("{arg}"),
|
||||
ObjInsArg::Reloc | ObjInsArg::RelocWithBase => String::new(),
|
||||
ObjInsArg::MipsArg(str) | ObjInsArg::MipsArgWithBase(str) => str.clone(),
|
||||
ObjInsArg::BranchOffset(arg) => format!("{arg}"),
|
||||
};
|
||||
let b_diff = if let Some(idx) = state.right_args_idx.get(&b_str) {
|
||||
ObjInsArgDiff { idx: *idx }
|
||||
} else {
|
||||
let idx = state.right_arg_idx;
|
||||
state.right_args_idx.insert(b_str, idx);
|
||||
state.right_arg_idx += 1;
|
||||
ObjInsArgDiff { idx }
|
||||
};
|
||||
result.left_args_diff.push(Some(a_diff));
|
||||
result.right_args_diff.push(Some(b_diff));
|
||||
}
|
||||
}
|
||||
} else if left.ins.is_some() {
|
||||
result.kind = ObjInsDiffKind::Delete;
|
||||
state.diff_count += 1;
|
||||
} else {
|
||||
result.kind = ObjInsDiffKind::Insert;
|
||||
state.diff_count += 1;
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn find_section_and_symbol(obj: &ObjInfo, name: &str) -> Option<(usize, usize)> {
|
||||
for (section_idx, section) in obj.sections.iter().enumerate() {
|
||||
let symbol_idx = match section.symbols.iter().position(|symbol| symbol.name == name) {
|
||||
Some(symbol_idx) => symbol_idx,
|
||||
None => continue,
|
||||
};
|
||||
return Some((section_idx, symbol_idx));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn diff_objs(mut left: Option<&mut ObjInfo>, mut right: Option<&mut ObjInfo>) -> Result<()> {
|
||||
if let Some(left) = left.as_mut() {
|
||||
for left_section in &mut left.sections {
|
||||
if left_section.kind == ObjSectionKind::Code {
|
||||
for left_symbol in &mut left_section.symbols {
|
||||
if let Some((right, (right_section_idx, right_symbol_idx))) =
|
||||
right.as_mut().and_then(|obj| {
|
||||
find_section_and_symbol(obj, &left_symbol.name).map(|s| (obj, s))
|
||||
})
|
||||
{
|
||||
let right_section = &mut right.sections[right_section_idx];
|
||||
let right_symbol = &mut right_section.symbols[right_symbol_idx];
|
||||
left_symbol.diff_symbol = Some(right_symbol.name.clone());
|
||||
right_symbol.diff_symbol = Some(left_symbol.name.clone());
|
||||
diff_code(
|
||||
left.architecture,
|
||||
&left_section.data,
|
||||
&right_section.data,
|
||||
left_symbol,
|
||||
right_symbol,
|
||||
&left_section.relocations,
|
||||
&right_section.relocations,
|
||||
&left.line_info,
|
||||
&right.line_info,
|
||||
)?;
|
||||
} else {
|
||||
no_diff_code(
|
||||
left.architecture,
|
||||
&left_section.data,
|
||||
left_symbol,
|
||||
&left_section.relocations,
|
||||
&left.line_info,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
} else if let Some(right_section) = right
|
||||
.as_mut()
|
||||
.and_then(|obj| obj.sections.iter_mut().find(|s| s.name == left_section.name))
|
||||
{
|
||||
if left_section.kind == ObjSectionKind::Data {
|
||||
diff_data(left_section, right_section);
|
||||
// diff_data_symbols(left_section, right_section)?;
|
||||
} else if left_section.kind == ObjSectionKind::Bss {
|
||||
diff_bss_symbols(&mut left_section.symbols, &mut right_section.symbols)?;
|
||||
}
|
||||
} else if left_section.kind == ObjSectionKind::Data {
|
||||
no_diff_data(left_section);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(right) = right.as_mut() {
|
||||
for right_section in right.sections.iter_mut() {
|
||||
if right_section.kind == ObjSectionKind::Code {
|
||||
for right_symbol in &mut right_section.symbols {
|
||||
if right_symbol.instructions.is_empty() {
|
||||
no_diff_code(
|
||||
right.architecture,
|
||||
&right_section.data,
|
||||
right_symbol,
|
||||
&right_section.relocations,
|
||||
&right.line_info,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
} else if right_section.kind == ObjSectionKind::Data
|
||||
&& right_section.data_diff.is_empty()
|
||||
{
|
||||
no_diff_data(right_section);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let (Some(left), Some(right)) = (left, right) {
|
||||
diff_bss_symbols(&mut left.common, &mut right.common)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn diff_bss_symbols(left_symbols: &mut [ObjSymbol], right_symbols: &mut [ObjSymbol]) -> Result<()> {
|
||||
for left_symbol in left_symbols {
|
||||
if let Some(right_symbol) = right_symbols.iter_mut().find(|s| s.name == left_symbol.name) {
|
||||
left_symbol.diff_symbol = Some(right_symbol.name.clone());
|
||||
right_symbol.diff_symbol = Some(left_symbol.name.clone());
|
||||
let percent = if left_symbol.size == right_symbol.size { 100.0 } else { 50.0 };
|
||||
left_symbol.match_percent = Some(percent);
|
||||
right_symbol.match_percent = Some(percent);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// WIP diff-by-symbol
|
||||
#[allow(dead_code)]
|
||||
fn diff_data_symbols(left: &mut ObjSection, right: &mut ObjSection) -> Result<()> {
|
||||
let mut left_ops = Vec::<u32>::with_capacity(left.symbols.len());
|
||||
let mut right_ops = Vec::<u32>::with_capacity(right.symbols.len());
|
||||
for left_symbol in &left.symbols {
|
||||
let data = &left.data
|
||||
[left_symbol.address as usize..(left_symbol.address + left_symbol.size) as usize];
|
||||
let hash = twox_hash::xxh3::hash64(data);
|
||||
left_ops.push(hash as u32);
|
||||
}
|
||||
for symbol in &right.symbols {
|
||||
let data = &right.data[symbol.address as usize..(symbol.address + symbol.size) as usize];
|
||||
let hash = twox_hash::xxh3::hash64(data);
|
||||
right_ops.push(hash as u32);
|
||||
}
|
||||
|
||||
let edit_ops = editops_find(&left_ops, &right_ops);
|
||||
if edit_ops.is_empty() && !left.data.is_empty() {
|
||||
let mut left_iter = left.symbols.iter_mut();
|
||||
let mut right_iter = right.symbols.iter_mut();
|
||||
loop {
|
||||
let (left_symbol, right_symbol) = match (left_iter.next(), right_iter.next()) {
|
||||
(Some(l), Some(r)) => (l, r),
|
||||
(None, None) => break,
|
||||
_ => return Err(anyhow::Error::msg("L/R mismatch in diff_data_symbols")),
|
||||
};
|
||||
let left_data = &left.data
|
||||
[left_symbol.address as usize..(left_symbol.address + left_symbol.size) as usize];
|
||||
let right_data = &right.data[right_symbol.address as usize
|
||||
..(right_symbol.address + right_symbol.size) as usize];
|
||||
|
||||
left.data_diff.push(ObjDataDiff {
|
||||
data: left_data.to_vec(),
|
||||
kind: ObjDataDiffKind::None,
|
||||
len: left_symbol.size as usize,
|
||||
symbol: left_symbol.name.clone(),
|
||||
});
|
||||
right.data_diff.push(ObjDataDiff {
|
||||
data: right_data.to_vec(),
|
||||
kind: ObjDataDiffKind::None,
|
||||
len: right_symbol.size as usize,
|
||||
symbol: right_symbol.name.clone(),
|
||||
});
|
||||
left_symbol.diff_symbol = Some(right_symbol.name.clone());
|
||||
left_symbol.match_percent = Some(100.0);
|
||||
right_symbol.diff_symbol = Some(left_symbol.name.clone());
|
||||
right_symbol.match_percent = Some(100.0);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn diff_data(left: &mut ObjSection, right: &mut ObjSection) {
|
||||
let edit_ops = editops_find(&left.data, &right.data);
|
||||
if edit_ops.is_empty() && !left.data.is_empty() {
|
||||
left.data_diff = vec![ObjDataDiff {
|
||||
data: left.data.clone(),
|
||||
kind: ObjDataDiffKind::None,
|
||||
len: left.data.len(),
|
||||
symbol: String::new(),
|
||||
}];
|
||||
right.data_diff = vec![ObjDataDiff {
|
||||
data: right.data.clone(),
|
||||
kind: ObjDataDiffKind::None,
|
||||
len: right.data.len(),
|
||||
symbol: String::new(),
|
||||
}];
|
||||
return;
|
||||
}
|
||||
|
||||
let mut left_diff = Vec::<ObjDataDiff>::new();
|
||||
let mut right_diff = Vec::<ObjDataDiff>::new();
|
||||
let mut left_cur = 0usize;
|
||||
let mut right_cur = 0usize;
|
||||
let mut cur_op = LevEditType::Replace;
|
||||
let mut cur_left_data = Vec::<u8>::new();
|
||||
let mut cur_right_data = Vec::<u8>::new();
|
||||
for op in edit_ops {
|
||||
if cur_op != op.op_type || left_cur < op.first_start || right_cur < op.second_start {
|
||||
match cur_op {
|
||||
LevEditType::Replace => {
|
||||
let left_data = take(&mut cur_left_data);
|
||||
let right_data = take(&mut cur_right_data);
|
||||
let left_data_len = left_data.len();
|
||||
let right_data_len = right_data.len();
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: left_data,
|
||||
kind: ObjDataDiffKind::Replace,
|
||||
len: left_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: right_data,
|
||||
kind: ObjDataDiffKind::Replace,
|
||||
len: right_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
}
|
||||
LevEditType::Insert => {
|
||||
let right_data = take(&mut cur_right_data);
|
||||
let right_data_len = right_data.len();
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: vec![],
|
||||
kind: ObjDataDiffKind::Insert,
|
||||
len: right_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: right_data,
|
||||
kind: ObjDataDiffKind::Insert,
|
||||
len: right_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
}
|
||||
LevEditType::Delete => {
|
||||
let left_data = take(&mut cur_left_data);
|
||||
let left_data_len = left_data.len();
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: left_data,
|
||||
kind: ObjDataDiffKind::Delete,
|
||||
len: left_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: vec![],
|
||||
kind: ObjDataDiffKind::Delete,
|
||||
len: left_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if left_cur < op.first_start {
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: left.data[left_cur..op.first_start].to_vec(),
|
||||
kind: ObjDataDiffKind::None,
|
||||
len: op.first_start - left_cur,
|
||||
symbol: String::new(),
|
||||
});
|
||||
left_cur = op.first_start;
|
||||
}
|
||||
if right_cur < op.second_start {
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: right.data[right_cur..op.second_start].to_vec(),
|
||||
kind: ObjDataDiffKind::None,
|
||||
len: op.second_start - right_cur,
|
||||
symbol: String::new(),
|
||||
});
|
||||
right_cur = op.second_start;
|
||||
}
|
||||
match op.op_type {
|
||||
LevEditType::Replace => {
|
||||
cur_left_data.push(left.data[left_cur]);
|
||||
cur_right_data.push(right.data[right_cur]);
|
||||
left_cur += 1;
|
||||
right_cur += 1;
|
||||
}
|
||||
LevEditType::Insert => {
|
||||
cur_right_data.push(right.data[right_cur]);
|
||||
right_cur += 1;
|
||||
}
|
||||
LevEditType::Delete => {
|
||||
cur_left_data.push(left.data[left_cur]);
|
||||
left_cur += 1;
|
||||
}
|
||||
}
|
||||
cur_op = op.op_type;
|
||||
}
|
||||
// if left_cur < left.data.len() {
|
||||
// let len = left.data.len() - left_cur;
|
||||
// left_diff.push(ObjDataDiff {
|
||||
// data: left.data[left_cur..].to_vec(),
|
||||
// kind: ObjDataDiffKind::Delete,
|
||||
// len,
|
||||
// });
|
||||
// right_diff.push(ObjDataDiff { data: vec![], kind: ObjDataDiffKind::Delete, len });
|
||||
// } else if right_cur < right.data.len() {
|
||||
// let len = right.data.len() - right_cur;
|
||||
// left_diff.push(ObjDataDiff { data: vec![], kind: ObjDataDiffKind::Insert, len });
|
||||
// right_diff.push(ObjDataDiff {
|
||||
// data: right.data[right_cur..].to_vec(),
|
||||
// kind: ObjDataDiffKind::Insert,
|
||||
// len,
|
||||
// });
|
||||
// }
|
||||
|
||||
// TODO: merge with above
|
||||
match cur_op {
|
||||
LevEditType::Replace => {
|
||||
let left_data = take(&mut cur_left_data);
|
||||
let right_data = take(&mut cur_right_data);
|
||||
let left_data_len = left_data.len();
|
||||
let right_data_len = right_data.len();
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: left_data,
|
||||
kind: ObjDataDiffKind::Replace,
|
||||
len: left_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: right_data,
|
||||
kind: ObjDataDiffKind::Replace,
|
||||
len: right_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
}
|
||||
LevEditType::Insert => {
|
||||
let right_data = take(&mut cur_right_data);
|
||||
let right_data_len = right_data.len();
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: vec![],
|
||||
kind: ObjDataDiffKind::Insert,
|
||||
len: right_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: right_data,
|
||||
kind: ObjDataDiffKind::Insert,
|
||||
len: right_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
}
|
||||
LevEditType::Delete => {
|
||||
let left_data = take(&mut cur_left_data);
|
||||
let left_data_len = left_data.len();
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: left_data,
|
||||
kind: ObjDataDiffKind::Delete,
|
||||
len: left_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: vec![],
|
||||
kind: ObjDataDiffKind::Delete,
|
||||
len: left_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if left_cur < left.data.len() {
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: left.data[left_cur..].to_vec(),
|
||||
kind: ObjDataDiffKind::None,
|
||||
len: left.data.len() - left_cur,
|
||||
symbol: String::new(),
|
||||
});
|
||||
}
|
||||
if right_cur < right.data.len() {
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: right.data[right_cur..].to_vec(),
|
||||
kind: ObjDataDiffKind::None,
|
||||
len: right.data.len() - right_cur,
|
||||
symbol: String::new(),
|
||||
});
|
||||
}
|
||||
|
||||
left.data_diff = left_diff;
|
||||
right.data_diff = right_diff;
|
||||
}
|
||||
|
||||
fn no_diff_data(section: &mut ObjSection) {
|
||||
section.data_diff = vec![ObjDataDiff {
|
||||
data: section.data.clone(),
|
||||
kind: ObjDataDiffKind::None,
|
||||
len: section.data.len(),
|
||||
symbol: String::new(),
|
||||
}];
|
||||
}
|
|
@ -0,0 +1,478 @@
|
|||
use std::{
|
||||
cmp::max,
|
||||
collections::BTreeMap,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use similar::{capture_diff_slices_deadline, Algorithm};
|
||||
|
||||
use crate::{
|
||||
diff::{
|
||||
editops::{editops_find, LevEditType},
|
||||
DiffAlg, ProcessCodeResult,
|
||||
},
|
||||
obj::{
|
||||
mips, ppc, ObjArchitecture, ObjInfo, ObjInsArg, ObjInsArgDiff, ObjInsBranchFrom,
|
||||
ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind, ObjReloc, ObjSymbol, ObjSymbolFlags,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn no_diff_code(
|
||||
arch: ObjArchitecture,
|
||||
data: &[u8],
|
||||
symbol: &mut ObjSymbol,
|
||||
relocs: &[ObjReloc],
|
||||
line_info: &Option<BTreeMap<u32, u32>>,
|
||||
) -> Result<()> {
|
||||
let code =
|
||||
&data[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
|
||||
let out = match arch {
|
||||
ObjArchitecture::PowerPc => ppc::process_code(code, symbol.address, relocs, line_info)?,
|
||||
ObjArchitecture::Mips => mips::process_code(
|
||||
code,
|
||||
symbol.address,
|
||||
symbol.address + symbol.size,
|
||||
relocs,
|
||||
line_info,
|
||||
)?,
|
||||
};
|
||||
|
||||
let mut diff = Vec::<ObjInsDiff>::new();
|
||||
for i in out.insts {
|
||||
diff.push(ObjInsDiff { ins: Some(i), kind: ObjInsDiffKind::None, ..Default::default() });
|
||||
}
|
||||
resolve_branches(&mut diff);
|
||||
symbol.instructions = diff;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn diff_code(
|
||||
alg: DiffAlg,
|
||||
arch: ObjArchitecture,
|
||||
left_data: &[u8],
|
||||
right_data: &[u8],
|
||||
left_symbol: &mut ObjSymbol,
|
||||
right_symbol: &mut ObjSymbol,
|
||||
left_relocs: &[ObjReloc],
|
||||
right_relocs: &[ObjReloc],
|
||||
left_line_info: &Option<BTreeMap<u32, u32>>,
|
||||
right_line_info: &Option<BTreeMap<u32, u32>>,
|
||||
) -> Result<()> {
|
||||
let left_code = &left_data[left_symbol.section_address as usize
|
||||
..(left_symbol.section_address + left_symbol.size) as usize];
|
||||
let right_code = &right_data[right_symbol.section_address as usize
|
||||
..(right_symbol.section_address + right_symbol.size) as usize];
|
||||
let (left_out, right_out) = match arch {
|
||||
ObjArchitecture::PowerPc => (
|
||||
ppc::process_code(left_code, left_symbol.address, left_relocs, left_line_info)?,
|
||||
ppc::process_code(right_code, right_symbol.address, right_relocs, right_line_info)?,
|
||||
),
|
||||
ObjArchitecture::Mips => (
|
||||
mips::process_code(
|
||||
left_code,
|
||||
left_symbol.address,
|
||||
left_symbol.address + left_symbol.size,
|
||||
left_relocs,
|
||||
left_line_info,
|
||||
)?,
|
||||
mips::process_code(
|
||||
right_code,
|
||||
right_symbol.address,
|
||||
left_symbol.address + left_symbol.size,
|
||||
right_relocs,
|
||||
right_line_info,
|
||||
)?,
|
||||
),
|
||||
};
|
||||
|
||||
let mut left_diff = Vec::<ObjInsDiff>::new();
|
||||
let mut right_diff = Vec::<ObjInsDiff>::new();
|
||||
match alg {
|
||||
DiffAlg::Levenshtein => {
|
||||
diff_instructions_lev(
|
||||
&mut left_diff,
|
||||
&mut right_diff,
|
||||
left_symbol,
|
||||
right_symbol,
|
||||
&left_out,
|
||||
&right_out,
|
||||
)?;
|
||||
}
|
||||
DiffAlg::Lcs => {
|
||||
diff_instructions_similar(
|
||||
Algorithm::Lcs,
|
||||
&mut left_diff,
|
||||
&mut right_diff,
|
||||
&left_out,
|
||||
&right_out,
|
||||
)?;
|
||||
}
|
||||
DiffAlg::Myers => {
|
||||
diff_instructions_similar(
|
||||
Algorithm::Myers,
|
||||
&mut left_diff,
|
||||
&mut right_diff,
|
||||
&left_out,
|
||||
&right_out,
|
||||
)?;
|
||||
}
|
||||
DiffAlg::Patience => {
|
||||
diff_instructions_similar(
|
||||
Algorithm::Patience,
|
||||
&mut left_diff,
|
||||
&mut right_diff,
|
||||
&left_out,
|
||||
&right_out,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
resolve_branches(&mut left_diff);
|
||||
resolve_branches(&mut right_diff);
|
||||
|
||||
let mut diff_state = InsDiffState::default();
|
||||
for (left, right) in left_diff.iter_mut().zip(right_diff.iter_mut()) {
|
||||
let result = compare_ins(left, right, &mut diff_state)?;
|
||||
left.kind = result.kind;
|
||||
right.kind = result.kind;
|
||||
left.arg_diff = result.left_args_diff;
|
||||
right.arg_diff = result.right_args_diff;
|
||||
}
|
||||
|
||||
let total = left_out.insts.len();
|
||||
let percent = if diff_state.diff_count >= total {
|
||||
0.0
|
||||
} else {
|
||||
((total - diff_state.diff_count) as f32 / total as f32) * 100.0
|
||||
};
|
||||
left_symbol.match_percent = Some(percent);
|
||||
right_symbol.match_percent = Some(percent);
|
||||
|
||||
left_symbol.instructions = left_diff;
|
||||
right_symbol.instructions = right_diff;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn diff_instructions_similar(
|
||||
alg: Algorithm,
|
||||
left_diff: &mut Vec<ObjInsDiff>,
|
||||
right_diff: &mut Vec<ObjInsDiff>,
|
||||
left_code: &ProcessCodeResult,
|
||||
right_code: &ProcessCodeResult,
|
||||
) -> Result<()> {
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
let ops = capture_diff_slices_deadline(alg, &left_code.ops, &right_code.ops, Some(deadline));
|
||||
if ops.is_empty() {
|
||||
left_diff.extend(
|
||||
left_code
|
||||
.insts
|
||||
.iter()
|
||||
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }),
|
||||
);
|
||||
right_diff.extend(
|
||||
right_code
|
||||
.insts
|
||||
.iter()
|
||||
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }),
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for op in ops {
|
||||
let (_tag, left_range, right_range) = op.as_tag_tuple();
|
||||
let len = max(left_range.len(), right_range.len());
|
||||
left_diff.extend(
|
||||
left_code.insts[left_range.clone()]
|
||||
.iter()
|
||||
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }),
|
||||
);
|
||||
right_diff.extend(
|
||||
right_code.insts[right_range.clone()]
|
||||
.iter()
|
||||
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }),
|
||||
);
|
||||
if left_range.len() < len {
|
||||
left_diff.extend((left_range.len()..len).map(|_| ObjInsDiff::default()));
|
||||
}
|
||||
if right_range.len() < len {
|
||||
right_diff.extend((right_range.len()..len).map(|_| ObjInsDiff::default()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn diff_instructions_lev(
|
||||
left_diff: &mut Vec<ObjInsDiff>,
|
||||
right_diff: &mut Vec<ObjInsDiff>,
|
||||
left_symbol: &ObjSymbol,
|
||||
right_symbol: &ObjSymbol,
|
||||
left_code: &ProcessCodeResult,
|
||||
right_code: &ProcessCodeResult,
|
||||
) -> Result<()> {
|
||||
let edit_ops = editops_find(&left_code.ops, &right_code.ops);
|
||||
|
||||
let mut op_iter = edit_ops.iter();
|
||||
let mut left_iter = left_code.insts.iter();
|
||||
let mut right_iter = right_code.insts.iter();
|
||||
let mut cur_op = op_iter.next();
|
||||
let mut cur_left = left_iter.next();
|
||||
let mut cur_right = right_iter.next();
|
||||
while let Some(op) = cur_op {
|
||||
let left_addr = op.first_start as u32 * 4;
|
||||
let right_addr = op.second_start as u32 * 4;
|
||||
while let (Some(left), Some(right)) = (cur_left, cur_right) {
|
||||
if (left.address - left_symbol.address as u32) < left_addr {
|
||||
left_diff.push(ObjInsDiff { ins: Some(left.clone()), ..ObjInsDiff::default() });
|
||||
right_diff.push(ObjInsDiff { ins: Some(right.clone()), ..ObjInsDiff::default() });
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
cur_left = left_iter.next();
|
||||
cur_right = right_iter.next();
|
||||
}
|
||||
if let (Some(left), Some(right)) = (cur_left, cur_right) {
|
||||
if (left.address - left_symbol.address as u32) != left_addr {
|
||||
return Err(anyhow::Error::msg("Instruction address mismatch (left)"));
|
||||
}
|
||||
if (right.address - right_symbol.address as u32) != right_addr {
|
||||
return Err(anyhow::Error::msg("Instruction address mismatch (right)"));
|
||||
}
|
||||
match op.op_type {
|
||||
LevEditType::Replace => {
|
||||
left_diff.push(ObjInsDiff { ins: Some(left.clone()), ..ObjInsDiff::default() });
|
||||
right_diff
|
||||
.push(ObjInsDiff { ins: Some(right.clone()), ..ObjInsDiff::default() });
|
||||
cur_left = left_iter.next();
|
||||
cur_right = right_iter.next();
|
||||
}
|
||||
LevEditType::Insert => {
|
||||
left_diff.push(ObjInsDiff::default());
|
||||
right_diff
|
||||
.push(ObjInsDiff { ins: Some(right.clone()), ..ObjInsDiff::default() });
|
||||
cur_right = right_iter.next();
|
||||
}
|
||||
LevEditType::Delete => {
|
||||
left_diff.push(ObjInsDiff { ins: Some(left.clone()), ..ObjInsDiff::default() });
|
||||
right_diff.push(ObjInsDiff::default());
|
||||
cur_left = left_iter.next();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
cur_op = op_iter.next();
|
||||
}
|
||||
|
||||
// Finalize
|
||||
while cur_left.is_some() || cur_right.is_some() {
|
||||
left_diff.push(ObjInsDiff { ins: cur_left.cloned(), ..ObjInsDiff::default() });
|
||||
right_diff.push(ObjInsDiff { ins: cur_right.cloned(), ..ObjInsDiff::default() });
|
||||
cur_left = left_iter.next();
|
||||
cur_right = right_iter.next();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_branches(vec: &mut [ObjInsDiff]) {
|
||||
let mut branch_idx = 0usize;
|
||||
// Map addresses to indices
|
||||
let mut addr_map = BTreeMap::<u32, usize>::new();
|
||||
for (i, ins_diff) in vec.iter().enumerate() {
|
||||
if let Some(ins) = &ins_diff.ins {
|
||||
addr_map.insert(ins.address, i);
|
||||
}
|
||||
}
|
||||
// Generate branches
|
||||
let mut branches = BTreeMap::<usize, ObjInsBranchFrom>::new();
|
||||
for (i, ins_diff) in vec.iter_mut().enumerate() {
|
||||
if let Some(ins) = &ins_diff.ins {
|
||||
// if ins.ins.is_blr() || ins.reloc.is_some() {
|
||||
// continue;
|
||||
// }
|
||||
if let Some(ins_idx) = ins
|
||||
.args
|
||||
.iter()
|
||||
.find_map(|a| if let ObjInsArg::BranchOffset(offs) = a { Some(offs) } else { None })
|
||||
.and_then(|offs| addr_map.get(&((ins.address as i32 + offs) as u32)))
|
||||
{
|
||||
if let Some(branch) = branches.get_mut(ins_idx) {
|
||||
ins_diff.branch_to =
|
||||
Some(ObjInsBranchTo { ins_idx: *ins_idx, branch_idx: branch.branch_idx });
|
||||
branch.ins_idx.push(i);
|
||||
} else {
|
||||
ins_diff.branch_to = Some(ObjInsBranchTo { ins_idx: *ins_idx, branch_idx });
|
||||
branches.insert(*ins_idx, ObjInsBranchFrom { ins_idx: vec![i], branch_idx });
|
||||
branch_idx += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Store branch from
|
||||
for (i, branch) in branches {
|
||||
vec[i].branch_from = Some(branch);
|
||||
}
|
||||
}
|
||||
|
||||
fn address_eq(left: &ObjSymbol, right: &ObjSymbol) -> bool {
|
||||
left.address as i64 + left.addend == right.address as i64 + right.addend
|
||||
}
|
||||
|
||||
fn reloc_eq(left_reloc: Option<&ObjReloc>, right_reloc: Option<&ObjReloc>) -> bool {
|
||||
let (Some(left), Some(right)) = (left_reloc, right_reloc) else {
|
||||
return false;
|
||||
};
|
||||
if left.kind != right.kind {
|
||||
return false;
|
||||
}
|
||||
|
||||
let name_matches = left.target.name == right.target.name;
|
||||
match (&left.target_section, &right.target_section) {
|
||||
(Some(sl), Some(sr)) => {
|
||||
// Match if section and name or address match
|
||||
sl == sr && (name_matches || address_eq(&left.target, &right.target))
|
||||
}
|
||||
(Some(_), None) => false,
|
||||
(None, Some(_)) => {
|
||||
// Match if possibly stripped weak symbol
|
||||
name_matches && right.target.flags.0.contains(ObjSymbolFlags::Weak)
|
||||
}
|
||||
(None, None) => name_matches,
|
||||
}
|
||||
}
|
||||
|
||||
fn arg_eq(
|
||||
left: &ObjInsArg,
|
||||
right: &ObjInsArg,
|
||||
left_diff: &ObjInsDiff,
|
||||
right_diff: &ObjInsDiff,
|
||||
) -> bool {
|
||||
return match left {
|
||||
ObjInsArg::PpcArg(l) => match right {
|
||||
ObjInsArg::PpcArg(r) => format!("{l}") == format!("{r}"),
|
||||
_ => false,
|
||||
},
|
||||
ObjInsArg::Reloc => {
|
||||
matches!(right, ObjInsArg::Reloc)
|
||||
&& reloc_eq(
|
||||
left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
||||
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
||||
)
|
||||
}
|
||||
ObjInsArg::RelocWithBase => {
|
||||
matches!(right, ObjInsArg::RelocWithBase)
|
||||
&& reloc_eq(
|
||||
left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
||||
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
||||
)
|
||||
}
|
||||
ObjInsArg::MipsArg(ls) | ObjInsArg::MipsArgWithBase(ls) => {
|
||||
matches!(right, ObjInsArg::MipsArg(rs) | ObjInsArg::MipsArgWithBase(rs) if ls == rs)
|
||||
}
|
||||
ObjInsArg::BranchOffset(_) => {
|
||||
// Compare dest instruction idx after diffing
|
||||
left_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
||||
== right_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct InsDiffState {
|
||||
diff_count: usize,
|
||||
left_arg_idx: usize,
|
||||
right_arg_idx: usize,
|
||||
left_args_idx: BTreeMap<String, usize>,
|
||||
right_args_idx: BTreeMap<String, usize>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct InsDiffResult {
|
||||
kind: ObjInsDiffKind,
|
||||
left_args_diff: Vec<Option<ObjInsArgDiff>>,
|
||||
right_args_diff: Vec<Option<ObjInsArgDiff>>,
|
||||
}
|
||||
|
||||
fn compare_ins(
|
||||
left: &ObjInsDiff,
|
||||
right: &ObjInsDiff,
|
||||
state: &mut InsDiffState,
|
||||
) -> Result<InsDiffResult> {
|
||||
let mut result = InsDiffResult::default();
|
||||
if let (Some(left_ins), Some(right_ins)) = (&left.ins, &right.ins) {
|
||||
if left_ins.args.len() != right_ins.args.len() || left_ins.op != right_ins.op {
|
||||
// Totally different op
|
||||
result.kind = ObjInsDiffKind::Replace;
|
||||
state.diff_count += 1;
|
||||
return Ok(result);
|
||||
}
|
||||
if left_ins.mnemonic != right_ins.mnemonic {
|
||||
// Same op but different mnemonic, still cmp args
|
||||
result.kind = ObjInsDiffKind::OpMismatch;
|
||||
state.diff_count += 1;
|
||||
}
|
||||
for (a, b) in left_ins.args.iter().zip(&right_ins.args) {
|
||||
if arg_eq(a, b, left, right) {
|
||||
result.left_args_diff.push(None);
|
||||
result.right_args_diff.push(None);
|
||||
} else {
|
||||
if result.kind == ObjInsDiffKind::None {
|
||||
result.kind = ObjInsDiffKind::ArgMismatch;
|
||||
state.diff_count += 1;
|
||||
}
|
||||
let a_str = match a {
|
||||
ObjInsArg::PpcArg(arg) => format!("{arg}"),
|
||||
ObjInsArg::Reloc | ObjInsArg::RelocWithBase => String::new(),
|
||||
ObjInsArg::MipsArg(str) | ObjInsArg::MipsArgWithBase(str) => str.clone(),
|
||||
ObjInsArg::BranchOffset(arg) => format!("{arg}"),
|
||||
};
|
||||
let a_diff = if let Some(idx) = state.left_args_idx.get(&a_str) {
|
||||
ObjInsArgDiff { idx: *idx }
|
||||
} else {
|
||||
let idx = state.left_arg_idx;
|
||||
state.left_args_idx.insert(a_str, idx);
|
||||
state.left_arg_idx += 1;
|
||||
ObjInsArgDiff { idx }
|
||||
};
|
||||
let b_str = match b {
|
||||
ObjInsArg::PpcArg(arg) => format!("{arg}"),
|
||||
ObjInsArg::Reloc | ObjInsArg::RelocWithBase => String::new(),
|
||||
ObjInsArg::MipsArg(str) | ObjInsArg::MipsArgWithBase(str) => str.clone(),
|
||||
ObjInsArg::BranchOffset(arg) => format!("{arg}"),
|
||||
};
|
||||
let b_diff = if let Some(idx) = state.right_args_idx.get(&b_str) {
|
||||
ObjInsArgDiff { idx: *idx }
|
||||
} else {
|
||||
let idx = state.right_arg_idx;
|
||||
state.right_args_idx.insert(b_str, idx);
|
||||
state.right_arg_idx += 1;
|
||||
ObjInsArgDiff { idx }
|
||||
};
|
||||
result.left_args_diff.push(Some(a_diff));
|
||||
result.right_args_diff.push(Some(b_diff));
|
||||
}
|
||||
}
|
||||
} else if left.ins.is_some() {
|
||||
result.kind = ObjInsDiffKind::Delete;
|
||||
state.diff_count += 1;
|
||||
} else {
|
||||
result.kind = ObjInsDiffKind::Insert;
|
||||
state.diff_count += 1;
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn find_section_and_symbol(obj: &ObjInfo, name: &str) -> Option<(usize, usize)> {
|
||||
for (section_idx, section) in obj.sections.iter().enumerate() {
|
||||
let symbol_idx = match section.symbols.iter().position(|symbol| symbol.name == name) {
|
||||
Some(symbol_idx) => symbol_idx,
|
||||
None => continue,
|
||||
};
|
||||
return Some((section_idx, symbol_idx));
|
||||
}
|
||||
None
|
||||
}
|
|
@ -0,0 +1,406 @@
|
|||
use std::{
|
||||
cmp::{max, min, Ordering},
|
||||
mem::take,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use similar::{capture_diff_slices_deadline, Algorithm};
|
||||
|
||||
use crate::{
|
||||
diff::{
|
||||
editops::{editops_find, LevEditType},
|
||||
DiffAlg,
|
||||
},
|
||||
obj::{ObjDataDiff, ObjDataDiffKind, ObjSection, ObjSymbol},
|
||||
};
|
||||
|
||||
pub fn diff_data(alg: DiffAlg, left: &mut ObjSection, right: &mut ObjSection) -> Result<()> {
|
||||
match alg {
|
||||
DiffAlg::Levenshtein => diff_data_lev(left, right),
|
||||
DiffAlg::Lcs => diff_data_similar(Algorithm::Lcs, left, right),
|
||||
DiffAlg::Myers => diff_data_similar(Algorithm::Myers, left, right),
|
||||
DiffAlg::Patience => diff_data_similar(Algorithm::Patience, left, right),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn diff_bss_symbols(
|
||||
left_symbols: &mut [ObjSymbol],
|
||||
right_symbols: &mut [ObjSymbol],
|
||||
) -> Result<()> {
|
||||
for left_symbol in left_symbols {
|
||||
if let Some(right_symbol) = right_symbols.iter_mut().find(|s| s.name == left_symbol.name) {
|
||||
left_symbol.diff_symbol = Some(right_symbol.name.clone());
|
||||
right_symbol.diff_symbol = Some(left_symbol.name.clone());
|
||||
let percent = if left_symbol.size == right_symbol.size { 100.0 } else { 50.0 };
|
||||
left_symbol.match_percent = Some(percent);
|
||||
right_symbol.match_percent = Some(percent);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// WIP diff-by-symbol
|
||||
#[allow(dead_code)]
|
||||
pub fn diff_data_symbols(left: &mut ObjSection, right: &mut ObjSection) -> Result<()> {
|
||||
let mut left_ops = Vec::<u32>::with_capacity(left.symbols.len());
|
||||
let mut right_ops = Vec::<u32>::with_capacity(right.symbols.len());
|
||||
for left_symbol in &left.symbols {
|
||||
let data = &left.data
|
||||
[left_symbol.address as usize..(left_symbol.address + left_symbol.size) as usize];
|
||||
let hash = twox_hash::xxh3::hash64(data);
|
||||
left_ops.push(hash as u32);
|
||||
}
|
||||
for symbol in &right.symbols {
|
||||
let data = &right.data[symbol.address as usize..(symbol.address + symbol.size) as usize];
|
||||
let hash = twox_hash::xxh3::hash64(data);
|
||||
right_ops.push(hash as u32);
|
||||
}
|
||||
|
||||
let edit_ops = editops_find(&left_ops, &right_ops);
|
||||
if edit_ops.is_empty() && !left.data.is_empty() {
|
||||
let mut left_iter = left.symbols.iter_mut();
|
||||
let mut right_iter = right.symbols.iter_mut();
|
||||
loop {
|
||||
let (left_symbol, right_symbol) = match (left_iter.next(), right_iter.next()) {
|
||||
(Some(l), Some(r)) => (l, r),
|
||||
(None, None) => break,
|
||||
_ => return Err(anyhow::Error::msg("L/R mismatch in diff_data_symbols")),
|
||||
};
|
||||
let left_data = &left.data
|
||||
[left_symbol.address as usize..(left_symbol.address + left_symbol.size) as usize];
|
||||
let right_data = &right.data[right_symbol.address as usize
|
||||
..(right_symbol.address + right_symbol.size) as usize];
|
||||
|
||||
left.data_diff.push(ObjDataDiff {
|
||||
data: left_data.to_vec(),
|
||||
kind: ObjDataDiffKind::None,
|
||||
len: left_symbol.size as usize,
|
||||
symbol: left_symbol.name.clone(),
|
||||
});
|
||||
right.data_diff.push(ObjDataDiff {
|
||||
data: right_data.to_vec(),
|
||||
kind: ObjDataDiffKind::None,
|
||||
len: right_symbol.size as usize,
|
||||
symbol: right_symbol.name.clone(),
|
||||
});
|
||||
left_symbol.diff_symbol = Some(right_symbol.name.clone());
|
||||
left_symbol.match_percent = Some(100.0);
|
||||
right_symbol.diff_symbol = Some(left_symbol.name.clone());
|
||||
right_symbol.match_percent = Some(100.0);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn diff_data_similar(
|
||||
alg: Algorithm,
|
||||
left: &mut ObjSection,
|
||||
right: &mut ObjSection,
|
||||
) -> Result<()> {
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
let ops = capture_diff_slices_deadline(alg, &left.data, &right.data, Some(deadline));
|
||||
|
||||
let mut left_diff = Vec::<ObjDataDiff>::new();
|
||||
let mut right_diff = Vec::<ObjDataDiff>::new();
|
||||
for op in ops {
|
||||
let (tag, left_range, right_range) = op.as_tag_tuple();
|
||||
let left_len = left_range.len();
|
||||
let right_len = right_range.len();
|
||||
let mut len = max(left_len, right_len);
|
||||
let kind = match tag {
|
||||
similar::DiffTag::Equal => ObjDataDiffKind::None,
|
||||
similar::DiffTag::Delete => ObjDataDiffKind::Delete,
|
||||
similar::DiffTag::Insert => ObjDataDiffKind::Insert,
|
||||
similar::DiffTag::Replace => {
|
||||
// Ensure replacements are equal length
|
||||
len = min(left_len, right_len);
|
||||
ObjDataDiffKind::Replace
|
||||
}
|
||||
};
|
||||
let left_data = &left.data[left_range];
|
||||
let right_data = &right.data[right_range];
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: left_data[..min(len, left_data.len())].to_vec(),
|
||||
kind,
|
||||
len,
|
||||
..Default::default()
|
||||
});
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: right_data[..min(len, right_data.len())].to_vec(),
|
||||
kind,
|
||||
len,
|
||||
..Default::default()
|
||||
});
|
||||
if kind == ObjDataDiffKind::Replace {
|
||||
match left_len.cmp(&right_len) {
|
||||
Ordering::Less => {
|
||||
let len = right_len - left_len;
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: vec![],
|
||||
kind: ObjDataDiffKind::Insert,
|
||||
len,
|
||||
..Default::default()
|
||||
});
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: right_data[left_len..right_len].to_vec(),
|
||||
kind: ObjDataDiffKind::Insert,
|
||||
len,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
Ordering::Greater => {
|
||||
let len = left_len - right_len;
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: left_data[right_len..left_len].to_vec(),
|
||||
kind: ObjDataDiffKind::Delete,
|
||||
len,
|
||||
..Default::default()
|
||||
});
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: vec![],
|
||||
kind: ObjDataDiffKind::Delete,
|
||||
len,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
Ordering::Equal => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
left.data_diff = left_diff;
|
||||
right.data_diff = right_diff;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn diff_data_lev(left: &mut ObjSection, right: &mut ObjSection) -> Result<()> {
|
||||
let matrix_size = (left.data.len() as u64).saturating_mul(right.data.len() as u64);
|
||||
if matrix_size > 1_000_000_000 {
|
||||
bail!(
|
||||
"Data section {} too large for Levenshtein diff ({} * {} = {})",
|
||||
left.name,
|
||||
left.data.len(),
|
||||
right.data.len(),
|
||||
matrix_size
|
||||
);
|
||||
}
|
||||
|
||||
let edit_ops = editops_find(&left.data, &right.data);
|
||||
if edit_ops.is_empty() && !left.data.is_empty() {
|
||||
left.data_diff = vec![ObjDataDiff {
|
||||
data: left.data.clone(),
|
||||
kind: ObjDataDiffKind::None,
|
||||
len: left.data.len(),
|
||||
symbol: String::new(),
|
||||
}];
|
||||
right.data_diff = vec![ObjDataDiff {
|
||||
data: right.data.clone(),
|
||||
kind: ObjDataDiffKind::None,
|
||||
len: right.data.len(),
|
||||
symbol: String::new(),
|
||||
}];
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut left_diff = Vec::<ObjDataDiff>::new();
|
||||
let mut right_diff = Vec::<ObjDataDiff>::new();
|
||||
let mut left_cur = 0usize;
|
||||
let mut right_cur = 0usize;
|
||||
let mut cur_op = LevEditType::Replace;
|
||||
let mut cur_left_data = Vec::<u8>::new();
|
||||
let mut cur_right_data = Vec::<u8>::new();
|
||||
for op in edit_ops {
|
||||
if cur_op != op.op_type || left_cur < op.first_start || right_cur < op.second_start {
|
||||
match cur_op {
|
||||
LevEditType::Replace => {
|
||||
let left_data = take(&mut cur_left_data);
|
||||
let right_data = take(&mut cur_right_data);
|
||||
let left_data_len = left_data.len();
|
||||
let right_data_len = right_data.len();
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: left_data,
|
||||
kind: ObjDataDiffKind::Replace,
|
||||
len: left_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: right_data,
|
||||
kind: ObjDataDiffKind::Replace,
|
||||
len: right_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
}
|
||||
LevEditType::Insert => {
|
||||
let right_data = take(&mut cur_right_data);
|
||||
let right_data_len = right_data.len();
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: vec![],
|
||||
kind: ObjDataDiffKind::Insert,
|
||||
len: right_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: right_data,
|
||||
kind: ObjDataDiffKind::Insert,
|
||||
len: right_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
}
|
||||
LevEditType::Delete => {
|
||||
let left_data = take(&mut cur_left_data);
|
||||
let left_data_len = left_data.len();
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: left_data,
|
||||
kind: ObjDataDiffKind::Delete,
|
||||
len: left_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: vec![],
|
||||
kind: ObjDataDiffKind::Delete,
|
||||
len: left_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if left_cur < op.first_start {
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: left.data[left_cur..op.first_start].to_vec(),
|
||||
kind: ObjDataDiffKind::None,
|
||||
len: op.first_start - left_cur,
|
||||
symbol: String::new(),
|
||||
});
|
||||
left_cur = op.first_start;
|
||||
}
|
||||
if right_cur < op.second_start {
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: right.data[right_cur..op.second_start].to_vec(),
|
||||
kind: ObjDataDiffKind::None,
|
||||
len: op.second_start - right_cur,
|
||||
symbol: String::new(),
|
||||
});
|
||||
right_cur = op.second_start;
|
||||
}
|
||||
match op.op_type {
|
||||
LevEditType::Replace => {
|
||||
cur_left_data.push(left.data[left_cur]);
|
||||
cur_right_data.push(right.data[right_cur]);
|
||||
left_cur += 1;
|
||||
right_cur += 1;
|
||||
}
|
||||
LevEditType::Insert => {
|
||||
cur_right_data.push(right.data[right_cur]);
|
||||
right_cur += 1;
|
||||
}
|
||||
LevEditType::Delete => {
|
||||
cur_left_data.push(left.data[left_cur]);
|
||||
left_cur += 1;
|
||||
}
|
||||
}
|
||||
cur_op = op.op_type;
|
||||
}
|
||||
// if left_cur < left.data.len() {
|
||||
// let len = left.data.len() - left_cur;
|
||||
// left_diff.push(ObjDataDiff {
|
||||
// data: left.data[left_cur..].to_vec(),
|
||||
// kind: ObjDataDiffKind::Delete,
|
||||
// len,
|
||||
// });
|
||||
// right_diff.push(ObjDataDiff { data: vec![], kind: ObjDataDiffKind::Delete, len });
|
||||
// } else if right_cur < right.data.len() {
|
||||
// let len = right.data.len() - right_cur;
|
||||
// left_diff.push(ObjDataDiff { data: vec![], kind: ObjDataDiffKind::Insert, len });
|
||||
// right_diff.push(ObjDataDiff {
|
||||
// data: right.data[right_cur..].to_vec(),
|
||||
// kind: ObjDataDiffKind::Insert,
|
||||
// len,
|
||||
// });
|
||||
// }
|
||||
|
||||
// TODO: merge with above
|
||||
match cur_op {
|
||||
LevEditType::Replace => {
|
||||
let left_data = take(&mut cur_left_data);
|
||||
let right_data = take(&mut cur_right_data);
|
||||
let left_data_len = left_data.len();
|
||||
let right_data_len = right_data.len();
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: left_data,
|
||||
kind: ObjDataDiffKind::Replace,
|
||||
len: left_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: right_data,
|
||||
kind: ObjDataDiffKind::Replace,
|
||||
len: right_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
}
|
||||
LevEditType::Insert => {
|
||||
let right_data = take(&mut cur_right_data);
|
||||
let right_data_len = right_data.len();
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: vec![],
|
||||
kind: ObjDataDiffKind::Insert,
|
||||
len: right_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: right_data,
|
||||
kind: ObjDataDiffKind::Insert,
|
||||
len: right_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
}
|
||||
LevEditType::Delete => {
|
||||
let left_data = take(&mut cur_left_data);
|
||||
let left_data_len = left_data.len();
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: left_data,
|
||||
kind: ObjDataDiffKind::Delete,
|
||||
len: left_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: vec![],
|
||||
kind: ObjDataDiffKind::Delete,
|
||||
len: left_data_len,
|
||||
symbol: String::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if left_cur < left.data.len() {
|
||||
left_diff.push(ObjDataDiff {
|
||||
data: left.data[left_cur..].to_vec(),
|
||||
kind: ObjDataDiffKind::None,
|
||||
len: left.data.len() - left_cur,
|
||||
symbol: String::new(),
|
||||
});
|
||||
}
|
||||
if right_cur < right.data.len() {
|
||||
right_diff.push(ObjDataDiff {
|
||||
data: right.data[right_cur..].to_vec(),
|
||||
kind: ObjDataDiffKind::None,
|
||||
len: right.data.len() - right_cur,
|
||||
symbol: String::new(),
|
||||
});
|
||||
}
|
||||
|
||||
left.data_diff = left_diff;
|
||||
right.data_diff = right_diff;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn no_diff_data(section: &mut ObjSection) {
|
||||
section.data_diff = vec![ObjDataDiff {
|
||||
data: section.data.clone(),
|
||||
kind: ObjDataDiffKind::None,
|
||||
len: section.data.len(),
|
||||
symbol: String::new(),
|
||||
}];
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
pub mod code;
|
||||
pub mod data;
|
||||
pub mod editops;
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
diff::{
|
||||
code::{diff_code, find_section_and_symbol, no_diff_code},
|
||||
data::{diff_bss_symbols, diff_data, no_diff_data},
|
||||
},
|
||||
obj::{ObjInfo, ObjIns, ObjSectionKind},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
pub enum DiffAlg {
|
||||
#[default]
|
||||
Patience,
|
||||
Levenshtein,
|
||||
Myers,
|
||||
Lcs,
|
||||
}
|
||||
|
||||
pub struct DiffObjConfig {
|
||||
pub code_alg: DiffAlg,
|
||||
pub data_alg: DiffAlg,
|
||||
}
|
||||
|
||||
pub struct ProcessCodeResult {
|
||||
pub ops: Vec<u8>,
|
||||
pub insts: Vec<ObjIns>,
|
||||
}
|
||||
|
||||
pub fn diff_objs(
|
||||
config: &DiffObjConfig,
|
||||
mut left: Option<&mut ObjInfo>,
|
||||
mut right: Option<&mut ObjInfo>,
|
||||
) -> Result<()> {
|
||||
if let Some(left) = left.as_mut() {
|
||||
for left_section in &mut left.sections {
|
||||
if left_section.kind == ObjSectionKind::Code {
|
||||
for left_symbol in &mut left_section.symbols {
|
||||
if let Some((right, (right_section_idx, right_symbol_idx))) =
|
||||
right.as_mut().and_then(|obj| {
|
||||
find_section_and_symbol(obj, &left_symbol.name).map(|s| (obj, s))
|
||||
})
|
||||
{
|
||||
let right_section = &mut right.sections[right_section_idx];
|
||||
let right_symbol = &mut right_section.symbols[right_symbol_idx];
|
||||
left_symbol.diff_symbol = Some(right_symbol.name.clone());
|
||||
right_symbol.diff_symbol = Some(left_symbol.name.clone());
|
||||
diff_code(
|
||||
config.code_alg,
|
||||
left.architecture,
|
||||
&left_section.data,
|
||||
&right_section.data,
|
||||
left_symbol,
|
||||
right_symbol,
|
||||
&left_section.relocations,
|
||||
&right_section.relocations,
|
||||
&left.line_info,
|
||||
&right.line_info,
|
||||
)?;
|
||||
} else {
|
||||
no_diff_code(
|
||||
left.architecture,
|
||||
&left_section.data,
|
||||
left_symbol,
|
||||
&left_section.relocations,
|
||||
&left.line_info,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
} else if let Some(right_section) = right
|
||||
.as_mut()
|
||||
.and_then(|obj| obj.sections.iter_mut().find(|s| s.name == left_section.name))
|
||||
{
|
||||
if left_section.kind == ObjSectionKind::Data {
|
||||
diff_data(config.data_alg, left_section, right_section)?;
|
||||
} else if left_section.kind == ObjSectionKind::Bss {
|
||||
diff_bss_symbols(&mut left_section.symbols, &mut right_section.symbols)?;
|
||||
}
|
||||
} else if left_section.kind == ObjSectionKind::Data {
|
||||
no_diff_data(left_section);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(right) = right.as_mut() {
|
||||
for right_section in right.sections.iter_mut() {
|
||||
if right_section.kind == ObjSectionKind::Code {
|
||||
for right_symbol in &mut right_section.symbols {
|
||||
if right_symbol.instructions.is_empty() {
|
||||
no_diff_code(
|
||||
right.architecture,
|
||||
&right_section.data,
|
||||
right_symbol,
|
||||
&right_section.relocations,
|
||||
&right.line_info,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
} else if right_section.kind == ObjSectionKind::Data
|
||||
&& right_section.data_diff.is_empty()
|
||||
{
|
||||
no_diff_data(right_section);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let (Some(left), Some(right)) = (left, right) {
|
||||
diff_bss_symbols(&mut left.common, &mut right.common)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
|
@ -4,7 +4,7 @@ use anyhow::{Context, Result};
|
|||
use self_update::{cargo_crate_version, update::Release};
|
||||
|
||||
use crate::{
|
||||
jobs::{start_job, update_status, Job, JobResult, JobState, JobStatusRef},
|
||||
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState},
|
||||
update::{build_updater, BIN_NAME},
|
||||
};
|
||||
|
||||
|
@ -14,20 +14,20 @@ pub struct CheckUpdateResult {
|
|||
pub found_binary: bool,
|
||||
}
|
||||
|
||||
fn run_check_update(status: &JobStatusRef, cancel: Receiver<()>) -> Result<Box<CheckUpdateResult>> {
|
||||
update_status(status, "Fetching latest release".to_string(), 0, 1, &cancel)?;
|
||||
fn run_check_update(context: &JobContext, cancel: Receiver<()>) -> Result<Box<CheckUpdateResult>> {
|
||||
update_status(context, "Fetching latest release".to_string(), 0, 1, &cancel)?;
|
||||
let updater = build_updater().context("Failed to create release updater")?;
|
||||
let latest_release = updater.get_latest_release()?;
|
||||
let update_available =
|
||||
self_update::version::bump_is_greater(cargo_crate_version!(), &latest_release.version)?;
|
||||
let found_binary = latest_release.assets.iter().any(|a| a.name == BIN_NAME);
|
||||
|
||||
update_status(status, "Complete".to_string(), 1, 1, &cancel)?;
|
||||
update_status(context, "Complete".to_string(), 1, 1, &cancel)?;
|
||||
Ok(Box::new(CheckUpdateResult { update_available, latest_release, found_binary }))
|
||||
}
|
||||
|
||||
pub fn start_check_update() -> JobState {
|
||||
start_job("Check for updates", Job::CheckUpdate, move |status, cancel| {
|
||||
run_check_update(status, cancel).map(|result| JobResult::CheckUpdate(Some(result)))
|
||||
pub fn start_check_update(ctx: &egui::Context) -> JobState {
|
||||
start_job(ctx, "Check for updates", Job::CheckUpdate, move |context, cancel| {
|
||||
run_check_update(&context, cancel).map(|result| JobResult::CheckUpdate(Some(result)))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ impl JobQueue {
|
|||
}
|
||||
|
||||
/// Returns whether any job is running.
|
||||
#[allow(dead_code)]
|
||||
pub fn any_running(&self) -> bool {
|
||||
self.jobs.iter().any(|job| {
|
||||
if let Some(handle) = &job.handle {
|
||||
|
@ -81,7 +82,7 @@ impl JobQueue {
|
|||
self.jobs.retain(|job| {
|
||||
!(job.should_remove
|
||||
&& job.handle.is_none()
|
||||
&& job.status.read().unwrap().error.is_none())
|
||||
&& job.context.status.read().unwrap().error.is_none())
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -89,13 +90,17 @@ impl JobQueue {
|
|||
pub fn remove(&mut self, id: usize) { self.jobs.retain(|job| job.id != id); }
|
||||
}
|
||||
|
||||
pub type JobStatusRef = Arc<RwLock<JobStatus>>;
|
||||
#[derive(Clone)]
|
||||
pub struct JobContext {
|
||||
pub status: Arc<RwLock<JobStatus>>,
|
||||
pub egui: egui::Context,
|
||||
}
|
||||
|
||||
pub struct JobState {
|
||||
pub id: usize,
|
||||
pub kind: Job,
|
||||
pub handle: Option<JoinHandle<JobResult>>,
|
||||
pub status: JobStatusRef,
|
||||
pub context: JobContext,
|
||||
pub cancel: Sender<()>,
|
||||
pub should_remove: bool,
|
||||
}
|
||||
|
@ -124,9 +129,10 @@ fn should_cancel(rx: &Receiver<()>) -> bool {
|
|||
}
|
||||
|
||||
fn start_job(
|
||||
ctx: &egui::Context,
|
||||
title: &str,
|
||||
kind: Job,
|
||||
run: impl FnOnce(&JobStatusRef, Receiver<()>) -> Result<JobResult> + Send + 'static,
|
||||
run: impl FnOnce(JobContext, Receiver<()>) -> Result<JobResult> + Send + 'static,
|
||||
) -> JobState {
|
||||
let status = Arc::new(RwLock::new(JobStatus {
|
||||
title: title.to_string(),
|
||||
|
@ -135,10 +141,11 @@ fn start_job(
|
|||
status: String::new(),
|
||||
error: None,
|
||||
}));
|
||||
let status_clone = status.clone();
|
||||
let context = JobContext { status: status.clone(), egui: ctx.clone() };
|
||||
let context_inner = JobContext { status: status.clone(), egui: ctx.clone() };
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let handle = std::thread::spawn(move || {
|
||||
return match run(&status, rx) {
|
||||
return match run(context_inner, rx) {
|
||||
Ok(state) => state,
|
||||
Err(e) => {
|
||||
if let Ok(mut w) = status.write() {
|
||||
|
@ -150,24 +157,18 @@ fn start_job(
|
|||
});
|
||||
let id = JOB_ID.fetch_add(1, Ordering::Relaxed);
|
||||
log::info!("Started job {}", id);
|
||||
JobState {
|
||||
id,
|
||||
kind,
|
||||
handle: Some(handle),
|
||||
status: status_clone,
|
||||
cancel: tx,
|
||||
should_remove: true,
|
||||
}
|
||||
JobState { id, kind, handle: Some(handle), context, cancel: tx, should_remove: true }
|
||||
}
|
||||
|
||||
fn update_status(
|
||||
status: &JobStatusRef,
|
||||
context: &JobContext,
|
||||
str: String,
|
||||
count: u32,
|
||||
total: u32,
|
||||
cancel: &Receiver<()>,
|
||||
) -> Result<()> {
|
||||
let mut w = status.write().map_err(|_| anyhow::Error::msg("Failed to lock job status"))?;
|
||||
let mut w =
|
||||
context.status.write().map_err(|_| anyhow::Error::msg("Failed to lock job status"))?;
|
||||
w.progress_items = Some([count, total]);
|
||||
w.progress_percent = count as f32 / total as f32;
|
||||
if should_cancel(cancel) {
|
||||
|
@ -176,5 +177,7 @@ fn update_status(
|
|||
} else {
|
||||
w.status = str;
|
||||
}
|
||||
drop(w);
|
||||
context.egui.request_repaint();
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@ use time::OffsetDateTime;
|
|||
|
||||
use crate::{
|
||||
app::{AppConfig, ObjectConfig},
|
||||
diff::diff_objs,
|
||||
jobs::{start_job, update_status, Job, JobResult, JobState, JobStatusRef},
|
||||
diff::{diff_objs, DiffAlg, DiffObjConfig},
|
||||
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState},
|
||||
obj::{elf, ObjInfo},
|
||||
};
|
||||
|
||||
|
@ -27,6 +27,8 @@ pub struct ObjDiffConfig {
|
|||
pub project_dir: Option<PathBuf>,
|
||||
pub selected_obj: Option<ObjectConfig>,
|
||||
pub selected_wsl_distro: Option<String>,
|
||||
pub code_alg: DiffAlg,
|
||||
pub data_alg: DiffAlg,
|
||||
}
|
||||
|
||||
impl ObjDiffConfig {
|
||||
|
@ -38,6 +40,8 @@ impl ObjDiffConfig {
|
|||
project_dir: config.project_dir.clone(),
|
||||
selected_obj: config.selected_obj.clone(),
|
||||
selected_wsl_distro: config.selected_wsl_distro.clone(),
|
||||
code_alg: config.code_alg,
|
||||
data_alg: config.data_alg,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +102,7 @@ fn run_make(cwd: &Path, arg: &Path, config: &ObjDiffConfig) -> BuildStatus {
|
|||
}
|
||||
|
||||
fn run_build(
|
||||
status: &JobStatusRef,
|
||||
context: &JobContext,
|
||||
cancel: Receiver<()>,
|
||||
config: ObjDiffConfig,
|
||||
) -> Result<Box<ObjDiffResult>> {
|
||||
|
@ -138,7 +142,7 @@ fn run_build(
|
|||
let first_status = match target_path_rel {
|
||||
Some(target_path_rel) if config.build_target => {
|
||||
update_status(
|
||||
status,
|
||||
context,
|
||||
format!("Building target {}", target_path_rel.display()),
|
||||
0,
|
||||
total,
|
||||
|
@ -152,7 +156,7 @@ fn run_build(
|
|||
let second_status = match base_path_rel {
|
||||
Some(base_path_rel) if config.build_base => {
|
||||
update_status(
|
||||
status,
|
||||
context,
|
||||
format!("Building base {}", base_path_rel.display()),
|
||||
0,
|
||||
total,
|
||||
|
@ -169,7 +173,7 @@ fn run_build(
|
|||
match &obj_config.target_path {
|
||||
Some(target_path) if first_status.success => {
|
||||
update_status(
|
||||
status,
|
||||
context,
|
||||
format!("Loading target {}", target_path_rel.unwrap().display()),
|
||||
2,
|
||||
total,
|
||||
|
@ -185,7 +189,7 @@ fn run_build(
|
|||
let mut second_obj = match &obj_config.base_path {
|
||||
Some(base_path) if second_status.success => {
|
||||
update_status(
|
||||
status,
|
||||
context,
|
||||
format!("Loading base {}", base_path_rel.unwrap().display()),
|
||||
3,
|
||||
total,
|
||||
|
@ -199,15 +203,16 @@ fn run_build(
|
|||
_ => None,
|
||||
};
|
||||
|
||||
update_status(status, "Performing diff".to_string(), 4, total, &cancel)?;
|
||||
diff_objs(first_obj.as_mut(), second_obj.as_mut())?;
|
||||
update_status(context, "Performing diff".to_string(), 4, total, &cancel)?;
|
||||
let diff_config = DiffObjConfig { code_alg: config.code_alg, data_alg: config.data_alg };
|
||||
diff_objs(&diff_config, first_obj.as_mut(), second_obj.as_mut())?;
|
||||
|
||||
update_status(status, "Complete".to_string(), total, total, &cancel)?;
|
||||
update_status(context, "Complete".to_string(), total, total, &cancel)?;
|
||||
Ok(Box::new(ObjDiffResult { first_status, second_status, first_obj, second_obj, time }))
|
||||
}
|
||||
|
||||
pub fn start_build(config: ObjDiffConfig) -> JobState {
|
||||
start_job("Object diff", Job::ObjDiff, move |status, cancel| {
|
||||
run_build(status, cancel, config).map(|result| JobResult::ObjDiff(Some(result)))
|
||||
pub fn start_build(ctx: &egui::Context, config: ObjDiffConfig) -> JobState {
|
||||
start_job(ctx, "Object diff", Job::ObjDiff, move |context, cancel| {
|
||||
run_build(&context, cancel, config).map(|result| JobResult::ObjDiff(Some(result)))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use anyhow::{Context, Result};
|
|||
use const_format::formatcp;
|
||||
|
||||
use crate::{
|
||||
jobs::{start_job, update_status, Job, JobResult, JobState, JobStatusRef},
|
||||
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState},
|
||||
update::{build_updater, BIN_NAME},
|
||||
};
|
||||
|
||||
|
@ -17,7 +17,7 @@ pub struct UpdateResult {
|
|||
pub exe_path: PathBuf,
|
||||
}
|
||||
|
||||
fn run_update(status: &JobStatusRef, cancel: Receiver<()>) -> Result<Box<UpdateResult>> {
|
||||
fn run_update(status: &JobContext, cancel: Receiver<()>) -> Result<Box<UpdateResult>> {
|
||||
update_status(status, "Fetching latest release".to_string(), 0, 3, &cancel)?;
|
||||
let updater = build_updater().context("Failed to create release updater")?;
|
||||
let latest_release = updater.get_latest_release()?;
|
||||
|
@ -53,8 +53,8 @@ fn run_update(status: &JobStatusRef, cancel: Receiver<()>) -> Result<Box<UpdateR
|
|||
Ok(Box::from(UpdateResult { exe_path: target_file }))
|
||||
}
|
||||
|
||||
pub fn start_update() -> JobState {
|
||||
start_job("Update app", Job::Update, move |status, cancel| {
|
||||
run_update(status, cancel).map(JobResult::Update)
|
||||
pub fn start_update(ctx: &egui::Context) -> JobState {
|
||||
start_job(ctx, "Update app", Job::Update, move |context, cancel| {
|
||||
run_update(&context, cancel).map(JobResult::Update)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ mod app;
|
|||
mod app_config;
|
||||
mod config;
|
||||
mod diff;
|
||||
mod editops;
|
||||
mod jobs;
|
||||
mod obj;
|
||||
mod update;
|
||||
|
|
|
@ -298,7 +298,7 @@ fn line_info(obj_file: &File<'_>) -> Result<Option<BTreeMap<u32, u32>>> {
|
|||
let address_delta = reader.read_u32::<BigEndian>()?;
|
||||
map.insert(base_address + address_delta, line_number);
|
||||
}
|
||||
log::debug!("Line info: {map:#X?}");
|
||||
// log::debug!("Line info: {map:#X?}");
|
||||
return Ok(Some(map));
|
||||
}
|
||||
Ok(None)
|
||||
|
|
|
@ -3,7 +3,10 @@ use std::collections::BTreeMap;
|
|||
use anyhow::Result;
|
||||
use rabbitizer::{config, Abi, InstrCategory, Instruction, OperandType};
|
||||
|
||||
use crate::obj::{ObjIns, ObjInsArg, ObjReloc};
|
||||
use crate::{
|
||||
diff::ProcessCodeResult,
|
||||
obj::{ObjIns, ObjInsArg, ObjReloc},
|
||||
};
|
||||
|
||||
fn configure_rabbitizer() {
|
||||
unsafe {
|
||||
|
@ -17,7 +20,7 @@ pub fn process_code(
|
|||
end_address: u64,
|
||||
relocs: &[ObjReloc],
|
||||
line_info: &Option<BTreeMap<u32, u32>>,
|
||||
) -> Result<(Vec<u8>, Vec<ObjIns>)> {
|
||||
) -> Result<ProcessCodeResult> {
|
||||
configure_rabbitizer();
|
||||
|
||||
let ins_count = data.len() / 4;
|
||||
|
@ -95,5 +98,5 @@ pub fn process_code(
|
|||
});
|
||||
cur_addr += 4;
|
||||
}
|
||||
Ok((ops, insts))
|
||||
Ok(ProcessCodeResult { ops, insts })
|
||||
}
|
||||
|
|
|
@ -3,7 +3,10 @@ use std::collections::BTreeMap;
|
|||
use anyhow::Result;
|
||||
use ppc750cl::{disasm_iter, Argument, SimplifiedIns};
|
||||
|
||||
use crate::obj::{ObjIns, ObjInsArg, ObjReloc, ObjRelocKind};
|
||||
use crate::{
|
||||
diff::ProcessCodeResult,
|
||||
obj::{ObjIns, ObjInsArg, ObjReloc, ObjRelocKind},
|
||||
};
|
||||
|
||||
// Relative relocation, can be Simm or BranchOffset
|
||||
fn is_relative_arg(arg: &ObjInsArg) -> bool {
|
||||
|
@ -22,7 +25,7 @@ pub fn process_code(
|
|||
address: u64,
|
||||
relocs: &[ObjReloc],
|
||||
line_info: &Option<BTreeMap<u32, u32>>,
|
||||
) -> Result<(Vec<u8>, Vec<ObjIns>)> {
|
||||
) -> Result<ProcessCodeResult> {
|
||||
let ins_count = data.len() / 4;
|
||||
let mut ops = Vec::<u8>::with_capacity(ins_count);
|
||||
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
|
||||
|
@ -92,5 +95,5 @@ pub fn process_code(
|
|||
orig: Some(format!("{}", SimplifiedIns::basic_form(ins))),
|
||||
});
|
||||
}
|
||||
Ok((ops, insts))
|
||||
Ok(ProcessCodeResult { ops, insts })
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#[cfg(windows)]
|
||||
#[cfg(feature = "wsl")]
|
||||
use std::string::FromUtf16Error;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
|
@ -6,12 +6,12 @@ use std::{
|
|||
path::{PathBuf, MAIN_SEPARATOR},
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
#[cfg(feature = "wsl")]
|
||||
use anyhow::{Context, Result};
|
||||
use const_format::formatcp;
|
||||
use egui::{
|
||||
output::OpenUrl, text::LayoutJob, CollapsingHeader, FontFamily, FontId, RichText,
|
||||
SelectableLabel, TextFormat, Widget,
|
||||
SelectableLabel, TextFormat, Widget, WidgetText,
|
||||
};
|
||||
use globset::Glob;
|
||||
use self_update::cargo_crate_version;
|
||||
|
@ -19,13 +19,17 @@ use self_update::cargo_crate_version;
|
|||
use crate::{
|
||||
app::{AppConfig, AppConfigRef, ObjectConfig},
|
||||
config::{ProjectObject, ProjectObjectNode},
|
||||
diff::DiffAlg,
|
||||
jobs::{
|
||||
check_update::{start_check_update, CheckUpdateResult},
|
||||
update::start_update,
|
||||
Job, JobQueue, JobResult,
|
||||
},
|
||||
update::RELEASE_URL,
|
||||
views::appearance::Appearance,
|
||||
views::{
|
||||
appearance::Appearance,
|
||||
file::{FileDialogResult, FileDialogState},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -41,12 +45,14 @@ pub struct ConfigViewState {
|
|||
pub load_error: Option<String>,
|
||||
pub object_search: String,
|
||||
pub filter_diffable: bool,
|
||||
#[cfg(windows)]
|
||||
pub filter_incomplete: bool,
|
||||
#[cfg(feature = "wsl")]
|
||||
pub available_wsl_distros: Option<Vec<String>>,
|
||||
pub file_dialog_state: FileDialogState,
|
||||
}
|
||||
|
||||
impl ConfigViewState {
|
||||
pub fn pre_update(&mut self, jobs: &mut JobQueue) {
|
||||
pub fn pre_update(&mut self, jobs: &mut JobQueue, config: &AppConfigRef) {
|
||||
jobs.results.retain_mut(|result| {
|
||||
if let JobResult::CheckUpdate(result) = result {
|
||||
self.check_update = take(result);
|
||||
|
@ -58,9 +64,52 @@ impl ConfigViewState {
|
|||
self.build_running = jobs.is_running(Job::ObjDiff);
|
||||
self.check_update_running = jobs.is_running(Job::CheckUpdate);
|
||||
self.update_running = jobs.is_running(Job::Update);
|
||||
|
||||
// Check async file dialog results
|
||||
match self.file_dialog_state.poll() {
|
||||
FileDialogResult::None => {}
|
||||
FileDialogResult::ProjectDir(path) => {
|
||||
let mut guard = config.write().unwrap();
|
||||
guard.set_project_dir(path.to_path_buf());
|
||||
}
|
||||
FileDialogResult::TargetDir(path) => {
|
||||
let mut guard = config.write().unwrap();
|
||||
guard.set_target_obj_dir(path.to_path_buf());
|
||||
}
|
||||
FileDialogResult::BaseDir(path) => {
|
||||
let mut guard = config.write().unwrap();
|
||||
guard.set_base_obj_dir(path.to_path_buf());
|
||||
}
|
||||
FileDialogResult::Object(path) => {
|
||||
let mut guard = config.write().unwrap();
|
||||
if let (Some(base_dir), Some(target_dir)) =
|
||||
(&guard.base_obj_dir, &guard.target_obj_dir)
|
||||
{
|
||||
if let Ok(obj_path) = path.strip_prefix(base_dir) {
|
||||
let target_path = target_dir.join(obj_path);
|
||||
guard.set_selected_obj(ObjectConfig {
|
||||
name: obj_path.display().to_string(),
|
||||
target_path: Some(target_path),
|
||||
base_path: Some(path),
|
||||
reverse_fn_order: None,
|
||||
complete: None,
|
||||
});
|
||||
} else if let Ok(obj_path) = path.strip_prefix(target_dir) {
|
||||
let base_path = base_dir.join(obj_path);
|
||||
guard.set_selected_obj(ObjectConfig {
|
||||
name: obj_path.display().to_string(),
|
||||
target_path: Some(path),
|
||||
base_path: Some(base_path),
|
||||
reverse_fn_order: None,
|
||||
complete: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn post_update(&mut self, jobs: &mut JobQueue, config: &AppConfigRef) {
|
||||
pub fn post_update(&mut self, ctx: &egui::Context, jobs: &mut JobQueue, config: &AppConfigRef) {
|
||||
if self.queue_build {
|
||||
self.queue_build = false;
|
||||
if let Ok(mut config) = config.write() {
|
||||
|
@ -70,12 +119,12 @@ impl ConfigViewState {
|
|||
|
||||
if self.queue_check_update {
|
||||
self.queue_check_update = false;
|
||||
jobs.push_once(Job::CheckUpdate, start_check_update);
|
||||
jobs.push_once(Job::CheckUpdate, || start_check_update(ctx));
|
||||
}
|
||||
|
||||
if self.queue_update {
|
||||
self.queue_update = false;
|
||||
jobs.push_once(Job::Update, start_update);
|
||||
jobs.push_once(Job::Update, || start_update(ctx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +134,7 @@ pub const DEFAULT_WATCH_PATTERNS: &[&str] = &[
|
|||
"*.inc", "*.py", "*.yml", "*.txt", "*.json",
|
||||
];
|
||||
|
||||
#[cfg(windows)]
|
||||
#[cfg(feature = "wsl")]
|
||||
fn process_utf16(bytes: &[u8]) -> Result<String, FromUtf16Error> {
|
||||
let u16_bytes: Vec<u16> = bytes
|
||||
.chunks_exact(2)
|
||||
|
@ -94,7 +143,7 @@ fn process_utf16(bytes: &[u8]) -> Result<String, FromUtf16Error> {
|
|||
String::from_utf16(&u16_bytes)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[cfg(feature = "wsl")]
|
||||
fn wsl_cmd(args: &[&str]) -> Result<String> {
|
||||
use std::{os::windows::process::CommandExt, process::Command};
|
||||
let output = Command::new("wsl")
|
||||
|
@ -105,7 +154,7 @@ fn wsl_cmd(args: &[&str]) -> Result<String> {
|
|||
process_utf16(&output.stdout).context("Failed to process stdout")
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[cfg(feature = "wsl")]
|
||||
fn fetch_wsl2_distros() -> Vec<String> {
|
||||
wsl_cmd(&["-l", "-q"])
|
||||
.map(|stdout| {
|
||||
|
@ -178,7 +227,7 @@ pub fn config_ui(
|
|||
}
|
||||
ui.separator();
|
||||
|
||||
#[cfg(windows)]
|
||||
#[cfg(feature = "wsl")]
|
||||
{
|
||||
ui.heading("Build");
|
||||
if state.available_wsl_distros.is_none() {
|
||||
|
@ -194,7 +243,7 @@ pub fn config_ui(
|
|||
});
|
||||
ui.separator();
|
||||
}
|
||||
#[cfg(not(windows))]
|
||||
#[cfg(not(feature = "wsl"))]
|
||||
{
|
||||
let _ = selected_wsl_distro;
|
||||
}
|
||||
|
@ -208,33 +257,19 @@ pub fn config_ui(
|
|||
|
||||
let mut new_selected_obj = selected_obj.clone();
|
||||
if objects.is_empty() {
|
||||
if let (Some(base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) {
|
||||
if let (Some(_base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) {
|
||||
if ui.button("Select object").clicked() {
|
||||
if let Some(path) = rfd::FileDialog::new()
|
||||
.set_directory(&target_dir)
|
||||
.add_filter("Object file", &["o", "elf"])
|
||||
.pick_file()
|
||||
{
|
||||
if let Ok(obj_path) = path.strip_prefix(&base_dir) {
|
||||
let target_path = target_dir.join(obj_path);
|
||||
new_selected_obj = Some(ObjectConfig {
|
||||
name: obj_path.display().to_string(),
|
||||
target_path: Some(target_path),
|
||||
base_path: Some(path),
|
||||
reverse_fn_order: None,
|
||||
complete: None,
|
||||
});
|
||||
} else if let Ok(obj_path) = path.strip_prefix(&target_dir) {
|
||||
let base_path = base_dir.join(obj_path);
|
||||
new_selected_obj = Some(ObjectConfig {
|
||||
name: obj_path.display().to_string(),
|
||||
target_path: Some(path),
|
||||
base_path: Some(base_path),
|
||||
reverse_fn_order: None,
|
||||
complete: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
state.file_dialog_state.queue(
|
||||
|| {
|
||||
Box::pin(
|
||||
rfd::AsyncFileDialog::new()
|
||||
.set_directory(&target_dir)
|
||||
.add_filter("Object file", &["o", "elf"])
|
||||
.pick_file(),
|
||||
)
|
||||
},
|
||||
FileDialogResult::Object,
|
||||
);
|
||||
}
|
||||
if let Some(obj) = selected_obj {
|
||||
ui.label(
|
||||
|
@ -276,6 +311,13 @@ pub fn config_ui(
|
|||
{
|
||||
state.filter_diffable = !state.filter_diffable;
|
||||
}
|
||||
if ui
|
||||
.selectable_label(state.filter_incomplete, "Incomplete")
|
||||
.on_hover_text_at_pointer("Only show objects not marked complete")
|
||||
.clicked()
|
||||
{
|
||||
state.filter_incomplete = !state.filter_incomplete;
|
||||
}
|
||||
});
|
||||
if state.object_search.is_empty() {
|
||||
if had_search {
|
||||
|
@ -295,12 +337,19 @@ pub fn config_ui(
|
|||
.default_open(true)
|
||||
.show(ui, |ui| {
|
||||
let mut nodes = Cow::Borrowed(object_nodes);
|
||||
if !state.object_search.is_empty() || state.filter_diffable {
|
||||
if !state.object_search.is_empty() || state.filter_diffable || state.filter_incomplete {
|
||||
let search = state.object_search.to_ascii_lowercase();
|
||||
nodes = Cow::Owned(
|
||||
object_nodes
|
||||
.iter()
|
||||
.filter_map(|node| filter_node(node, &search, state.filter_diffable))
|
||||
.filter_map(|node| {
|
||||
filter_node(
|
||||
node,
|
||||
&search,
|
||||
state.filter_diffable,
|
||||
state.filter_incomplete,
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
|
@ -434,12 +483,14 @@ fn filter_node(
|
|||
node: &ProjectObjectNode,
|
||||
search: &str,
|
||||
filter_diffable: bool,
|
||||
filter_incomplete: bool,
|
||||
) -> Option<ProjectObjectNode> {
|
||||
match node {
|
||||
ProjectObjectNode::File(name, object) => {
|
||||
if (search.is_empty() || name.to_ascii_lowercase().contains(search))
|
||||
&& (!filter_diffable
|
||||
|| (object.base_path.is_some() && object.target_path.is_some()))
|
||||
&& (!filter_incomplete || matches!(object.complete, None | Some(false)))
|
||||
{
|
||||
Some(node.clone())
|
||||
} else {
|
||||
|
@ -447,13 +498,15 @@ fn filter_node(
|
|||
}
|
||||
}
|
||||
ProjectObjectNode::Dir(name, children) => {
|
||||
if (search.is_empty() || name.to_ascii_lowercase().contains(search)) && !filter_diffable
|
||||
if (search.is_empty() || name.to_ascii_lowercase().contains(search))
|
||||
&& !filter_diffable
|
||||
&& !filter_incomplete
|
||||
{
|
||||
return Some(node.clone());
|
||||
}
|
||||
let new_children = children
|
||||
.iter()
|
||||
.filter_map(|child| filter_node(child, search, filter_diffable))
|
||||
.filter_map(|child| filter_node(child, search, filter_diffable, filter_incomplete))
|
||||
.collect::<Vec<_>>();
|
||||
if !new_children.is_empty() {
|
||||
Some(ProjectObjectNode::Dir(name.clone(), new_children))
|
||||
|
@ -559,9 +612,10 @@ fn split_obj_config_ui(
|
|||
true,
|
||||
);
|
||||
if response.clicked() {
|
||||
if let Some(path) = rfd::FileDialog::new().pick_folder() {
|
||||
config.set_project_dir(path);
|
||||
}
|
||||
state.file_dialog_state.queue(
|
||||
|| Box::pin(rfd::AsyncFileDialog::new().pick_folder()),
|
||||
FileDialogResult::ProjectDir,
|
||||
);
|
||||
}
|
||||
ui.separator();
|
||||
|
||||
|
@ -620,9 +674,10 @@ fn split_obj_config_ui(
|
|||
config.project_config_info.is_none(),
|
||||
);
|
||||
if response.clicked() {
|
||||
if let Some(path) = rfd::FileDialog::new().set_directory(&project_dir).pick_folder() {
|
||||
config.set_target_obj_dir(path);
|
||||
}
|
||||
state.file_dialog_state.queue(
|
||||
|| Box::pin(rfd::AsyncFileDialog::new().set_directory(&project_dir).pick_folder()),
|
||||
FileDialogResult::TargetDir,
|
||||
);
|
||||
}
|
||||
ui.checkbox(&mut config.build_target, "Build target objects").on_hover_ui(|ui| {
|
||||
let mut job = LayoutJob::default();
|
||||
|
@ -670,9 +725,10 @@ fn split_obj_config_ui(
|
|||
config.project_config_info.is_none(),
|
||||
);
|
||||
if response.clicked() {
|
||||
if let Some(path) = rfd::FileDialog::new().set_directory(&project_dir).pick_folder() {
|
||||
config.set_base_obj_dir(path);
|
||||
}
|
||||
state.file_dialog_state.queue(
|
||||
|| Box::pin(rfd::AsyncFileDialog::new().set_directory(&project_dir).pick_folder()),
|
||||
FileDialogResult::BaseDir,
|
||||
);
|
||||
}
|
||||
ui.checkbox(&mut config.build_base, "Build base objects").on_hover_ui(|ui| {
|
||||
let mut job = LayoutJob::default();
|
||||
|
@ -751,3 +807,70 @@ fn split_obj_config_ui(
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn diff_options_window(
|
||||
ctx: &egui::Context,
|
||||
config: &AppConfigRef,
|
||||
show: &mut bool,
|
||||
appearance: &Appearance,
|
||||
) {
|
||||
let mut config_guard = config.write().unwrap();
|
||||
egui::Window::new("Diff Options").open(show).show(ctx, |ui| {
|
||||
diff_options_ui(ui, &mut config_guard, appearance);
|
||||
});
|
||||
}
|
||||
|
||||
fn diff_options_ui(ui: &mut egui::Ui, config: &mut AppConfig, appearance: &Appearance) {
|
||||
let mut job = LayoutJob::default();
|
||||
job.append(
|
||||
"Current default: ",
|
||||
0.0,
|
||||
TextFormat::simple(appearance.ui_font.clone(), appearance.text_color),
|
||||
);
|
||||
job.append(
|
||||
diff_alg_to_string(DiffAlg::default()),
|
||||
0.0,
|
||||
TextFormat::simple(appearance.ui_font.clone(), appearance.emphasized_text_color),
|
||||
);
|
||||
ui.label(job);
|
||||
let mut job = LayoutJob::default();
|
||||
job.append(
|
||||
"Previous default: ",
|
||||
0.0,
|
||||
TextFormat::simple(appearance.ui_font.clone(), appearance.text_color),
|
||||
);
|
||||
job.append(
|
||||
"Levenshtein",
|
||||
0.0,
|
||||
TextFormat::simple(appearance.ui_font.clone(), appearance.emphasized_text_color),
|
||||
);
|
||||
ui.label(job);
|
||||
ui.label("Please provide feedback!");
|
||||
if diff_alg_ui(ui, "Code diff algorithm", &mut config.code_alg) {
|
||||
config.queue_reload = true;
|
||||
}
|
||||
if diff_alg_ui(ui, "Data diff algorithm", &mut config.data_alg) {
|
||||
config.queue_reload = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn diff_alg_ui(ui: &mut egui::Ui, label: impl Into<WidgetText>, alg: &mut DiffAlg) -> bool {
|
||||
let response = egui::ComboBox::from_label(label)
|
||||
.selected_text(diff_alg_to_string(*alg))
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(alg, DiffAlg::Patience, "Patience").changed()
|
||||
| ui.selectable_value(alg, DiffAlg::Levenshtein, "Levenshtein").changed()
|
||||
| ui.selectable_value(alg, DiffAlg::Myers, "Myers").changed()
|
||||
| ui.selectable_value(alg, DiffAlg::Lcs, "LCS").changed()
|
||||
});
|
||||
response.inner.unwrap_or(false)
|
||||
}
|
||||
|
||||
const fn diff_alg_to_string(alg: DiffAlg) -> &'static str {
|
||||
match alg {
|
||||
DiffAlg::Patience => "Patience",
|
||||
DiffAlg::Levenshtein => "Levenshtein",
|
||||
DiffAlg::Lcs => "LCS",
|
||||
DiffAlg::Myers => "Myers",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
use crate::views::{appearance::Appearance, frame_history::FrameHistory};
|
||||
|
||||
pub fn debug_window(
|
||||
ctx: &egui::Context,
|
||||
show: &mut bool,
|
||||
frame_history: &mut FrameHistory,
|
||||
appearance: &Appearance,
|
||||
) {
|
||||
egui::Window::new("Debug").open(show).show(ctx, |ui| {
|
||||
debug_ui(ui, frame_history, appearance);
|
||||
});
|
||||
}
|
||||
|
||||
fn debug_ui(ui: &mut egui::Ui, frame_history: &mut FrameHistory, _appearance: &Appearance) {
|
||||
ui.label(format!("Repainting the UI each frame. FPS: {:.1}", frame_history.fps()));
|
||||
frame_history.ui(ui);
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
use std::{future::Future, path::PathBuf, pin::Pin, thread::JoinHandle};
|
||||
|
||||
use pollster::FutureExt;
|
||||
use rfd::FileHandle;
|
||||
|
||||
#[derive(Default)]
|
||||
pub enum FileDialogResult {
|
||||
#[default]
|
||||
None,
|
||||
ProjectDir(PathBuf),
|
||||
TargetDir(PathBuf),
|
||||
BaseDir(PathBuf),
|
||||
Object(PathBuf),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FileDialogState {
|
||||
thread: Option<JoinHandle<FileDialogResult>>,
|
||||
}
|
||||
|
||||
impl FileDialogState {
|
||||
pub fn queue<InitCb, ResultCb>(&mut self, init: InitCb, result_cb: ResultCb)
|
||||
where
|
||||
InitCb: FnOnce() -> Pin<Box<dyn Future<Output = Option<FileHandle>> + Send>>,
|
||||
ResultCb: FnOnce(PathBuf) -> FileDialogResult + Send + 'static,
|
||||
{
|
||||
if self.thread.is_some() {
|
||||
return;
|
||||
}
|
||||
let future = init();
|
||||
self.thread = Some(std::thread::spawn(move || {
|
||||
if let Some(handle) = future.block_on() {
|
||||
result_cb(PathBuf::from(handle))
|
||||
} else {
|
||||
FileDialogResult::None
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
pub fn poll(&mut self) -> FileDialogResult {
|
||||
if let Some(thread) = &mut self.thread {
|
||||
if thread.is_finished() {
|
||||
self.thread.take().unwrap().join().unwrap_or(FileDialogResult::None)
|
||||
} else {
|
||||
FileDialogResult::None
|
||||
}
|
||||
} else {
|
||||
FileDialogResult::None
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
// From https://github.com/emilk/egui/blob/e037489ac20a9e419715ae75d205a8baa117c3cf/crates/egui_demo_app/src/frame_history.rs
|
||||
// Copyright (c) 2018-2021 Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any
|
||||
// person obtaining a copy of this software and associated
|
||||
// documentation files (the "Software"), to deal in the
|
||||
// Software without restriction, including without
|
||||
// limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of
|
||||
// the Software, and to permit persons to whom the Software
|
||||
// is furnished to do so, subject to the following
|
||||
// conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice
|
||||
// shall be included in all copies or substantial portions
|
||||
// of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
use egui::util::History;
|
||||
|
||||
pub struct FrameHistory {
|
||||
frame_times: History<f32>,
|
||||
}
|
||||
|
||||
impl Default for FrameHistory {
|
||||
fn default() -> Self {
|
||||
let max_age: f32 = 1.0;
|
||||
let max_len = (max_age * 300.0).round() as usize;
|
||||
Self { frame_times: History::new(0..max_len, max_age) }
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameHistory {
|
||||
// Called first
|
||||
pub fn on_new_frame(&mut self, now: f64, previous_frame_time: Option<f32>) {
|
||||
let previous_frame_time = previous_frame_time.unwrap_or_default();
|
||||
if let Some(latest) = self.frame_times.latest_mut() {
|
||||
*latest = previous_frame_time; // rewrite history now that we know
|
||||
}
|
||||
self.frame_times.add(now, previous_frame_time); // projected
|
||||
}
|
||||
|
||||
pub fn mean_frame_time(&self) -> f32 { self.frame_times.average().unwrap_or_default() }
|
||||
|
||||
pub fn fps(&self) -> f32 { 1.0 / self.frame_times.mean_time_interval().unwrap_or_default() }
|
||||
|
||||
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
||||
ui.label(format!("Mean CPU usage: {:.2} ms / frame", 1e3 * self.mean_frame_time()))
|
||||
.on_hover_text(
|
||||
"Includes egui layout and tessellation time.\n\
|
||||
Does not include GPU usage, nor overhead for sending data to GPU.",
|
||||
);
|
||||
egui::warn_if_debug_build(ui);
|
||||
|
||||
if !cfg!(target_arch = "wasm32") {
|
||||
egui::CollapsingHeader::new("📊 CPU usage history").default_open(false).show(
|
||||
ui,
|
||||
|ui| {
|
||||
self.graph(ui);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn graph(&mut self, ui: &mut egui::Ui) -> egui::Response {
|
||||
use egui::*;
|
||||
|
||||
ui.label("egui CPU usage history");
|
||||
|
||||
let history = &self.frame_times;
|
||||
|
||||
// TODO(emilk): we should not use `slider_width` as default graph width.
|
||||
let height = ui.spacing().slider_width;
|
||||
let size = vec2(ui.available_size_before_wrap().x, height);
|
||||
let (rect, response) = ui.allocate_at_least(size, Sense::hover());
|
||||
let style = ui.style().noninteractive();
|
||||
|
||||
let graph_top_cpu_usage = 0.010;
|
||||
let graph_rect = Rect::from_x_y_ranges(history.max_age()..=0.0, graph_top_cpu_usage..=0.0);
|
||||
let to_screen = emath::RectTransform::from_to(graph_rect, rect);
|
||||
|
||||
let mut shapes = Vec::with_capacity(3 + 2 * history.len());
|
||||
shapes.push(Shape::Rect(epaint::RectShape::new(
|
||||
rect,
|
||||
style.rounding,
|
||||
ui.visuals().extreme_bg_color,
|
||||
ui.style().noninteractive().bg_stroke,
|
||||
)));
|
||||
|
||||
let rect = rect.shrink(4.0);
|
||||
let color = ui.visuals().text_color();
|
||||
let line_stroke = Stroke::new(1.0, color);
|
||||
|
||||
if let Some(pointer_pos) = response.hover_pos() {
|
||||
let y = pointer_pos.y;
|
||||
shapes.push(Shape::line_segment(
|
||||
[pos2(rect.left(), y), pos2(rect.right(), y)],
|
||||
line_stroke,
|
||||
));
|
||||
let cpu_usage = to_screen.inverse().transform_pos(pointer_pos).y;
|
||||
let text = format!("{:.1} ms", 1e3 * cpu_usage);
|
||||
shapes.push(ui.fonts(|f| {
|
||||
Shape::text(
|
||||
f,
|
||||
pos2(rect.left(), y),
|
||||
egui::Align2::LEFT_BOTTOM,
|
||||
text,
|
||||
TextStyle::Monospace.resolve(ui.style()),
|
||||
color,
|
||||
)
|
||||
}));
|
||||
}
|
||||
|
||||
let circle_color = color;
|
||||
let radius = 2.0;
|
||||
let right_side_time = ui.input(|i| i.time); // Time at right side of screen
|
||||
|
||||
for (time, cpu_usage) in history.iter() {
|
||||
let age = (right_side_time - time) as f32;
|
||||
let pos = to_screen.transform_pos_clamped(Pos2::new(age, cpu_usage));
|
||||
|
||||
shapes.push(Shape::line_segment([pos2(pos.x, rect.bottom()), pos], line_stroke));
|
||||
|
||||
if cpu_usage < graph_top_cpu_usage {
|
||||
shapes.push(Shape::circle_filled(pos, radius, circle_color));
|
||||
}
|
||||
}
|
||||
|
||||
ui.painter().extend(shapes);
|
||||
|
||||
response
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ pub fn jobs_ui(ui: &mut egui::Ui, jobs: &mut JobQueue, appearance: &Appearance)
|
|||
|
||||
let mut remove_job: Option<usize> = None;
|
||||
for job in jobs.iter_mut() {
|
||||
let Ok(status) = job.status.read() else {
|
||||
let Ok(status) = job.context.status.read() else {
|
||||
continue;
|
||||
};
|
||||
ui.group(|ui| {
|
||||
|
|
|
@ -3,7 +3,10 @@ use egui::{text::LayoutJob, Color32, FontId, TextFormat};
|
|||
pub(crate) mod appearance;
|
||||
pub(crate) mod config;
|
||||
pub(crate) mod data_diff;
|
||||
pub(crate) mod debug;
|
||||
pub(crate) mod demangle;
|
||||
pub(crate) mod file;
|
||||
pub(crate) mod frame_history;
|
||||
pub(crate) mod function_diff;
|
||||
pub(crate) mod jobs;
|
||||
pub(crate) mod symbol_diff;
|
||||
|
|
|
@ -73,7 +73,7 @@ impl DiffViewState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn post_update(&mut self, _jobs: &mut JobQueue, config: &AppConfigRef) {
|
||||
pub fn post_update(&mut self, config: &AppConfigRef) {
|
||||
if self.queue_build {
|
||||
self.queue_build = false;
|
||||
if let Ok(mut config) = config.write() {
|
||||
|
|
Loading…
Reference in New Issue