mirror of
https://github.com/encounter/objdiff.git
synced 2025-12-15 16:16:15 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e202c3ef95 | |||
|
|
b7730b3d00 | ||
|
|
a4fdb61f04 | ||
|
|
2876be37a3 | ||
| 11171763eb | |||
| 6037a79ba2 | |||
| f7efe5fdff | |||
| 0692deac59 | |||
| c3e3d175c5 | |||
| c45f4bbc99 | |||
| b0c5431ac5 | |||
|
|
9ab246367b | ||
|
|
dcafe51eda |
2
.github/workflows/build.yaml
vendored
2
.github/workflows/build.yaml
vendored
@@ -65,7 +65,7 @@ jobs:
|
||||
continue-on-error: ${{ matrix.checks == 'advisories' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||
with:
|
||||
command: check ${{ matrix.checks }}
|
||||
|
||||
|
||||
938
Cargo.lock
generated
938
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -13,9 +13,9 @@ strip = "debuginfo"
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.package]
|
||||
version = "2.5.0"
|
||||
version = "2.7.1"
|
||||
authors = ["Luke Street <luke@street.dev>"]
|
||||
edition = "2021"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/encounter/objdiff"
|
||||
rust-version = "1.74"
|
||||
rust-version = "1.81"
|
||||
|
||||
@@ -73,7 +73,6 @@ ignore = [
|
||||
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
||||
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
||||
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
||||
{ id = "RUSTSEC-2024-0384", reason = "Unmaintained indirect dependency" },
|
||||
]
|
||||
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
||||
# If this is false, then it uses a built-in git library.
|
||||
@@ -240,7 +239,7 @@ allow-git = []
|
||||
|
||||
[sources.allow-org]
|
||||
# github.com organizations to allow git sources for
|
||||
github = ["notify-rs"]
|
||||
github = []
|
||||
# gitlab.com organizations to allow git sources for
|
||||
gitlab = []
|
||||
# bitbucket.org organizations to allow git sources for
|
||||
|
||||
@@ -12,7 +12,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use argp::FromArgs;
|
||||
use crossterm::{
|
||||
event,
|
||||
@@ -27,9 +27,11 @@ use objdiff_core::{
|
||||
watcher::{create_watcher, Watcher},
|
||||
BuildConfig,
|
||||
},
|
||||
config::{build_globset, default_watch_patterns, ProjectConfig, ProjectObject},
|
||||
config::{build_globset, ProjectConfig, ProjectObject},
|
||||
diff,
|
||||
diff::ObjDiff,
|
||||
diff::{
|
||||
ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig, MappingConfig, ObjDiff,
|
||||
},
|
||||
jobs::{
|
||||
objdiff::{start_build, ObjDiffConfig},
|
||||
Job, JobQueue, JobResult,
|
||||
@@ -63,9 +65,6 @@ pub struct Args {
|
||||
#[argp(option, short = 'u')]
|
||||
/// Unit name within project
|
||||
unit: Option<String>,
|
||||
#[argp(switch, short = 'x')]
|
||||
/// Relax relocation diffs
|
||||
relax_reloc_diffs: bool,
|
||||
#[argp(option, short = 'o')]
|
||||
/// Output file (one-shot mode) ("-" for stdout)
|
||||
output: Option<PathBuf>,
|
||||
@@ -75,6 +74,18 @@ pub struct Args {
|
||||
#[argp(positional)]
|
||||
/// Function symbol to diff
|
||||
symbol: Option<String>,
|
||||
#[argp(option, short = 'c')]
|
||||
/// Configuration property (key=value)
|
||||
config: Vec<String>,
|
||||
#[argp(option, short = 'm')]
|
||||
/// Symbol mapping (target=base)
|
||||
mapping: Vec<String>,
|
||||
#[argp(option)]
|
||||
/// Left symbol name for selection
|
||||
selecting_left: Option<String>,
|
||||
#[argp(option)]
|
||||
/// Right symbol name for selection
|
||||
selecting_right: Option<String>,
|
||||
}
|
||||
|
||||
pub fn run(args: Args) -> Result<()> {
|
||||
@@ -84,7 +95,9 @@ pub fn run(args: Args) -> Result<()> {
|
||||
&args.project,
|
||||
&args.unit,
|
||||
) {
|
||||
(Some(t), Some(b), None, None) => (Some(t.clone()), Some(b.clone()), None),
|
||||
(Some(_), Some(_), None, None)
|
||||
| (Some(_), None, None, None)
|
||||
| (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None),
|
||||
(None, None, p, u) => {
|
||||
let project = match p {
|
||||
Some(project) => project.clone(),
|
||||
@@ -193,6 +206,43 @@ pub fn run(args: Args) -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)> {
|
||||
let mut diff_config = DiffObjConfig::default();
|
||||
for config in &args.config {
|
||||
let (key, value) = config.split_once('=').context("--config expects \"key=value\"")?;
|
||||
let property_id = ConfigPropertyId::from_str(key)
|
||||
.map_err(|()| anyhow!("Invalid configuration property: {}", key))?;
|
||||
diff_config.set_property_value_str(property_id, value).map_err(|()| {
|
||||
let mut options = String::new();
|
||||
match property_id.kind() {
|
||||
ConfigPropertyKind::Boolean => {
|
||||
options = "true, false".to_string();
|
||||
}
|
||||
ConfigPropertyKind::Choice(variants) => {
|
||||
for (i, variant) in variants.iter().enumerate() {
|
||||
if i > 0 {
|
||||
options.push_str(", ");
|
||||
}
|
||||
options.push_str(variant.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
anyhow!("Invalid value for {}. Expected one of: {}", property_id.name(), options)
|
||||
})?;
|
||||
}
|
||||
let mut mapping_config = MappingConfig {
|
||||
mappings: Default::default(),
|
||||
selecting_left: args.selecting_left.clone(),
|
||||
selecting_right: args.selecting_right.clone(),
|
||||
};
|
||||
for mapping in &args.mapping {
|
||||
let (target, base) =
|
||||
mapping.split_once('=').context("--mapping expects \"target=base\"")?;
|
||||
mapping_config.mappings.insert(target.to_string(), base.to_string());
|
||||
}
|
||||
Ok((diff_config, mapping_config))
|
||||
}
|
||||
|
||||
fn run_oneshot(
|
||||
args: &Args,
|
||||
output: &Path,
|
||||
@@ -200,17 +250,19 @@ fn run_oneshot(
|
||||
base_path: Option<&Path>,
|
||||
) -> Result<()> {
|
||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||
let config = diff::DiffObjConfig {
|
||||
relax_reloc_diffs: args.relax_reloc_diffs,
|
||||
..Default::default() // TODO
|
||||
};
|
||||
let (diff_config, mapping_config) = build_config_from_args(args)?;
|
||||
let target = target_path
|
||||
.map(|p| obj::read::read(p, &config).with_context(|| format!("Loading {}", p.display())))
|
||||
.map(|p| {
|
||||
obj::read::read(p, &diff_config).with_context(|| format!("Loading {}", p.display()))
|
||||
})
|
||||
.transpose()?;
|
||||
let base = base_path
|
||||
.map(|p| obj::read::read(p, &config).with_context(|| format!("Loading {}", p.display())))
|
||||
.map(|p| {
|
||||
obj::read::read(p, &diff_config).with_context(|| format!("Loading {}", p.display()))
|
||||
})
|
||||
.transpose()?;
|
||||
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), None)?;
|
||||
let result =
|
||||
diff::diff_objs(&diff_config, &mapping_config, target.as_ref(), base.as_ref(), None)?;
|
||||
let left = target.as_ref().and_then(|o| result.left.as_ref().map(|d| (o, d)));
|
||||
let right = base.as_ref().and_then(|o| result.right.as_ref().map(|d| (o, d)));
|
||||
write_output(&DiffResult::new(left, right), Some(output), output_format)?;
|
||||
@@ -229,9 +281,10 @@ pub struct AppState {
|
||||
pub prev_obj: Option<(ObjInfo, ObjDiff)>,
|
||||
pub reload_time: Option<time::OffsetDateTime>,
|
||||
pub time_format: Vec<time::format_description::FormatItem<'static>>,
|
||||
pub relax_reloc_diffs: bool,
|
||||
pub watcher: Option<Watcher>,
|
||||
pub modified: Arc<AtomicBool>,
|
||||
pub diff_obj_config: DiffObjConfig,
|
||||
pub mapping_config: MappingConfig,
|
||||
}
|
||||
|
||||
fn create_objdiff_config(state: &AppState) -> ObjDiffConfig {
|
||||
@@ -257,13 +310,8 @@ fn create_objdiff_config(state: &AppState) -> ObjDiffConfig {
|
||||
.is_some_and(|p| p.build_target.unwrap_or(false)),
|
||||
target_path: state.target_path.clone(),
|
||||
base_path: state.base_path.clone(),
|
||||
diff_obj_config: diff::DiffObjConfig {
|
||||
relax_reloc_diffs: state.relax_reloc_diffs,
|
||||
..Default::default() // TODO
|
||||
},
|
||||
symbol_mappings: Default::default(),
|
||||
selecting_left: None,
|
||||
selecting_right: None,
|
||||
diff_obj_config: state.diff_obj_config.clone(),
|
||||
mapping_config: state.mapping_config.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,6 +362,7 @@ fn run_interactive(
|
||||
let Some(symbol_name) = &args.symbol else { bail!("Interactive mode requires a symbol name") };
|
||||
let time_format = time::format_description::parse_borrowed::<2>("[hour]:[minute]:[second]")
|
||||
.context("Failed to parse time format")?;
|
||||
let (diff_obj_config, mapping_config) = build_config_from_args(&args)?;
|
||||
let mut state = AppState {
|
||||
jobs: Default::default(),
|
||||
waker: Default::default(),
|
||||
@@ -326,17 +375,13 @@ fn run_interactive(
|
||||
prev_obj: None,
|
||||
reload_time: None,
|
||||
time_format,
|
||||
relax_reloc_diffs: args.relax_reloc_diffs,
|
||||
watcher: None,
|
||||
modified: Default::default(),
|
||||
diff_obj_config,
|
||||
mapping_config,
|
||||
};
|
||||
if let Some(project_dir) = &state.project_dir {
|
||||
let watch_patterns = state
|
||||
.project_config
|
||||
.as_ref()
|
||||
.and_then(|c| c.watch_patterns.as_ref())
|
||||
.cloned()
|
||||
.unwrap_or_else(default_watch_patterns);
|
||||
if let (Some(project_dir), Some(project_config)) = (&state.project_dir, &state.project_config) {
|
||||
let watch_patterns = project_config.build_watch_patterns()?;
|
||||
state.watcher = Some(create_watcher(
|
||||
state.modified.clone(),
|
||||
project_dir,
|
||||
|
||||
@@ -169,22 +169,29 @@ fn report_object(
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let config = diff::DiffObjConfig { relax_reloc_diffs: true, ..Default::default() };
|
||||
let diff_config = diff::DiffObjConfig {
|
||||
function_reloc_diffs: diff::FunctionRelocDiffs::None,
|
||||
..Default::default()
|
||||
};
|
||||
let mapping_config = diff::MappingConfig::default();
|
||||
let target = object
|
||||
.target_path
|
||||
.as_ref()
|
||||
.map(|p| {
|
||||
obj::read::read(p, &config).with_context(|| format!("Failed to open {}", p.display()))
|
||||
obj::read::read(p, &diff_config)
|
||||
.with_context(|| format!("Failed to open {}", p.display()))
|
||||
})
|
||||
.transpose()?;
|
||||
let base = object
|
||||
.base_path
|
||||
.as_ref()
|
||||
.map(|p| {
|
||||
obj::read::read(p, &config).with_context(|| format!("Failed to open {}", p.display()))
|
||||
obj::read::read(p, &diff_config)
|
||||
.with_context(|| format!("Failed to open {}", p.display()))
|
||||
})
|
||||
.transpose()?;
|
||||
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), None)?;
|
||||
let result =
|
||||
diff::diff_objs(&diff_config, &mapping_config, target.as_ref(), base.as_ref(), None)?;
|
||||
|
||||
let metadata = ReportUnitMetadata {
|
||||
complete: object.complete(),
|
||||
|
||||
@@ -3,7 +3,7 @@ use crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers, MouseButton,
|
||||
use objdiff_core::{
|
||||
diff::{
|
||||
display::{display_diff, DiffText, HighlightKind},
|
||||
ObjDiff, ObjInsDiffKind, ObjSymbolDiff,
|
||||
FunctionRelocDiffs, ObjDiff, ObjInsDiffKind, ObjSymbolDiff,
|
||||
},
|
||||
obj::{ObjInfo, ObjSectionKind, ObjSymbol, SymbolRef},
|
||||
};
|
||||
@@ -368,9 +368,15 @@ impl UiView for FunctionDiffUi {
|
||||
self.scroll_x = self.scroll_x.saturating_sub(1);
|
||||
result.redraw = true;
|
||||
}
|
||||
// Toggle relax relocation diffs
|
||||
// Cycle through function relocation diff mode
|
||||
KeyCode::Char('x') => {
|
||||
state.relax_reloc_diffs = !state.relax_reloc_diffs;
|
||||
state.diff_obj_config.function_reloc_diffs =
|
||||
match state.diff_obj_config.function_reloc_diffs {
|
||||
FunctionRelocDiffs::None => FunctionRelocDiffs::NameAddress,
|
||||
FunctionRelocDiffs::NameAddress => FunctionRelocDiffs::DataValue,
|
||||
FunctionRelocDiffs::DataValue => FunctionRelocDiffs::All,
|
||||
FunctionRelocDiffs::All => FunctionRelocDiffs::None,
|
||||
};
|
||||
result.redraw = true;
|
||||
return EventControlFlow::Reload;
|
||||
}
|
||||
|
||||
@@ -16,18 +16,104 @@ documentation = "https://docs.rs/objdiff-core"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "arm64", "bindings", "build"]
|
||||
any-arch = ["config", "dep:bimap", "dep:strum", "dep:similar", "dep:flagset", "dep:log", "dep:memmap2", "dep:byteorder", "dep:num-traits"] # Implicit, used to check if any arch is enabled
|
||||
bindings = ["dep:serde_json", "dep:prost", "dep:pbjson", "dep:serde", "dep:prost-build", "dep:pbjson-build"]
|
||||
build = ["dep:shell-escape", "dep:path-slash", "dep:winapi", "dep:notify", "dep:notify-debouncer-full", "dep:reqwest", "dep:self_update", "dep:tempfile", "dep:time"]
|
||||
config = ["dep:bimap", "dep:globset", "dep:semver", "dep:serde_json", "dep:serde_yaml", "dep:serde", "dep:filetime"]
|
||||
all = [
|
||||
# Features
|
||||
"bindings",
|
||||
"build",
|
||||
"config",
|
||||
"dwarf",
|
||||
# Architectures
|
||||
"mips",
|
||||
"ppc",
|
||||
"x86",
|
||||
"arm",
|
||||
"arm64",
|
||||
]
|
||||
# Implicit, used to check if any arch is enabled
|
||||
any-arch = [
|
||||
"config",
|
||||
"dep:bimap",
|
||||
"dep:byteorder",
|
||||
"dep:flagset",
|
||||
"dep:heck",
|
||||
"dep:log",
|
||||
"dep:memmap2",
|
||||
"dep:num-traits",
|
||||
"dep:prettyplease",
|
||||
"dep:proc-macro2",
|
||||
"dep:quote",
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
"dep:similar",
|
||||
"dep:strum",
|
||||
"dep:syn",
|
||||
]
|
||||
bindings = [
|
||||
"dep:pbjson",
|
||||
"dep:pbjson-build",
|
||||
"dep:prost",
|
||||
"dep:prost-build",
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
]
|
||||
build = [
|
||||
"dep:notify",
|
||||
"dep:notify-debouncer-full",
|
||||
"dep:path-slash",
|
||||
"dep:reqwest",
|
||||
"dep:self_update",
|
||||
"dep:shell-escape",
|
||||
"dep:tempfile",
|
||||
"dep:time",
|
||||
"dep:winapi",
|
||||
]
|
||||
config = [
|
||||
"dep:bimap",
|
||||
"dep:filetime",
|
||||
"dep:globset",
|
||||
"dep:semver",
|
||||
"dep:serde",
|
||||
"dep:serde_json",
|
||||
"dep:serde_yaml",
|
||||
]
|
||||
dwarf = ["dep:gimli"]
|
||||
mips = ["any-arch", "dep:rabbitizer"]
|
||||
ppc = ["any-arch", "dep:cwdemangle", "dep:cwextab", "dep:ppc750cl"]
|
||||
x86 = ["any-arch", "dep:cpp_demangle", "dep:iced-x86", "dep:msvc-demangler"]
|
||||
arm = ["any-arch", "dep:cpp_demangle", "dep:unarm", "dep:arm-attr"]
|
||||
arm64 = ["any-arch", "dep:cpp_demangle", "dep:yaxpeax-arch", "dep:yaxpeax-arm"]
|
||||
wasm = ["bindings", "any-arch", "dep:console_error_panic_hook", "dep:console_log", "dep:wasm-bindgen", "dep:tsify-next", "dep:log"]
|
||||
mips = [
|
||||
"any-arch",
|
||||
"dep:rabbitizer",
|
||||
]
|
||||
ppc = [
|
||||
"any-arch",
|
||||
"dep:cwdemangle",
|
||||
"dep:cwextab",
|
||||
"dep:ppc750cl",
|
||||
]
|
||||
x86 = [
|
||||
"any-arch",
|
||||
"dep:cpp_demangle",
|
||||
"dep:iced-x86",
|
||||
"dep:msvc-demangler",
|
||||
]
|
||||
arm = [
|
||||
"any-arch",
|
||||
"dep:arm-attr",
|
||||
"dep:cpp_demangle",
|
||||
"dep:unarm",
|
||||
]
|
||||
arm64 = [
|
||||
"any-arch",
|
||||
"dep:cpp_demangle",
|
||||
"dep:yaxpeax-arch",
|
||||
"dep:yaxpeax-arm",
|
||||
]
|
||||
wasm = [
|
||||
"any-arch",
|
||||
"bindings",
|
||||
"dep:console_error_panic_hook",
|
||||
"dep:console_log",
|
||||
"dep:log",
|
||||
"dep:tsify-next",
|
||||
"dep:wasm-bindgen",
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["all"]
|
||||
@@ -63,7 +149,7 @@ gimli = { version = "0.31", default-features = false, features = ["read-all"], o
|
||||
|
||||
# ppc
|
||||
cwdemangle = { version = "1.0", optional = true }
|
||||
cwextab = { version = "1.0.2", optional = true }
|
||||
cwextab = { version = "1.0", optional = true }
|
||||
ppc750cl = { version = "0.3", optional = true }
|
||||
|
||||
# mips
|
||||
@@ -83,10 +169,10 @@ yaxpeax-arch = { version = "0.3", default-features = false, features = ["std"],
|
||||
yaxpeax-arm = { version = "0.3", default-features = false, features = ["std"], optional = true }
|
||||
|
||||
# build
|
||||
notify = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39dbb7f301ff7b20e594e34c3a2", optional = true }
|
||||
notify-debouncer-full = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39dbb7f301ff7b20e594e34c3a2", optional = true }
|
||||
notify = { version = "8.0.0", optional = true }
|
||||
notify-debouncer-full = { version = "0.5.0", optional = true }
|
||||
shell-escape = { version = "0.1", optional = true }
|
||||
tempfile = { version = "3.14", optional = true }
|
||||
tempfile = { version = "3.15", optional = true }
|
||||
time = { version = "0.3", optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
@@ -96,13 +182,20 @@ winapi = { version = "0.3", optional = true }
|
||||
# For Linux static binaries, use rustls
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "rustls-tls"], optional = true }
|
||||
self_update = { version = "0.41", default-features = false, features = ["rustls"], optional = true }
|
||||
self_update = { version = "0.42", default-features = false, features = ["rustls"], optional = true }
|
||||
|
||||
# For all other platforms, use native TLS
|
||||
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
||||
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "default-tls"], optional = true }
|
||||
self_update = { version = "0.41", optional = true }
|
||||
self_update = { version = "0.42", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = { version = "0.13", optional = true }
|
||||
heck = { version = "0.5", optional = true }
|
||||
pbjson-build = { version = "0.7", optional = true }
|
||||
prettyplease = { version = "0.2", optional = true }
|
||||
proc-macro2 = { version = "1.0", optional = true }
|
||||
prost-build = { version = "0.13", optional = true }
|
||||
quote = { version = "1.0", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
syn = { version = "2.0", optional = true }
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#[cfg(feature = "any-arch")]
|
||||
mod config_gen;
|
||||
|
||||
fn main() {
|
||||
#[cfg(feature = "bindings")]
|
||||
compile_protos();
|
||||
#[cfg(feature = "any-arch")]
|
||||
config_gen::generate_diff_config();
|
||||
}
|
||||
|
||||
#[cfg(feature = "bindings")]
|
||||
|
||||
248
objdiff-core/config-schema.json
Normal file
248
objdiff-core/config-schema.json
Normal file
@@ -0,0 +1,248 @@
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"id": "functionRelocDiffs",
|
||||
"type": "choice",
|
||||
"default": "name_address",
|
||||
"name": "Function relocation diffs",
|
||||
"description": "How relocation targets will be diffed in the function view.",
|
||||
"items": [
|
||||
{
|
||||
"value": "none",
|
||||
"name": "None"
|
||||
},
|
||||
{
|
||||
"value": "name_address",
|
||||
"name": "Name or address"
|
||||
},
|
||||
{
|
||||
"value": "data_value",
|
||||
"name": "Data value"
|
||||
},
|
||||
{
|
||||
"value": "all",
|
||||
"name": "Name or address, data value"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "spaceBetweenArgs",
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"name": "Space between args",
|
||||
"description": "Adds a space between arguments in the diff output."
|
||||
},
|
||||
{
|
||||
"id": "combineDataSections",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Combine data sections",
|
||||
"description": "Combines data sections with equal names."
|
||||
},
|
||||
{
|
||||
"id": "arm.archVersion",
|
||||
"type": "choice",
|
||||
"default": "auto",
|
||||
"name": "Architecture version",
|
||||
"description": "ARM architecture version to use for disassembly.",
|
||||
"items": [
|
||||
{
|
||||
"value": "auto",
|
||||
"name": "Auto"
|
||||
},
|
||||
{
|
||||
"value": "v4t",
|
||||
"name": "ARMv4T (GBA)"
|
||||
},
|
||||
{
|
||||
"value": "v5te",
|
||||
"name": "ARMv5TE (DS)"
|
||||
},
|
||||
{
|
||||
"value": "v6k",
|
||||
"name": "ARMv6K (3DS)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "arm.unifiedSyntax",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Unified syntax",
|
||||
"description": "Disassemble as unified assembly language (UAL)."
|
||||
},
|
||||
{
|
||||
"id": "arm.avRegisters",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Use A/V registers",
|
||||
"description": "Display R0-R3 as A1-A4 and R4-R11 as V1-V8."
|
||||
},
|
||||
{
|
||||
"id": "arm.r9Usage",
|
||||
"type": "choice",
|
||||
"default": "generalPurpose",
|
||||
"name": "Display R9 as",
|
||||
"items": [
|
||||
{
|
||||
"value": "generalPurpose",
|
||||
"name": "R9 or V6",
|
||||
"description": "Use R9 as a general-purpose register."
|
||||
},
|
||||
{
|
||||
"value": "sb",
|
||||
"name": "SB (static base)",
|
||||
"description": "Used for position-independent data (PID)."
|
||||
},
|
||||
{
|
||||
"value": "tr",
|
||||
"name": "TR (TLS register)",
|
||||
"description": "Used for thread-local storage."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "arm.slUsage",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Display R10 as SL",
|
||||
"description": "Used for explicit stack limits."
|
||||
},
|
||||
{
|
||||
"id": "arm.fpUsage",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Display R11 as FP",
|
||||
"description": "Used for frame pointers."
|
||||
},
|
||||
{
|
||||
"id": "arm.ipUsage",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Display R12 as IP",
|
||||
"description": "Used for interworking and long branches."
|
||||
},
|
||||
{
|
||||
"id": "mips.abi",
|
||||
"type": "choice",
|
||||
"default": "auto",
|
||||
"name": "ABI",
|
||||
"description": "MIPS ABI to use for disassembly.",
|
||||
"items": [
|
||||
{
|
||||
"value": "auto",
|
||||
"name": "Auto"
|
||||
},
|
||||
{
|
||||
"value": "o32",
|
||||
"name": "O32"
|
||||
},
|
||||
{
|
||||
"value": "n32",
|
||||
"name": "N32"
|
||||
},
|
||||
{
|
||||
"value": "n64",
|
||||
"name": "N64"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mips.instrCategory",
|
||||
"type": "choice",
|
||||
"default": "auto",
|
||||
"name": "Instruction category",
|
||||
"description": "MIPS instruction category to use for disassembly.",
|
||||
"items": [
|
||||
{
|
||||
"value": "auto",
|
||||
"name": "Auto"
|
||||
},
|
||||
{
|
||||
"value": "cpu",
|
||||
"name": "CPU"
|
||||
},
|
||||
{
|
||||
"value": "rsp",
|
||||
"name": "RSP (N64)"
|
||||
},
|
||||
{
|
||||
"value": "r3000gte",
|
||||
"name": "R3000 GTE (PS1)"
|
||||
},
|
||||
{
|
||||
"value": "r4000allegrex",
|
||||
"name": "R4000 ALLEGREX (PSP)"
|
||||
},
|
||||
{
|
||||
"value": "r5900",
|
||||
"name": "R5900 EE (PS2)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "x86.formatter",
|
||||
"type": "choice",
|
||||
"default": "intel",
|
||||
"name": "Format",
|
||||
"description": "x86 disassembly syntax.",
|
||||
"items": [
|
||||
{
|
||||
"value": "intel",
|
||||
"name": "Intel"
|
||||
},
|
||||
{
|
||||
"value": "gas",
|
||||
"name": "AT&T"
|
||||
},
|
||||
{
|
||||
"value": "nasm",
|
||||
"name": "NASM"
|
||||
},
|
||||
{
|
||||
"value": "masm",
|
||||
"name": "MASM"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": "general",
|
||||
"name": "General",
|
||||
"properties": [
|
||||
"functionRelocDiffs",
|
||||
"spaceBetweenArgs",
|
||||
"combineDataSections"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "arm",
|
||||
"name": "ARM",
|
||||
"properties": [
|
||||
"arm.archVersion",
|
||||
"arm.unifiedSyntax",
|
||||
"arm.avRegisters",
|
||||
"arm.r9Usage",
|
||||
"arm.slUsage",
|
||||
"arm.fpUsage",
|
||||
"arm.ipUsage"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mips",
|
||||
"name": "MIPS",
|
||||
"properties": [
|
||||
"mips.abi",
|
||||
"mips.instrCategory"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "x86",
|
||||
"name": "x86",
|
||||
"properties": [
|
||||
"x86.formatter"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
491
objdiff-core/config_gen.rs
Normal file
491
objdiff-core/config_gen.rs
Normal file
@@ -0,0 +1,491 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct ConfigSchema {
|
||||
pub properties: Vec<ConfigProperty>,
|
||||
pub groups: Vec<ConfigGroup>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ConfigProperty {
|
||||
#[serde(rename = "boolean")]
|
||||
Boolean(ConfigPropertyBoolean),
|
||||
#[serde(rename = "choice")]
|
||||
Choice(ConfigPropertyChoice),
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct ConfigPropertyBase {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct ConfigPropertyBoolean {
|
||||
#[serde(flatten)]
|
||||
pub base: ConfigPropertyBase,
|
||||
pub default: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct ConfigPropertyChoice {
|
||||
#[serde(flatten)]
|
||||
pub base: ConfigPropertyBase,
|
||||
pub default: String,
|
||||
pub items: Vec<ConfigPropertyChoiceItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct ConfigPropertyChoiceItem {
|
||||
pub value: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct ConfigGroup {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub properties: Vec<String>,
|
||||
}
|
||||
|
||||
fn build_doc(name: &str, description: Option<&str>) -> TokenStream {
|
||||
let mut doc = format!(" {}", name);
|
||||
let mut out = quote! { #[doc = #doc] };
|
||||
if let Some(description) = description {
|
||||
doc = format!(" {}", description);
|
||||
out.extend(quote! { #[doc = ""] });
|
||||
out.extend(quote! { #[doc = #doc] });
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
pub fn generate_diff_config() {
|
||||
let schema_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("config-schema.json");
|
||||
println!("cargo:rerun-if-changed={}", schema_path.display());
|
||||
let schema_file = File::open(schema_path).expect("Failed to open config schema file");
|
||||
let schema: ConfigSchema =
|
||||
serde_json::from_reader(schema_file).expect("Failed to parse config schema");
|
||||
|
||||
let mut enums = TokenStream::new();
|
||||
for property in &schema.properties {
|
||||
let ConfigProperty::Choice(choice) = property else {
|
||||
continue;
|
||||
};
|
||||
let enum_ident = format_ident!("{}", choice.base.id.to_upper_camel_case());
|
||||
let mut variants = TokenStream::new();
|
||||
let mut full_variants = TokenStream::new();
|
||||
let mut variant_info = TokenStream::new();
|
||||
let mut variant_to_str = TokenStream::new();
|
||||
let mut variant_to_name = TokenStream::new();
|
||||
let mut variant_to_description = TokenStream::new();
|
||||
let mut variant_from_str = TokenStream::new();
|
||||
for item in &choice.items {
|
||||
let variant_name = item.value.to_upper_camel_case();
|
||||
let variant_ident = format_ident!("{}", variant_name);
|
||||
let is_default = item.value == choice.default;
|
||||
variants.extend(build_doc(&item.name, item.description.as_deref()));
|
||||
if is_default {
|
||||
variants.extend(quote! { #[default] });
|
||||
}
|
||||
let value = &item.value;
|
||||
variants.extend(quote! {
|
||||
#[serde(rename = #value, alias = #variant_name)]
|
||||
#variant_ident,
|
||||
});
|
||||
full_variants.extend(quote! { #enum_ident::#variant_ident, });
|
||||
variant_to_str.extend(quote! { #enum_ident::#variant_ident => #value, });
|
||||
let name = &item.name;
|
||||
variant_to_name.extend(quote! { #enum_ident::#variant_ident => #name, });
|
||||
if let Some(description) = &item.description {
|
||||
variant_to_description.extend(quote! {
|
||||
#enum_ident::#variant_ident => Some(#description),
|
||||
});
|
||||
} else {
|
||||
variant_to_description.extend(quote! {
|
||||
#enum_ident::#variant_ident => None,
|
||||
});
|
||||
}
|
||||
let description = if let Some(description) = &item.description {
|
||||
quote! { Some(#description) }
|
||||
} else {
|
||||
quote! { None }
|
||||
};
|
||||
variant_info.extend(quote! {
|
||||
ConfigEnumVariantInfo {
|
||||
value: #value,
|
||||
name: #name,
|
||||
description: #description,
|
||||
is_default: #is_default,
|
||||
},
|
||||
});
|
||||
variant_from_str.extend(quote! {
|
||||
if s.eq_ignore_ascii_case(#value) { return Ok(#enum_ident::#variant_ident); }
|
||||
});
|
||||
}
|
||||
enums.extend(quote! {
|
||||
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
pub enum #enum_ident {
|
||||
#variants
|
||||
}
|
||||
impl ConfigEnum for #enum_ident {
|
||||
#[inline]
|
||||
fn variants() -> &'static [Self] {
|
||||
static VARIANTS: &[#enum_ident] = &[#full_variants];
|
||||
VARIANTS
|
||||
}
|
||||
#[inline]
|
||||
fn variant_info() -> &'static [ConfigEnumVariantInfo] {
|
||||
static VARIANT_INFO: &[ConfigEnumVariantInfo] = &[
|
||||
#variant_info
|
||||
];
|
||||
VARIANT_INFO
|
||||
}
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
#variant_to_str
|
||||
}
|
||||
}
|
||||
fn name(&self) -> &'static str {
|
||||
match self {
|
||||
#variant_to_name
|
||||
}
|
||||
}
|
||||
fn description(&self) -> Option<&'static str> {
|
||||
match self {
|
||||
#variant_to_description
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::str::FromStr for #enum_ident {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
#variant_from_str
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let mut groups = TokenStream::new();
|
||||
let mut group_idents = Vec::new();
|
||||
for group in &schema.groups {
|
||||
let ident = format_ident!("CONFIG_GROUP_{}", group.id.to_shouty_snake_case());
|
||||
let id = &group.id;
|
||||
let name = &group.name;
|
||||
let description = if let Some(description) = &group.description {
|
||||
quote! { Some(#description) }
|
||||
} else {
|
||||
quote! { None }
|
||||
};
|
||||
let properties =
|
||||
group.properties.iter().map(|p| format_ident!("{}", p.to_upper_camel_case()));
|
||||
groups.extend(quote! {
|
||||
ConfigPropertyGroup {
|
||||
id: #id,
|
||||
name: #name,
|
||||
description: #description,
|
||||
properties: &[#(ConfigPropertyId::#properties,)*],
|
||||
},
|
||||
});
|
||||
group_idents.push(ident);
|
||||
}
|
||||
|
||||
let mut property_idents = Vec::new();
|
||||
let mut property_variants = TokenStream::new();
|
||||
let mut variant_info = TokenStream::new();
|
||||
let mut config_property_id_to_str = TokenStream::new();
|
||||
let mut config_property_id_to_name = TokenStream::new();
|
||||
let mut config_property_id_to_description = TokenStream::new();
|
||||
let mut config_property_id_to_kind = TokenStream::new();
|
||||
let mut property_fields = TokenStream::new();
|
||||
let mut default_fields = TokenStream::new();
|
||||
let mut get_property_value_variants = TokenStream::new();
|
||||
let mut set_property_value_variants = TokenStream::new();
|
||||
let mut set_property_value_str_variants = TokenStream::new();
|
||||
let mut config_property_id_from_str = TokenStream::new();
|
||||
for property in &schema.properties {
|
||||
let base = match property {
|
||||
ConfigProperty::Boolean(b) => &b.base,
|
||||
ConfigProperty::Choice(c) => &c.base,
|
||||
};
|
||||
let id = &base.id;
|
||||
let enum_ident = format_ident!("{}", id.to_upper_camel_case());
|
||||
property_idents.push(enum_ident.clone());
|
||||
config_property_id_to_str.extend(quote! { Self::#enum_ident => #id, });
|
||||
let name = &base.name;
|
||||
config_property_id_to_name.extend(quote! { Self::#enum_ident => #name, });
|
||||
if let Some(description) = &base.description {
|
||||
config_property_id_to_description.extend(quote! {
|
||||
Self::#enum_ident => Some(#description),
|
||||
});
|
||||
} else {
|
||||
config_property_id_to_description.extend(quote! {
|
||||
Self::#enum_ident => None,
|
||||
});
|
||||
}
|
||||
let doc = build_doc(name, base.description.as_deref());
|
||||
property_variants.extend(quote! { #doc #enum_ident, });
|
||||
property_fields.extend(doc);
|
||||
let field_ident = format_ident!("{}", id.to_snake_case());
|
||||
match property {
|
||||
ConfigProperty::Boolean(b) => {
|
||||
let default = b.default;
|
||||
if default {
|
||||
property_fields.extend(quote! {
|
||||
#[serde(default = "default_true")]
|
||||
});
|
||||
}
|
||||
property_fields.extend(quote! {
|
||||
pub #field_ident: bool,
|
||||
});
|
||||
default_fields.extend(quote! {
|
||||
#field_ident: #default,
|
||||
});
|
||||
}
|
||||
ConfigProperty::Choice(_) => {
|
||||
property_fields.extend(quote! {
|
||||
pub #field_ident: #enum_ident,
|
||||
});
|
||||
default_fields.extend(quote! {
|
||||
#field_ident: #enum_ident::default(),
|
||||
});
|
||||
}
|
||||
}
|
||||
let property_value = match property {
|
||||
ConfigProperty::Boolean(_) => {
|
||||
quote! { ConfigPropertyValue::Boolean(self.#field_ident) }
|
||||
}
|
||||
ConfigProperty::Choice(_) => {
|
||||
quote! { ConfigPropertyValue::Choice(self.#field_ident.as_str()) }
|
||||
}
|
||||
};
|
||||
get_property_value_variants.extend(quote! {
|
||||
ConfigPropertyId::#enum_ident => #property_value,
|
||||
});
|
||||
match property {
|
||||
ConfigProperty::Boolean(_) => {
|
||||
set_property_value_variants.extend(quote! {
|
||||
ConfigPropertyId::#enum_ident => {
|
||||
if let ConfigPropertyValue::Boolean(value) = value {
|
||||
self.#field_ident = value;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
},
|
||||
});
|
||||
set_property_value_str_variants.extend(quote! {
|
||||
ConfigPropertyId::#enum_ident => {
|
||||
if let Ok(value) = value.parse() {
|
||||
self.#field_ident = value;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
ConfigProperty::Choice(_) => {
|
||||
set_property_value_variants.extend(quote! {
|
||||
ConfigPropertyId::#enum_ident => {
|
||||
if let ConfigPropertyValue::Choice(value) = value {
|
||||
if let Ok(value) = value.parse() {
|
||||
self.#field_ident = value;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
},
|
||||
});
|
||||
set_property_value_str_variants.extend(quote! {
|
||||
ConfigPropertyId::#enum_ident => {
|
||||
if let Ok(value) = value.parse() {
|
||||
self.#field_ident = value;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
let description = if let Some(description) = &base.description {
|
||||
quote! { Some(#description) }
|
||||
} else {
|
||||
quote! { None }
|
||||
};
|
||||
variant_info.extend(quote! {
|
||||
ConfigEnumVariantInfo {
|
||||
value: #id,
|
||||
name: #name,
|
||||
description: #description,
|
||||
is_default: false,
|
||||
},
|
||||
});
|
||||
match property {
|
||||
ConfigProperty::Boolean(_) => {
|
||||
config_property_id_to_kind.extend(quote! {
|
||||
Self::#enum_ident => ConfigPropertyKind::Boolean,
|
||||
});
|
||||
}
|
||||
ConfigProperty::Choice(_) => {
|
||||
config_property_id_to_kind.extend(quote! {
|
||||
Self::#enum_ident => ConfigPropertyKind::Choice(#enum_ident::variant_info()),
|
||||
});
|
||||
}
|
||||
}
|
||||
let snake_id = id.to_snake_case();
|
||||
config_property_id_from_str.extend(quote! {
|
||||
if s.eq_ignore_ascii_case(#id) || s.eq_ignore_ascii_case(#snake_id) {
|
||||
return Ok(Self::#enum_ident);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let tokens = quote! {
|
||||
pub trait ConfigEnum: Sized {
|
||||
fn variants() -> &'static [Self];
|
||||
fn variant_info() -> &'static [ConfigEnumVariantInfo];
|
||||
fn as_str(&self) -> &'static str;
|
||||
fn name(&self) -> &'static str;
|
||||
fn description(&self) -> Option<&'static str>;
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ConfigEnumVariantInfo {
|
||||
pub value: &'static str,
|
||||
pub name: &'static str,
|
||||
pub description: Option<&'static str>,
|
||||
pub is_default: bool,
|
||||
}
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum ConfigPropertyId {
|
||||
#property_variants
|
||||
}
|
||||
impl ConfigEnum for ConfigPropertyId {
|
||||
#[inline]
|
||||
fn variants() -> &'static [Self] {
|
||||
static VARIANTS: &[ConfigPropertyId] = &[#(ConfigPropertyId::#property_idents,)*];
|
||||
VARIANTS
|
||||
}
|
||||
#[inline]
|
||||
fn variant_info() -> &'static [ConfigEnumVariantInfo] {
|
||||
static VARIANT_INFO: &[ConfigEnumVariantInfo] = &[
|
||||
#variant_info
|
||||
];
|
||||
VARIANT_INFO
|
||||
}
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
#config_property_id_to_str
|
||||
}
|
||||
}
|
||||
fn name(&self) -> &'static str {
|
||||
match self {
|
||||
#config_property_id_to_name
|
||||
}
|
||||
}
|
||||
fn description(&self) -> Option<&'static str> {
|
||||
match self {
|
||||
#config_property_id_to_description
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ConfigPropertyId {
|
||||
pub fn kind(&self) -> ConfigPropertyKind {
|
||||
match self {
|
||||
#config_property_id_to_kind
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::str::FromStr for ConfigPropertyId {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
#config_property_id_from_str
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ConfigPropertyGroup {
|
||||
pub id: &'static str,
|
||||
pub name: &'static str,
|
||||
pub description: Option<&'static str>,
|
||||
pub properties: &'static [ConfigPropertyId],
|
||||
}
|
||||
pub static CONFIG_GROUPS: &[ConfigPropertyGroup] = &[#groups];
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum ConfigPropertyValue {
|
||||
Boolean(bool),
|
||||
Choice(&'static str),
|
||||
}
|
||||
impl ConfigPropertyValue {
|
||||
pub fn to_json(&self) -> serde_json::Value {
|
||||
match self {
|
||||
ConfigPropertyValue::Boolean(value) => serde_json::Value::Bool(*value),
|
||||
ConfigPropertyValue::Choice(value) => serde_json::Value::String(value.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ConfigPropertyKind {
|
||||
Boolean,
|
||||
Choice(&'static [ConfigEnumVariantInfo]),
|
||||
}
|
||||
#enums
|
||||
#[inline(always)]
|
||||
fn default_true() -> bool { true }
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
#[serde(default)]
|
||||
pub struct DiffObjConfig {
|
||||
#property_fields
|
||||
}
|
||||
impl Default for DiffObjConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
#default_fields
|
||||
}
|
||||
}
|
||||
}
|
||||
impl DiffObjConfig {
|
||||
pub fn get_property_value(&self, id: ConfigPropertyId) -> ConfigPropertyValue {
|
||||
match id {
|
||||
#get_property_value_variants
|
||||
}
|
||||
}
|
||||
#[allow(clippy::result_unit_err)]
|
||||
pub fn set_property_value(&mut self, id: ConfigPropertyId, value: ConfigPropertyValue) -> Result<(), ()> {
|
||||
match id {
|
||||
#set_property_value_variants
|
||||
}
|
||||
}
|
||||
#[allow(clippy::result_unit_err)]
|
||||
pub fn set_property_value_str(&mut self, id: ConfigPropertyId, value: &str) -> Result<(), ()> {
|
||||
match id {
|
||||
#set_property_value_str_variants
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let file = syn::parse2(tokens).unwrap();
|
||||
let formatted = prettyplease::unparse(&file);
|
||||
std::fs::write(
|
||||
PathBuf::from(std::env::var_os("OUT_DIR").unwrap()).join("config.gen.rs"),
|
||||
formatted,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -21,9 +21,9 @@ enum SymbolFlag {
|
||||
SYMBOL_NONE = 0;
|
||||
SYMBOL_GLOBAL = 1;
|
||||
SYMBOL_LOCAL = 2;
|
||||
SYMBOL_WEAK = 3;
|
||||
SYMBOL_COMMON = 4;
|
||||
SYMBOL_HIDDEN = 5;
|
||||
SYMBOL_WEAK = 4;
|
||||
SYMBOL_COMMON = 8;
|
||||
SYMBOL_HIDDEN = 16;
|
||||
}
|
||||
|
||||
// A single parsed instruction
|
||||
@@ -122,10 +122,17 @@ message InstructionBranchTo {
|
||||
uint32 branch_index = 2;
|
||||
}
|
||||
|
||||
message FunctionDiff {
|
||||
message SymbolRef {
|
||||
optional uint32 section_index = 1;
|
||||
uint32 symbol_index = 2;
|
||||
}
|
||||
|
||||
message SymbolDiff {
|
||||
Symbol symbol = 1;
|
||||
repeated InstructionDiff instructions = 2;
|
||||
optional float match_percent = 3;
|
||||
// The symbol ref in the _other_ object that this symbol was diffed against
|
||||
optional SymbolRef target = 5;
|
||||
}
|
||||
|
||||
message DataDiff {
|
||||
@@ -140,7 +147,7 @@ message SectionDiff {
|
||||
SectionKind kind = 2;
|
||||
uint64 size = 3;
|
||||
uint64 address = 4;
|
||||
repeated FunctionDiff functions = 5;
|
||||
repeated SymbolDiff symbols = 5;
|
||||
repeated DataDiff data = 6;
|
||||
optional float match_percent = 7;
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -139,9 +139,9 @@ impl ObjArch for ObjArchArm {
|
||||
|
||||
let version = match config.arm_arch_version {
|
||||
ArmArchVersion::Auto => self.detected_version.unwrap_or(ArmVersion::V5Te),
|
||||
ArmArchVersion::V4T => ArmVersion::V4T,
|
||||
ArmArchVersion::V5TE => ArmVersion::V5Te,
|
||||
ArmArchVersion::V6K => ArmVersion::V6K,
|
||||
ArmArchVersion::V4t => ArmVersion::V4T,
|
||||
ArmArchVersion::V5te => ArmVersion::V5Te,
|
||||
ArmArchVersion::V6k => ArmVersion::V6K,
|
||||
};
|
||||
let endian = match self.endianness {
|
||||
object::Endianness::Little => unarm::Endian::Little,
|
||||
@@ -276,6 +276,19 @@ impl ObjArch for ObjArchArm {
|
||||
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
|
||||
Cow::Owned(format!("<{flags:?}>"))
|
||||
}
|
||||
|
||||
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize {
|
||||
match flags {
|
||||
RelocationFlags::Elf { r_type } => match r_type {
|
||||
elf::R_ARM_ABS32 => 4,
|
||||
elf::R_ARM_REL32 => 4,
|
||||
elf::R_ARM_ABS16 => 2,
|
||||
elf::R_ARM_ABS8 => 1,
|
||||
_ => 1,
|
||||
},
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
||||
@@ -173,6 +173,21 @@ impl ObjArch for ObjArchArm64 {
|
||||
_ => Cow::Owned(format!("<{flags:?}>")),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize {
|
||||
match flags {
|
||||
RelocationFlags::Elf { r_type } => match r_type {
|
||||
elf::R_AARCH64_ABS64 => 8,
|
||||
elf::R_AARCH64_ABS32 => 4,
|
||||
elf::R_AARCH64_ABS16 => 2,
|
||||
elf::R_AARCH64_PREL64 => 8,
|
||||
elf::R_AARCH64_PREL32 => 4,
|
||||
elf::R_AARCH64_PREL16 => 2,
|
||||
_ => 1,
|
||||
},
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DisplayCtx<'a> {
|
||||
|
||||
@@ -99,8 +99,8 @@ impl ObjArch for ObjArchMips {
|
||||
MipsInstrCategory::Auto => self.instr_category,
|
||||
MipsInstrCategory::Cpu => InstrCategory::CPU,
|
||||
MipsInstrCategory::Rsp => InstrCategory::RSP,
|
||||
MipsInstrCategory::R3000Gte => InstrCategory::R3000GTE,
|
||||
MipsInstrCategory::R4000Allegrex => InstrCategory::R4000ALLEGREX,
|
||||
MipsInstrCategory::R3000gte => InstrCategory::R3000GTE,
|
||||
MipsInstrCategory::R4000allegrex => InstrCategory::R4000ALLEGREX,
|
||||
MipsInstrCategory::R5900 => InstrCategory::R5900,
|
||||
};
|
||||
|
||||
@@ -271,6 +271,17 @@ impl ObjArch for ObjArchMips {
|
||||
_ => Cow::Owned(format!("<{flags:?}>")),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize {
|
||||
match flags {
|
||||
RelocationFlags::Elf { r_type } => match r_type {
|
||||
elf::R_MIPS_16 => 2,
|
||||
elf::R_MIPS_32 => 4,
|
||||
_ => 1,
|
||||
},
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn push_reloc(args: &mut Vec<ObjInsArg>, reloc: &ObjReloc) -> Result<()> {
|
||||
|
||||
@@ -148,6 +148,8 @@ pub trait ObjArch: Send + Sync {
|
||||
|
||||
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str>;
|
||||
|
||||
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize;
|
||||
|
||||
fn symbol_address(&self, symbol: &Symbol) -> u64 { symbol.address() }
|
||||
|
||||
fn guess_data_type(&self, _instruction: &ObjIns) -> Option<DataType> { None }
|
||||
@@ -156,6 +158,17 @@ pub trait ObjArch: Send + Sync {
|
||||
Some(format!("Bytes: {:#x?}", bytes))
|
||||
}
|
||||
|
||||
fn display_ins_data(&self, ins: &ObjIns) -> Option<String> {
|
||||
let reloc = ins.reloc.as_ref()?;
|
||||
if reloc.addend >= 0 && reloc.target.bytes.len() > reloc.addend as usize {
|
||||
self.guess_data_type(ins).and_then(|ty| {
|
||||
self.display_data_type(ty, &reloc.target.bytes[reloc.addend as usize..])
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Downcast methods
|
||||
#[cfg(feature = "ppc")]
|
||||
fn ppc(&self) -> Option<&ppc::ObjArchPpc> { None }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{BTreeMap, HashMap},
|
||||
collections::{BTreeMap, HashMap, HashSet},
|
||||
};
|
||||
|
||||
use anyhow::{bail, ensure, Result};
|
||||
@@ -10,7 +10,7 @@ use object::{
|
||||
elf, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, RelocationTarget,
|
||||
Symbol, SymbolKind,
|
||||
};
|
||||
use ppc750cl::{Argument, InsIter, Opcode, ParsedIns, GPR};
|
||||
use ppc750cl::{Argument, Arguments, Ins, InsIter, Opcode, ParsedIns, GPR};
|
||||
|
||||
use crate::{
|
||||
arch::{DataType, ObjArch, ProcessCodeResult},
|
||||
@@ -143,6 +143,15 @@ impl ObjArch for ObjArchPpc {
|
||||
}
|
||||
}
|
||||
|
||||
if reloc.is_none() {
|
||||
if let Some(fake_pool_reloc) = fake_pool_reloc_for_addr.get(&cur_addr) {
|
||||
// If this instruction has a fake pool relocation, show it as a fake argument
|
||||
// at the end of the line.
|
||||
args.push(ObjInsArg::PlainText(" ".into()));
|
||||
push_reloc(&mut args, fake_pool_reloc)?;
|
||||
}
|
||||
}
|
||||
|
||||
ops.push(ins.op as u16);
|
||||
let line = line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
|
||||
insts.push(ObjIns {
|
||||
@@ -193,24 +202,23 @@ impl ObjArch for ObjArchPpc {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize {
|
||||
match flags {
|
||||
RelocationFlags::Elf { r_type } => match r_type {
|
||||
elf::R_PPC_ADDR32 => 4,
|
||||
elf::R_PPC_UADDR32 => 4,
|
||||
_ => 1,
|
||||
},
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn guess_data_type(&self, instruction: &ObjIns) -> Option<super::DataType> {
|
||||
if instruction.reloc.as_ref().is_some_and(|r| r.target.name.starts_with("@stringBase")) {
|
||||
return Some(DataType::String);
|
||||
}
|
||||
|
||||
let op = Opcode::from(instruction.op as u8);
|
||||
if let Some(ty) = guess_data_type_from_load_store_inst_op(op) {
|
||||
Some(ty)
|
||||
} else if op == Opcode::Addi {
|
||||
// Assume that any addi instruction that references a local symbol is loading a string.
|
||||
// This hack is not ideal and results in tons of false positives where it will show
|
||||
// garbage strings (e.g. misinterpreting arrays, float literals, etc).
|
||||
// But not all strings are in the @stringBase pool, so the condition above that checks
|
||||
// the target symbol name would miss some.
|
||||
Some(DataType::String)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
guess_data_type_from_load_store_inst_op(Opcode::from(instruction.op as u8))
|
||||
}
|
||||
|
||||
fn display_data_type(&self, ty: DataType, bytes: &[u8]) -> Option<String> {
|
||||
@@ -248,6 +256,12 @@ fn push_reloc(args: &mut Vec<ObjInsArg>, reloc: &ObjReloc) -> Result<()> {
|
||||
elf::R_PPC_ADDR32 | elf::R_PPC_UADDR32 | elf::R_PPC_REL24 | elf::R_PPC_REL14 => {
|
||||
args.push(ObjInsArg::Reloc);
|
||||
}
|
||||
elf::R_PPC_NONE => {
|
||||
// Fake pool relocation.
|
||||
args.push(ObjInsArg::PlainText("<".into()));
|
||||
args.push(ObjInsArg::Reloc);
|
||||
args.push(ObjInsArg::PlainText(">".into()));
|
||||
}
|
||||
_ => bail!("Unsupported ELF PPC relocation type {r_type}"),
|
||||
},
|
||||
flags => bail!("Unsupported PPC relocation kind: {flags:?}"),
|
||||
@@ -442,16 +456,43 @@ fn get_offset_and_addr_gpr_for_possible_pool_reference(
|
||||
Argument::Simm(simm),
|
||||
) => Some((simm.0, addr_src_gpr, Some(addr_dst_gpr))),
|
||||
(
|
||||
// `mr` or `mr.`
|
||||
Opcode::Or,
|
||||
Argument::GPR(addr_dst_gpr),
|
||||
Argument::GPR(addr_src_gpr),
|
||||
Argument::None,
|
||||
) => Some((0, addr_src_gpr, Some(addr_dst_gpr))), // `mr` or `mr.`
|
||||
) => Some((0, addr_src_gpr, Some(addr_dst_gpr))),
|
||||
(
|
||||
Opcode::Add,
|
||||
Argument::GPR(addr_dst_gpr),
|
||||
Argument::GPR(addr_src_gpr),
|
||||
Argument::GPR(_offset_gpr),
|
||||
) => Some((0, addr_src_gpr, Some(addr_dst_gpr))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the relocation we're keeping track of in a particular register when an instruction reuses
|
||||
// that register to hold some other value, unrelated to pool relocation addresses.
|
||||
fn clear_overwritten_gprs(ins: Ins, gpr_pool_relocs: &mut HashMap<u8, ObjReloc>) {
|
||||
let mut def_args = Arguments::default();
|
||||
ins.parse_defs(&mut def_args);
|
||||
for arg in def_args {
|
||||
if let Argument::GPR(gpr) = arg {
|
||||
if ins.op == Opcode::Lmw {
|
||||
// `lmw` overwrites all registers from rd to r31.
|
||||
// ppc750cl only returns rd itself, so we manually clear the rest of them.
|
||||
for reg in gpr.0..31 {
|
||||
gpr_pool_relocs.remove(®);
|
||||
}
|
||||
break;
|
||||
}
|
||||
gpr_pool_relocs.remove(&gpr.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We create a fake relocation for an instruction, vaguely simulating what the actual relocation
|
||||
// might have looked like if it wasn't pooled. This is so minimal changes are needed to display
|
||||
// pooled accesses vs non-pooled accesses. We set the relocation type to R_PPC_NONE to indicate that
|
||||
@@ -461,14 +502,17 @@ fn get_offset_and_addr_gpr_for_possible_pool_reference(
|
||||
fn make_fake_pool_reloc(offset: i16, cur_addr: u32, pool_reloc: &ObjReloc) -> Option<ObjReloc> {
|
||||
let offset_from_pool = pool_reloc.addend + offset as i64;
|
||||
let target_address = pool_reloc.target.address.checked_add_signed(offset_from_pool)?;
|
||||
let orig_section_index = pool_reloc.target.orig_section_index?;
|
||||
// We also need to create a fake target symbol to go inside our fake relocation.
|
||||
// This is because we don't have access to list of all symbols in this section, so we can't find
|
||||
// the real symbol yet. Instead we make a placeholder that has the correct `orig_section_index`
|
||||
// and `address` fields, and then later on when this information is displayed to the user, we
|
||||
// can find the real symbol by searching through the object's section's symbols for one that
|
||||
// contains this address.
|
||||
let fake_target_symbol = ObjSymbol {
|
||||
let target;
|
||||
let addend;
|
||||
if pool_reloc.target.orig_section_index.is_some() {
|
||||
// If the target symbol is within this current object, then we also need to create a fake
|
||||
// target symbol to go inside our fake relocation. This is because we don't have access to
|
||||
// list of all symbols in this section, so we can't find the real symbol within the pool
|
||||
// based on its address yet. Instead we make a placeholder that has the correct
|
||||
// `orig_section_index` and `address` fields, and then later on when this information is
|
||||
// displayed to the user, we can find the real symbol by searching through the object's
|
||||
// section's symbols for one that contains this address.
|
||||
target = ObjSymbol {
|
||||
name: "".to_string(),
|
||||
demangled_name: None,
|
||||
address: target_address,
|
||||
@@ -477,19 +521,32 @@ fn make_fake_pool_reloc(offset: i16, cur_addr: u32, pool_reloc: &ObjReloc) -> Op
|
||||
size_known: false,
|
||||
kind: Default::default(),
|
||||
flags: Default::default(),
|
||||
orig_section_index: Some(orig_section_index),
|
||||
orig_section_index: pool_reloc.target.orig_section_index,
|
||||
virtual_address: None,
|
||||
original_index: None,
|
||||
bytes: vec![],
|
||||
};
|
||||
// The addend is also fake because we don't know yet if the `target_address` here is the exact
|
||||
// start of the symbol or if it's in the middle of it.
|
||||
let fake_addend = 0;
|
||||
addend = 0;
|
||||
} else {
|
||||
// But if the target symbol is in a different object (extern), then we simply copy the pool
|
||||
// relocation's target. This is because it won't be possible to locate the actual symbol
|
||||
// later on based only off of an offset without knowing the object or section it's in. And
|
||||
// doing that for external symbols would also be unnecessary, because when the compiler
|
||||
// generates an instruction that accesses an external "pool" plus some offset, that won't be
|
||||
// a normal pool that contains other symbols within it that we want to display. It will be
|
||||
// something like a vtable for a class with multiple inheritance (for example, dCcD_Cyl in
|
||||
// The Wind Waker). So just showing that vtable symbol plus an addend to represent the
|
||||
// offset into it works fine in this case, no fake symbol to hold an address is necessary.
|
||||
target = pool_reloc.target.clone();
|
||||
addend = pool_reloc.addend;
|
||||
};
|
||||
Some(ObjReloc {
|
||||
flags: RelocationFlags::Elf { r_type: elf::R_PPC_NONE },
|
||||
address: cur_addr as u64,
|
||||
target: fake_target_symbol,
|
||||
addend: fake_addend,
|
||||
target,
|
||||
addend,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -497,64 +554,137 @@ fn make_fake_pool_reloc(offset: i16, cur_addr: u32, pool_reloc: &ObjReloc) -> Op
|
||||
// of pooled data relocations in them, finding which instructions load data from those addresses,
|
||||
// and constructing a mapping of the address of that instruction to a "fake pool relocation" that
|
||||
// simulates what that instruction's relocation would look like if data hadn't been pooled.
|
||||
// Limitations: This method currently only goes through the instructions in a function in linear
|
||||
// order, from start to finish. It does *not* follow any branches. This means that it could have
|
||||
// false positives or false negatives in determining which relocation is currently loaded in which
|
||||
// register at any given point in the function, as control flow is not respected.
|
||||
// There are currently no known examples of this method producing inaccurate results in reality, but
|
||||
// if examples are found, it may be possible to update this method to also follow all branches so
|
||||
// that it produces more accurate results.
|
||||
// This method tries to follow the function's proper control flow. It keeps track of a queue of
|
||||
// states it hasn't traversed yet, where each state holds an instruction address and a HashMap of
|
||||
// which registers hold which pool relocations at that point.
|
||||
// When a conditional or unconditional branch is encountered, the destination of the branch is added
|
||||
// to the queue. Conditional branches will traverse both the path where the branch is taken and the
|
||||
// one where it's not. Unconditional branches only follow the branch, ignoring any code immediately
|
||||
// after the branch instruction.
|
||||
// Limitations: This method cannot read jump tables. This is because the jump tables are located in
|
||||
// the .data section, but ObjArch.process_code only has access to the .text section. In order to
|
||||
// work around this limitation and avoid completely missing most code inside switch statements that
|
||||
// use jump tables, we instead guess that any parts of a function we missed were switch cases, and
|
||||
// traverse them as if the last `bctr` before that address had branched there. This should be fairly
|
||||
// accurate in practice - in testing the only instructions it seems to miss are double branches that
|
||||
// the compiler generates in error which can never be reached during normal execution anyway.
|
||||
fn generate_fake_pool_reloc_for_addr_mapping(
|
||||
address: u64,
|
||||
func_address: u64,
|
||||
code: &[u8],
|
||||
relocations: &[ObjReloc],
|
||||
) -> HashMap<u32, ObjReloc> {
|
||||
let mut active_pool_relocs = HashMap::new();
|
||||
let mut visited_ins_addrs = HashSet::new();
|
||||
let mut pool_reloc_for_addr = HashMap::new();
|
||||
for (cur_addr, ins) in InsIter::new(code, address as u32) {
|
||||
let simplified = ins.simplified();
|
||||
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
|
||||
let mut ins_iters_with_gpr_state =
|
||||
vec![(InsIter::new(code, func_address as u32), HashMap::new())];
|
||||
let mut gpr_state_at_bctr = BTreeMap::new();
|
||||
while let Some((ins_iter, mut gpr_pool_relocs)) = ins_iters_with_gpr_state.pop() {
|
||||
for (cur_addr, ins) in ins_iter {
|
||||
if visited_ins_addrs.contains(&cur_addr) {
|
||||
// Avoid getting stuck in an infinite loop when following looping branches.
|
||||
break;
|
||||
}
|
||||
visited_ins_addrs.insert(cur_addr);
|
||||
|
||||
let simplified = ins.simplified();
|
||||
|
||||
// First handle traversing the function's control flow.
|
||||
let mut branch_dest = None;
|
||||
for arg in simplified.args_iter() {
|
||||
if let Argument::BranchDest(dest) = arg {
|
||||
let dest = cur_addr.wrapping_add_signed(dest.0);
|
||||
branch_dest = Some(dest);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Some(branch_dest) = branch_dest {
|
||||
if branch_dest >= func_address as u32
|
||||
&& (branch_dest - func_address as u32) < code.len() as u32
|
||||
{
|
||||
let dest_offset_into_func = branch_dest - func_address as u32;
|
||||
let dest_code_slice = &code[dest_offset_into_func as usize..];
|
||||
match ins.op {
|
||||
Opcode::Bc => {
|
||||
// Conditional branch.
|
||||
// Add the branch destination to the queue to do later.
|
||||
ins_iters_with_gpr_state.push((
|
||||
InsIter::new(dest_code_slice, branch_dest),
|
||||
gpr_pool_relocs.clone(),
|
||||
));
|
||||
// Then continue on with the current iterator.
|
||||
}
|
||||
Opcode::B => {
|
||||
if simplified.mnemonic != "bl" {
|
||||
// Unconditional branch.
|
||||
// Add the branch destination to the queue.
|
||||
ins_iters_with_gpr_state.push((
|
||||
InsIter::new(dest_code_slice, branch_dest),
|
||||
gpr_pool_relocs.clone(),
|
||||
));
|
||||
// Break out of the current iterator so we can do the newly added one.
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Opcode::Bcctr = ins.op {
|
||||
if simplified.mnemonic == "bctr" {
|
||||
// Unconditional branch to count register.
|
||||
// Likely a jump table.
|
||||
gpr_state_at_bctr.insert(cur_addr, gpr_pool_relocs.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Then handle keeping track of which GPR contains which pool relocation.
|
||||
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
|
||||
if let Some(reloc) = reloc {
|
||||
// This instruction has a real relocation, so it may be a pool load we want to keep
|
||||
// track of.
|
||||
let args = &simplified.args;
|
||||
match (ins.op, args[0], args[1], args[2]) {
|
||||
(
|
||||
// `lis` + `addi`
|
||||
Opcode::Addi,
|
||||
Argument::GPR(addr_dst_gpr),
|
||||
Argument::GPR(_addr_src_gpr),
|
||||
Argument::Simm(_simm),
|
||||
) => {
|
||||
active_pool_relocs.insert(addr_dst_gpr.0, reloc.clone()); // `lis` + `addi`
|
||||
gpr_pool_relocs.insert(addr_dst_gpr.0, reloc.clone());
|
||||
}
|
||||
(
|
||||
// `lis` + `ori`
|
||||
Opcode::Ori,
|
||||
Argument::GPR(addr_dst_gpr),
|
||||
Argument::GPR(_addr_src_gpr),
|
||||
Argument::Uimm(_uimm),
|
||||
) => {
|
||||
active_pool_relocs.insert(addr_dst_gpr.0, reloc.clone()); // `lis` + `ori`
|
||||
gpr_pool_relocs.insert(addr_dst_gpr.0, reloc.clone());
|
||||
}
|
||||
(Opcode::B, _, _, _) => {
|
||||
if simplified.mnemonic == "bl" {
|
||||
// When encountering a function call, clear any active pool relocations from
|
||||
// the volatile registers (r0, r3-r12), but not the nonvolatile registers.
|
||||
active_pool_relocs.remove(&0);
|
||||
gpr_pool_relocs.remove(&0);
|
||||
for gpr in 3..12 {
|
||||
active_pool_relocs.remove(&gpr);
|
||||
gpr_pool_relocs.remove(&gpr);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
_ => {
|
||||
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
|
||||
}
|
||||
}
|
||||
} else if let Some((offset, addr_src_gpr, addr_dst_gpr)) =
|
||||
get_offset_and_addr_gpr_for_possible_pool_reference(ins.op, &simplified)
|
||||
{
|
||||
// This instruction doesn't have a real relocation, so it may be a reference to one of
|
||||
// the already-loaded pools.
|
||||
if let Some(pool_reloc) = active_pool_relocs.get(&addr_src_gpr.0) {
|
||||
if let Some(fake_pool_reloc) = make_fake_pool_reloc(offset, cur_addr, pool_reloc) {
|
||||
if let Some(pool_reloc) = gpr_pool_relocs.get(&addr_src_gpr.0) {
|
||||
if let Some(fake_pool_reloc) =
|
||||
make_fake_pool_reloc(offset, cur_addr, pool_reloc)
|
||||
{
|
||||
pool_reloc_for_addr.insert(cur_addr, fake_pool_reloc);
|
||||
}
|
||||
if let Some(addr_dst_gpr) = addr_dst_gpr {
|
||||
@@ -568,7 +698,40 @@ fn generate_fake_pool_reloc_for_addr_mapping(
|
||||
// Then the body of the loop will `lwzx` one of the array elements from r21.
|
||||
let mut new_reloc = pool_reloc.clone();
|
||||
new_reloc.addend += offset as i64;
|
||||
active_pool_relocs.insert(addr_dst_gpr.0, new_reloc);
|
||||
gpr_pool_relocs.insert(addr_dst_gpr.0, new_reloc);
|
||||
} else {
|
||||
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
|
||||
}
|
||||
} else {
|
||||
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
|
||||
}
|
||||
} else {
|
||||
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, if we're about to finish the outer loop and don't have any more control flow to
|
||||
// follow, we check if there are any instruction addresses in this function that we missed.
|
||||
// If so, and if there were any `bctr` instructions before those points in this function,
|
||||
// then we try to traverse those missing spots as switch cases.
|
||||
if ins_iters_with_gpr_state.is_empty() {
|
||||
let unseen_addrs = (func_address as u32..func_address as u32 + code.len() as u32)
|
||||
.step_by(4)
|
||||
.filter(|addr| !visited_ins_addrs.contains(addr));
|
||||
for unseen_addr in unseen_addrs {
|
||||
let prev_bctr_gpr_state = gpr_state_at_bctr
|
||||
.iter()
|
||||
.filter(|(&addr, _)| addr < unseen_addr)
|
||||
.min_by_key(|(&addr, _)| addr)
|
||||
.map(|(_, gpr_state)| gpr_state);
|
||||
if let Some(gpr_pool_relocs) = prev_bctr_gpr_state {
|
||||
let dest_offset_into_func = unseen_addr - func_address as u32;
|
||||
let dest_code_slice = &code[dest_offset_into_func as usize..];
|
||||
ins_iters_with_gpr_state.push((
|
||||
InsIter::new(dest_code_slice, unseen_addr),
|
||||
gpr_pool_relocs.clone(),
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +162,19 @@ impl ObjArch for ObjArchX86 {
|
||||
_ => Cow::Owned(format!("<{flags:?}>")),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize {
|
||||
match flags {
|
||||
RelocationFlags::Coff { typ } => match typ {
|
||||
pe::IMAGE_REL_I386_DIR16 => 2,
|
||||
pe::IMAGE_REL_I386_REL16 => 2,
|
||||
pe::IMAGE_REL_I386_DIR32 => 4,
|
||||
pe::IMAGE_REL_I386_REL32 => 4,
|
||||
_ => 1,
|
||||
},
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_arg(
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::{
|
||||
ObjDataDiff, ObjDataDiffKind, ObjDiff, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo,
|
||||
ObjInsDiff, ObjInsDiffKind, ObjSectionDiff, ObjSymbolDiff,
|
||||
},
|
||||
obj,
|
||||
obj::{
|
||||
ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSectionKind, ObjSymbol,
|
||||
ObjSymbolFlagSet, ObjSymbolFlags,
|
||||
@@ -39,14 +40,15 @@ impl ObjectDiff {
|
||||
impl SectionDiff {
|
||||
pub fn new(obj: &ObjInfo, section_index: usize, section_diff: &ObjSectionDiff) -> Self {
|
||||
let section = &obj.sections[section_index];
|
||||
let functions = section_diff.symbols.iter().map(|d| FunctionDiff::new(obj, d)).collect();
|
||||
let symbols = section_diff.symbols.iter().map(|d| SymbolDiff::new(obj, d)).collect();
|
||||
let data = section_diff.data_diff.iter().map(|d| DataDiff::new(obj, d)).collect();
|
||||
// TODO: section_diff.reloc_diff
|
||||
Self {
|
||||
name: section.name.to_string(),
|
||||
kind: SectionKind::from(section.kind) as i32,
|
||||
size: section.size,
|
||||
address: section.address,
|
||||
functions,
|
||||
symbols,
|
||||
data,
|
||||
match_percent: section_diff.match_percent,
|
||||
}
|
||||
@@ -64,13 +66,22 @@ impl From<ObjSectionKind> for SectionKind {
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionDiff {
|
||||
impl From<obj::SymbolRef> for SymbolRef {
|
||||
fn from(value: obj::SymbolRef) -> Self {
|
||||
Self {
|
||||
section_index: if value.section_idx == obj::SECTION_COMMON {
|
||||
None
|
||||
} else {
|
||||
Some(value.section_idx as u32)
|
||||
},
|
||||
symbol_index: value.symbol_idx as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SymbolDiff {
|
||||
pub fn new(object: &ObjInfo, symbol_diff: &ObjSymbolDiff) -> Self {
|
||||
let (_section, symbol) = object.section_symbol(symbol_diff.symbol_ref);
|
||||
// let diff_symbol = symbol_diff.diff_symbol.map(|symbol_ref| {
|
||||
// let (_section, symbol) = object.section_symbol(symbol_ref);
|
||||
// Symbol::from(symbol)
|
||||
// });
|
||||
let instructions = symbol_diff
|
||||
.instructions
|
||||
.iter()
|
||||
@@ -78,9 +89,9 @@ impl FunctionDiff {
|
||||
.collect();
|
||||
Self {
|
||||
symbol: Some(Symbol::new(symbol)),
|
||||
// diff_symbol,
|
||||
instructions,
|
||||
match_percent: symbol_diff.match_percent,
|
||||
target: symbol_diff.target_symbol.map(SymbolRef::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,7 +121,7 @@ impl Symbol {
|
||||
fn symbol_flags(value: ObjSymbolFlagSet) -> u32 {
|
||||
let mut flags = 0u32;
|
||||
if value.0.contains(ObjSymbolFlags::Global) {
|
||||
flags |= SymbolFlag::SymbolNone as u32;
|
||||
flags |= SymbolFlag::SymbolGlobal as u32;
|
||||
}
|
||||
if value.0.contains(ObjSymbolFlags::Local) {
|
||||
flags |= SymbolFlag::SymbolLocal as u32;
|
||||
|
||||
@@ -13,20 +13,22 @@ fn parse_object(
|
||||
fn parse_and_run_diff(
|
||||
left: Option<Box<[u8]>>,
|
||||
right: Option<Box<[u8]>>,
|
||||
config: diff::DiffObjConfig,
|
||||
diff_config: diff::DiffObjConfig,
|
||||
mapping_config: diff::MappingConfig,
|
||||
) -> Result<DiffResult, JsError> {
|
||||
let target = parse_object(left, &config)?;
|
||||
let base = parse_object(right, &config)?;
|
||||
run_diff(target.as_ref(), base.as_ref(), config)
|
||||
let target = parse_object(left, &diff_config)?;
|
||||
let base = parse_object(right, &diff_config)?;
|
||||
run_diff(target.as_ref(), base.as_ref(), diff_config, mapping_config)
|
||||
}
|
||||
|
||||
fn run_diff(
|
||||
left: Option<&obj::ObjInfo>,
|
||||
right: Option<&obj::ObjInfo>,
|
||||
config: diff::DiffObjConfig,
|
||||
diff_config: diff::DiffObjConfig,
|
||||
mapping_config: diff::MappingConfig,
|
||||
) -> Result<DiffResult, JsError> {
|
||||
log::debug!("Running diff with config: {:?}", config);
|
||||
let result = diff::diff_objs(&config, left, right, None).to_js()?;
|
||||
log::debug!("Running diff with config: {:?}", diff_config);
|
||||
let result = diff::diff_objs(&diff_config, &mapping_config, left, right, None).to_js()?;
|
||||
let left = left.and_then(|o| result.left.as_ref().map(|d| (o, d)));
|
||||
let right = right.and_then(|o| result.right.as_ref().map(|d| (o, d)));
|
||||
Ok(DiffResult::new(left, right))
|
||||
@@ -46,9 +48,10 @@ fn run_diff(
|
||||
pub fn run_diff_proto(
|
||||
left: Option<Box<[u8]>>,
|
||||
right: Option<Box<[u8]>>,
|
||||
config: diff::DiffObjConfig,
|
||||
diff_config: diff::DiffObjConfig,
|
||||
mapping_config: diff::MappingConfig,
|
||||
) -> Result<Box<[u8]>, JsError> {
|
||||
let out = parse_and_run_diff(left, right, config)?;
|
||||
let out = parse_and_run_diff(left, right, diff_config, mapping_config)?;
|
||||
Ok(out.encode_to_vec().into_boxed_slice())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fs,
|
||||
fs::File,
|
||||
io::{BufReader, BufWriter, Read},
|
||||
@@ -6,11 +7,11 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use bimap::BiBTreeMap;
|
||||
use filetime::FileTime;
|
||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||
|
||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
pub struct ProjectConfig {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub min_version: Option<String>,
|
||||
@@ -27,7 +28,7 @@ pub struct ProjectConfig {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub build_target: Option<bool>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub watch_patterns: Option<Vec<Glob>>,
|
||||
pub watch_patterns: Option<Vec<String>>,
|
||||
#[serde(default, alias = "objects", skip_serializing_if = "Option::is_none")]
|
||||
pub units: Option<Vec<ProjectObject>>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -52,9 +53,21 @@ impl ProjectConfig {
|
||||
pub fn progress_categories_mut(&mut self) -> &mut Vec<ProjectProgressCategory> {
|
||||
self.progress_categories.get_or_insert_with(Vec::new)
|
||||
}
|
||||
|
||||
pub fn build_watch_patterns(&self) -> Result<Vec<Glob>, globset::Error> {
|
||||
Ok(if let Some(watch_patterns) = &self.watch_patterns {
|
||||
watch_patterns
|
||||
.iter()
|
||||
.map(|s| Glob::new(s))
|
||||
.collect::<Result<Vec<Glob>, globset::Error>>()?
|
||||
} else {
|
||||
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
pub struct ProjectObject {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
@@ -78,9 +91,11 @@ pub struct ProjectObject {
|
||||
pub symbol_mappings: Option<SymbolMappings>,
|
||||
}
|
||||
|
||||
pub type SymbolMappings = BiBTreeMap<String, String>;
|
||||
#[cfg_attr(feature = "wasm", tsify_next::declare)]
|
||||
pub type SymbolMappings = BTreeMap<String, String>;
|
||||
|
||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
pub struct ProjectObjectMetadata {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub complete: Option<bool>,
|
||||
@@ -95,6 +110,7 @@ pub struct ProjectObjectMetadata {
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
pub struct ProjectProgressCategory {
|
||||
#[serde(default)]
|
||||
pub id: String,
|
||||
@@ -154,6 +170,7 @@ impl ProjectObject {
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
pub struct ScratchConfig {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub platform: Option<String>,
|
||||
|
||||
@@ -3,13 +3,17 @@ use std::{cmp::max, collections::BTreeMap};
|
||||
use anyhow::{anyhow, Result};
|
||||
use similar::{capture_diff_slices_deadline, Algorithm};
|
||||
|
||||
use super::FunctionRelocDiffs;
|
||||
use crate::{
|
||||
arch::ProcessCodeResult,
|
||||
diff::{
|
||||
DiffObjConfig, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind,
|
||||
ObjSymbolDiff,
|
||||
},
|
||||
obj::{ObjInfo, ObjInsArg, ObjReloc, ObjSection, ObjSymbol, ObjSymbolFlags, SymbolRef},
|
||||
obj::{
|
||||
ObjInfo, ObjIns, ObjInsArg, ObjReloc, ObjSection, ObjSymbol, ObjSymbolFlags, ObjSymbolKind,
|
||||
SymbolRef,
|
||||
},
|
||||
};
|
||||
|
||||
pub fn process_code_symbol(
|
||||
@@ -192,7 +196,7 @@ fn address_eq(left: &ObjReloc, right: &ObjReloc) -> bool {
|
||||
left.target.address as i64 + left.addend == right.target.address as i64 + right.addend
|
||||
}
|
||||
|
||||
fn section_name_eq(
|
||||
pub fn section_name_eq(
|
||||
left_obj: &ObjInfo,
|
||||
right_obj: &ObjInfo,
|
||||
left_orig_section_index: usize,
|
||||
@@ -215,16 +219,19 @@ fn reloc_eq(
|
||||
config: &DiffObjConfig,
|
||||
left_obj: &ObjInfo,
|
||||
right_obj: &ObjInfo,
|
||||
left_reloc: Option<&ObjReloc>,
|
||||
right_reloc: Option<&ObjReloc>,
|
||||
left_ins: Option<&ObjIns>,
|
||||
right_ins: Option<&ObjIns>,
|
||||
) -> bool {
|
||||
let (Some(left), Some(right)) = (left_reloc, right_reloc) else {
|
||||
let (Some(left_ins), Some(right_ins)) = (left_ins, right_ins) else {
|
||||
return false;
|
||||
};
|
||||
let (Some(left), Some(right)) = (&left_ins.reloc, &right_ins.reloc) else {
|
||||
return false;
|
||||
};
|
||||
if left.flags != right.flags {
|
||||
return false;
|
||||
}
|
||||
if config.relax_reloc_diffs {
|
||||
if config.function_reloc_diffs == FunctionRelocDiffs::None {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -233,7 +240,13 @@ fn reloc_eq(
|
||||
(Some(sl), Some(sr)) => {
|
||||
// Match if section and name or address match
|
||||
section_name_eq(left_obj, right_obj, *sl, *sr)
|
||||
&& (symbol_name_matches || address_eq(left, right))
|
||||
&& (config.function_reloc_diffs == FunctionRelocDiffs::DataValue
|
||||
|| symbol_name_matches
|
||||
|| address_eq(left, right))
|
||||
&& (config.function_reloc_diffs == FunctionRelocDiffs::NameAddress
|
||||
|| left.target.kind != ObjSymbolKind::Object
|
||||
|| left_obj.arch.display_ins_data(left_ins)
|
||||
== left_obj.arch.display_ins_data(right_ins))
|
||||
}
|
||||
(Some(_), None) => false,
|
||||
(None, Some(_)) => {
|
||||
@@ -259,10 +272,10 @@ fn arg_eq(
|
||||
_ => false,
|
||||
},
|
||||
ObjInsArg::Arg(l) => match right {
|
||||
ObjInsArg::Arg(r) => l == r,
|
||||
ObjInsArg::Arg(r) => l.loose_eq(r),
|
||||
// If relocations are relaxed, match if left is a constant and right is a reloc
|
||||
// Useful for instances where the target object is created without relocations
|
||||
ObjInsArg::Reloc => config.relax_reloc_diffs,
|
||||
ObjInsArg::Reloc => config.function_reloc_diffs == FunctionRelocDiffs::None,
|
||||
_ => false,
|
||||
},
|
||||
ObjInsArg::Reloc => {
|
||||
@@ -271,8 +284,8 @@ fn arg_eq(
|
||||
config,
|
||||
left_obj,
|
||||
right_obj,
|
||||
left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
||||
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
||||
left_diff.ins.as_ref(),
|
||||
right_diff.ins.as_ref(),
|
||||
)
|
||||
}
|
||||
ObjInsArg::BranchDest(_) => match right {
|
||||
@@ -283,7 +296,7 @@ fn arg_eq(
|
||||
}
|
||||
// If relocations are relaxed, match if left is a constant and right is a reloc
|
||||
// Useful for instances where the target object is created without relocations
|
||||
ObjInsArg::Reloc => config.relax_reloc_diffs,
|
||||
ObjInsArg::Reloc => config.function_reloc_diffs == FunctionRelocDiffs::None,
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
use std::cmp::{max, min, Ordering};
|
||||
use std::{
|
||||
cmp::{max, min, Ordering},
|
||||
ops::Range,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use similar::{capture_diff_slices_deadline, get_diff_ratio, Algorithm};
|
||||
|
||||
use super::code::section_name_eq;
|
||||
use crate::{
|
||||
diff::{ObjDataDiff, ObjDataDiffKind, ObjSectionDiff, ObjSymbolDiff},
|
||||
obj::{ObjInfo, ObjSection, SymbolRef},
|
||||
diff::{ObjDataDiff, ObjDataDiffKind, ObjDataRelocDiff, ObjSectionDiff, ObjSymbolDiff},
|
||||
obj::{ObjInfo, ObjReloc, ObjSection, ObjSymbolFlags, SymbolRef},
|
||||
};
|
||||
|
||||
pub fn diff_bss_symbol(
|
||||
@@ -37,8 +41,110 @@ pub fn no_diff_symbol(_obj: &ObjInfo, symbol_ref: SymbolRef) -> ObjSymbolDiff {
|
||||
ObjSymbolDiff { symbol_ref, target_symbol: None, instructions: vec![], match_percent: None }
|
||||
}
|
||||
|
||||
fn address_eq(left: &ObjReloc, right: &ObjReloc) -> bool {
|
||||
if right.target.size == 0 && left.target.size != 0 {
|
||||
// The base relocation is against a pool but the target relocation isn't.
|
||||
// This can happen in rare cases where the compiler will generate a pool+addend relocation
|
||||
// in the base, but the one detected in the target is direct with no addend.
|
||||
// Just check that the final address is the same so these count as a match.
|
||||
left.target.address as i64 + left.addend == right.target.address as i64 + right.addend
|
||||
} else {
|
||||
// But otherwise, if the compiler isn't using a pool, we're more strict and check that the
|
||||
// target symbol address and relocation addend both match exactly.
|
||||
left.target.address == right.target.address && left.addend == right.addend
|
||||
}
|
||||
}
|
||||
|
||||
fn reloc_eq(left_obj: &ObjInfo, right_obj: &ObjInfo, left: &ObjReloc, right: &ObjReloc) -> bool {
|
||||
if left.flags != right.flags {
|
||||
return false;
|
||||
}
|
||||
|
||||
let symbol_name_matches = left.target.name == right.target.name;
|
||||
match (&left.target.orig_section_index, &right.target.orig_section_index) {
|
||||
(Some(sl), Some(sr)) => {
|
||||
// Match if section and name+addend or address match
|
||||
section_name_eq(left_obj, right_obj, *sl, *sr)
|
||||
&& ((symbol_name_matches && left.addend == right.addend) || address_eq(left, right))
|
||||
}
|
||||
(Some(_), None) => false,
|
||||
(None, Some(_)) => {
|
||||
// Match if possibly stripped weak symbol
|
||||
(symbol_name_matches && left.addend == right.addend)
|
||||
&& right.target.flags.0.contains(ObjSymbolFlags::Weak)
|
||||
}
|
||||
(None, None) => symbol_name_matches,
|
||||
}
|
||||
}
|
||||
|
||||
/// Compares relocations contained with a certain data range.
|
||||
/// The ObjDataDiffKind for each diff will either be `None`` (if the relocation matches),
|
||||
/// or `Replace` (if a relocation was changed, added, or removed).
|
||||
/// `Insert` and `Delete` are not used when a relocation is added or removed to avoid confusing diffs
|
||||
/// where it looks like the bytes themselves were changed but actually only the relocations changed.
|
||||
fn diff_data_relocs_for_range(
|
||||
left_obj: &ObjInfo,
|
||||
right_obj: &ObjInfo,
|
||||
left: &ObjSection,
|
||||
right: &ObjSection,
|
||||
left_range: Range<usize>,
|
||||
right_range: Range<usize>,
|
||||
) -> Vec<(ObjDataDiffKind, Option<ObjReloc>, Option<ObjReloc>)> {
|
||||
let mut diffs = Vec::new();
|
||||
for left_reloc in left.relocations.iter() {
|
||||
if !left_range.contains(&(left_reloc.address as usize)) {
|
||||
continue;
|
||||
}
|
||||
let left_offset = left_reloc.address as usize - left_range.start;
|
||||
let Some(right_reloc) = right.relocations.iter().find(|r| {
|
||||
if !right_range.contains(&(r.address as usize)) {
|
||||
return false;
|
||||
}
|
||||
let right_offset = r.address as usize - right_range.start;
|
||||
right_offset == left_offset
|
||||
}) else {
|
||||
diffs.push((ObjDataDiffKind::Delete, Some(left_reloc.clone()), None));
|
||||
continue;
|
||||
};
|
||||
if reloc_eq(left_obj, right_obj, left_reloc, right_reloc) {
|
||||
diffs.push((
|
||||
ObjDataDiffKind::None,
|
||||
Some(left_reloc.clone()),
|
||||
Some(right_reloc.clone()),
|
||||
));
|
||||
} else {
|
||||
diffs.push((
|
||||
ObjDataDiffKind::Replace,
|
||||
Some(left_reloc.clone()),
|
||||
Some(right_reloc.clone()),
|
||||
));
|
||||
}
|
||||
}
|
||||
for right_reloc in right.relocations.iter() {
|
||||
if !right_range.contains(&(right_reloc.address as usize)) {
|
||||
continue;
|
||||
}
|
||||
let right_offset = right_reloc.address as usize - right_range.start;
|
||||
let Some(_) = left.relocations.iter().find(|r| {
|
||||
if !left_range.contains(&(r.address as usize)) {
|
||||
return false;
|
||||
}
|
||||
let left_offset = r.address as usize - left_range.start;
|
||||
left_offset == right_offset
|
||||
}) else {
|
||||
diffs.push((ObjDataDiffKind::Insert, None, Some(right_reloc.clone())));
|
||||
continue;
|
||||
};
|
||||
// No need to check the cases for relocations being deleted or matching again.
|
||||
// They were already handled in the loop over the left relocs.
|
||||
}
|
||||
diffs
|
||||
}
|
||||
|
||||
/// Compare the data sections of two object files.
|
||||
pub fn diff_data_section(
|
||||
left_obj: &ObjInfo,
|
||||
right_obj: &ObjInfo,
|
||||
left: &ObjSection,
|
||||
right: &ObjSection,
|
||||
left_section_diff: &ObjSectionDiff,
|
||||
@@ -121,17 +227,45 @@ pub fn diff_data_section(
|
||||
}
|
||||
}
|
||||
|
||||
let mut left_reloc_diffs = Vec::new();
|
||||
let mut right_reloc_diffs = Vec::new();
|
||||
for (diff_kind, left_reloc, right_reloc) in diff_data_relocs_for_range(
|
||||
left_obj,
|
||||
right_obj,
|
||||
left,
|
||||
right,
|
||||
0..left_max as usize,
|
||||
0..right_max as usize,
|
||||
) {
|
||||
if let Some(left_reloc) = left_reloc {
|
||||
let len = left_obj.arch.get_reloc_byte_size(left_reloc.flags);
|
||||
let range = left_reloc.address as usize..left_reloc.address as usize + len;
|
||||
left_reloc_diffs.push(ObjDataRelocDiff { reloc: left_reloc, kind: diff_kind, range });
|
||||
}
|
||||
if let Some(right_reloc) = right_reloc {
|
||||
let len = right_obj.arch.get_reloc_byte_size(right_reloc.flags);
|
||||
let range = right_reloc.address as usize..right_reloc.address as usize + len;
|
||||
right_reloc_diffs.push(ObjDataRelocDiff { reloc: right_reloc, kind: diff_kind, range });
|
||||
}
|
||||
}
|
||||
|
||||
let (mut left_section_diff, mut right_section_diff) =
|
||||
diff_generic_section(left, right, left_section_diff, right_section_diff)?;
|
||||
let all_left_relocs_match = left_reloc_diffs.iter().all(|d| d.kind == ObjDataDiffKind::None);
|
||||
left_section_diff.data_diff = left_diff;
|
||||
right_section_diff.data_diff = right_diff;
|
||||
left_section_diff.reloc_diff = left_reloc_diffs;
|
||||
right_section_diff.reloc_diff = right_reloc_diffs;
|
||||
if all_left_relocs_match {
|
||||
// Use the highest match percent between two options:
|
||||
// - Left symbols matching right symbols by name
|
||||
// - Diff of the data itself
|
||||
// We only do this when all relocations on the left side match.
|
||||
if left_section_diff.match_percent.unwrap_or(-1.0) < match_percent {
|
||||
left_section_diff.match_percent = Some(match_percent);
|
||||
right_section_diff.match_percent = Some(match_percent);
|
||||
}
|
||||
}
|
||||
Ok((left_section_diff, right_section_diff))
|
||||
}
|
||||
|
||||
@@ -147,13 +281,54 @@ pub fn diff_data_symbol(
|
||||
let left_section = left_section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
|
||||
let right_section = right_section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
|
||||
|
||||
let left_data = &left_section.data[left_symbol.section_address as usize
|
||||
..(left_symbol.section_address + left_symbol.size) as usize];
|
||||
let right_data = &right_section.data[right_symbol.section_address as usize
|
||||
..(right_symbol.section_address + right_symbol.size) as usize];
|
||||
let left_range = left_symbol.section_address as usize
|
||||
..(left_symbol.section_address + left_symbol.size) as usize;
|
||||
let right_range = right_symbol.section_address as usize
|
||||
..(right_symbol.section_address + right_symbol.size) as usize;
|
||||
let left_data = &left_section.data[left_range.clone()];
|
||||
let right_data = &right_section.data[right_range.clone()];
|
||||
|
||||
let reloc_diffs = diff_data_relocs_for_range(
|
||||
left_obj,
|
||||
right_obj,
|
||||
left_section,
|
||||
right_section,
|
||||
left_range,
|
||||
right_range,
|
||||
);
|
||||
|
||||
let ops = capture_diff_slices_deadline(Algorithm::Patience, left_data, right_data, None);
|
||||
let match_percent = get_diff_ratio(&ops, left_data.len(), right_data.len()) * 100.0;
|
||||
let bytes_match_ratio = get_diff_ratio(&ops, left_data.len(), right_data.len());
|
||||
|
||||
let mut match_ratio = bytes_match_ratio;
|
||||
if !reloc_diffs.is_empty() {
|
||||
let mut total_reloc_bytes = 0;
|
||||
let mut matching_reloc_bytes = 0;
|
||||
for (diff_kind, left_reloc, right_reloc) in reloc_diffs {
|
||||
let reloc_diff_len = match (left_reloc, right_reloc) {
|
||||
(None, None) => unreachable!(),
|
||||
(None, Some(right_reloc)) => right_obj.arch.get_reloc_byte_size(right_reloc.flags),
|
||||
(Some(left_reloc), _) => left_obj.arch.get_reloc_byte_size(left_reloc.flags),
|
||||
};
|
||||
total_reloc_bytes += reloc_diff_len;
|
||||
if diff_kind == ObjDataDiffKind::None {
|
||||
matching_reloc_bytes += reloc_diff_len;
|
||||
}
|
||||
}
|
||||
if total_reloc_bytes > 0 {
|
||||
let relocs_match_ratio = matching_reloc_bytes as f32 / total_reloc_bytes as f32;
|
||||
// Adjust the overall match ratio to include relocation differences.
|
||||
// We calculate it so that bytes that contain a relocation are counted twice: once for the
|
||||
// byte's raw value, and once for its relocation.
|
||||
// e.g. An 8 byte symbol that has 8 matching raw bytes and a single 4 byte relocation that
|
||||
// doesn't match would show as 66% (weighted average of 100% and 0%).
|
||||
match_ratio = ((bytes_match_ratio * (left_data.len() as f32))
|
||||
+ (relocs_match_ratio * total_reloc_bytes as f32))
|
||||
/ (left_data.len() + total_reloc_bytes) as f32;
|
||||
}
|
||||
}
|
||||
|
||||
let match_percent = match_ratio * 100.0;
|
||||
|
||||
Ok((
|
||||
ObjSymbolDiff {
|
||||
@@ -190,8 +365,18 @@ pub fn diff_generic_section(
|
||||
/ left.size as f32
|
||||
};
|
||||
Ok((
|
||||
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
||||
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
||||
ObjSectionDiff {
|
||||
symbols: vec![],
|
||||
data_diff: vec![],
|
||||
reloc_diff: vec![],
|
||||
match_percent: Some(match_percent),
|
||||
},
|
||||
ObjSectionDiff {
|
||||
symbols: vec![],
|
||||
data_diff: vec![],
|
||||
reloc_diff: vec![],
|
||||
match_percent: Some(match_percent),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
@@ -216,7 +401,17 @@ pub fn diff_bss_section(
|
||||
}
|
||||
|
||||
Ok((
|
||||
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
||||
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
||||
ObjSectionDiff {
|
||||
symbols: vec![],
|
||||
data_diff: vec![],
|
||||
reloc_diff: vec![],
|
||||
match_percent: Some(match_percent),
|
||||
},
|
||||
ObjSectionDiff {
|
||||
symbols: vec![],
|
||||
data_diff: vec![],
|
||||
reloc_diff: vec![],
|
||||
match_percent: Some(match_percent),
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::HashSet;
|
||||
use std::{collections::HashSet, ops::Range};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
@@ -11,195 +11,16 @@ use crate::{
|
||||
diff_generic_section, no_diff_symbol,
|
||||
},
|
||||
},
|
||||
obj::{ObjInfo, ObjIns, ObjSection, ObjSectionKind, ObjSymbol, SymbolRef, SECTION_COMMON},
|
||||
obj::{
|
||||
ObjInfo, ObjIns, ObjReloc, ObjSection, ObjSectionKind, ObjSymbol, SymbolRef, SECTION_COMMON,
|
||||
},
|
||||
};
|
||||
|
||||
pub mod code;
|
||||
pub mod data;
|
||||
pub mod display;
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Copy,
|
||||
Clone,
|
||||
Default,
|
||||
Eq,
|
||||
PartialEq,
|
||||
serde::Deserialize,
|
||||
serde::Serialize,
|
||||
strum::VariantArray,
|
||||
strum::EnumMessage,
|
||||
)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
|
||||
pub enum X86Formatter {
|
||||
#[default]
|
||||
#[strum(message = "Intel (default)")]
|
||||
Intel,
|
||||
#[strum(message = "AT&T")]
|
||||
Gas,
|
||||
#[strum(message = "NASM")]
|
||||
Nasm,
|
||||
#[strum(message = "MASM")]
|
||||
Masm,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Copy,
|
||||
Clone,
|
||||
Default,
|
||||
Eq,
|
||||
PartialEq,
|
||||
serde::Deserialize,
|
||||
serde::Serialize,
|
||||
strum::VariantArray,
|
||||
strum::EnumMessage,
|
||||
)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
|
||||
pub enum MipsAbi {
|
||||
#[default]
|
||||
#[strum(message = "Auto (default)")]
|
||||
Auto,
|
||||
#[strum(message = "O32")]
|
||||
O32,
|
||||
#[strum(message = "N32")]
|
||||
N32,
|
||||
#[strum(message = "N64")]
|
||||
N64,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Copy,
|
||||
Clone,
|
||||
Default,
|
||||
Eq,
|
||||
PartialEq,
|
||||
serde::Deserialize,
|
||||
serde::Serialize,
|
||||
strum::VariantArray,
|
||||
strum::EnumMessage,
|
||||
)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
|
||||
pub enum MipsInstrCategory {
|
||||
#[default]
|
||||
#[strum(message = "Auto (default)")]
|
||||
Auto,
|
||||
#[strum(message = "CPU")]
|
||||
Cpu,
|
||||
#[strum(message = "RSP (N64)")]
|
||||
Rsp,
|
||||
#[strum(message = "R3000 GTE (PS1)")]
|
||||
R3000Gte,
|
||||
#[strum(message = "R4000 ALLEGREX (PSP)")]
|
||||
R4000Allegrex,
|
||||
#[strum(message = "R5900 EE (PS2)")]
|
||||
R5900,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Copy,
|
||||
Clone,
|
||||
Default,
|
||||
Eq,
|
||||
PartialEq,
|
||||
serde::Deserialize,
|
||||
serde::Serialize,
|
||||
strum::VariantArray,
|
||||
strum::EnumMessage,
|
||||
)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
|
||||
pub enum ArmArchVersion {
|
||||
#[default]
|
||||
#[strum(message = "Auto (default)")]
|
||||
Auto,
|
||||
#[strum(message = "ARMv4T (GBA)")]
|
||||
V4T,
|
||||
#[strum(message = "ARMv5TE (DS)")]
|
||||
V5TE,
|
||||
#[strum(message = "ARMv6K (3DS)")]
|
||||
V6K,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Copy,
|
||||
Clone,
|
||||
Default,
|
||||
Eq,
|
||||
PartialEq,
|
||||
serde::Deserialize,
|
||||
serde::Serialize,
|
||||
strum::VariantArray,
|
||||
strum::EnumMessage,
|
||||
)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
|
||||
pub enum ArmR9Usage {
|
||||
#[default]
|
||||
#[strum(
|
||||
message = "R9 or V6 (default)",
|
||||
detailed_message = "Use R9 as a general-purpose register."
|
||||
)]
|
||||
GeneralPurpose,
|
||||
#[strum(
|
||||
message = "SB (static base)",
|
||||
detailed_message = "Used for position-independent data (PID)."
|
||||
)]
|
||||
Sb,
|
||||
#[strum(message = "TR (TLS register)", detailed_message = "Used for thread-local storage.")]
|
||||
Tr,
|
||||
}
|
||||
|
||||
#[inline]
|
||||
const fn default_true() -> bool { true }
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
|
||||
#[cfg_attr(feature = "wasm", tsify(from_wasm_abi))]
|
||||
#[serde(default)]
|
||||
pub struct DiffObjConfig {
|
||||
pub relax_reloc_diffs: bool,
|
||||
#[serde(default = "default_true")]
|
||||
pub space_between_args: bool,
|
||||
pub combine_data_sections: bool,
|
||||
#[serde(default)]
|
||||
pub symbol_mappings: MappingConfig,
|
||||
// x86
|
||||
pub x86_formatter: X86Formatter,
|
||||
// MIPS
|
||||
pub mips_abi: MipsAbi,
|
||||
pub mips_instr_category: MipsInstrCategory,
|
||||
// ARM
|
||||
pub arm_arch_version: ArmArchVersion,
|
||||
pub arm_unified_syntax: bool,
|
||||
pub arm_av_registers: bool,
|
||||
pub arm_r9_usage: ArmR9Usage,
|
||||
pub arm_sl_usage: bool,
|
||||
pub arm_fp_usage: bool,
|
||||
pub arm_ip_usage: bool,
|
||||
}
|
||||
|
||||
impl Default for DiffObjConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
relax_reloc_diffs: false,
|
||||
space_between_args: true,
|
||||
combine_data_sections: false,
|
||||
symbol_mappings: Default::default(),
|
||||
x86_formatter: Default::default(),
|
||||
mips_abi: Default::default(),
|
||||
mips_instr_category: Default::default(),
|
||||
arm_arch_version: Default::default(),
|
||||
arm_unified_syntax: true,
|
||||
arm_av_registers: false,
|
||||
arm_r9_usage: Default::default(),
|
||||
arm_sl_usage: false,
|
||||
arm_fp_usage: false,
|
||||
arm_ip_usage: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
include!(concat!(env!("OUT_DIR"), "/config.gen.rs"));
|
||||
|
||||
impl DiffObjConfig {
|
||||
pub fn separator(&self) -> &'static str {
|
||||
@@ -215,6 +36,7 @@ impl DiffObjConfig {
|
||||
pub struct ObjSectionDiff {
|
||||
pub symbols: Vec<ObjSymbolDiff>,
|
||||
pub data_diff: Vec<ObjDataDiff>,
|
||||
pub reloc_diff: Vec<ObjDataRelocDiff>,
|
||||
pub match_percent: Option<f32>,
|
||||
}
|
||||
|
||||
@@ -222,6 +44,7 @@ impl ObjSectionDiff {
|
||||
fn merge(&mut self, other: ObjSectionDiff) {
|
||||
// symbols ignored
|
||||
self.data_diff = other.data_diff;
|
||||
self.reloc_diff = other.reloc_diff;
|
||||
self.match_percent = other.match_percent;
|
||||
}
|
||||
}
|
||||
@@ -268,6 +91,13 @@ pub struct ObjDataDiff {
|
||||
pub symbol: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ObjDataRelocDiff {
|
||||
pub reloc: ObjReloc,
|
||||
pub kind: ObjDataDiffKind,
|
||||
pub range: Range<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
|
||||
pub enum ObjDataDiffKind {
|
||||
#[default]
|
||||
@@ -335,6 +165,7 @@ impl ObjDiff {
|
||||
len: section.data.len(),
|
||||
symbol: section.name.clone(),
|
||||
}],
|
||||
reloc_diff: vec![],
|
||||
match_percent: None,
|
||||
});
|
||||
}
|
||||
@@ -386,12 +217,13 @@ pub struct DiffObjsResult {
|
||||
}
|
||||
|
||||
pub fn diff_objs(
|
||||
config: &DiffObjConfig,
|
||||
diff_config: &DiffObjConfig,
|
||||
mapping_config: &MappingConfig,
|
||||
left: Option<&ObjInfo>,
|
||||
right: Option<&ObjInfo>,
|
||||
prev: Option<&ObjInfo>,
|
||||
) -> Result<DiffObjsResult> {
|
||||
let symbol_matches = matching_symbols(left, right, prev, &config.symbol_mappings)?;
|
||||
let symbol_matches = matching_symbols(left, right, prev, mapping_config)?;
|
||||
let section_matches = matching_sections(left, right)?;
|
||||
let mut left = left.map(|p| (p, ObjDiff::new_from_obj(p)));
|
||||
let mut right = right.map(|p| (p, ObjDiff::new_from_obj(p)));
|
||||
@@ -409,8 +241,10 @@ pub fn diff_objs(
|
||||
let (right_obj, right_out) = right.as_mut().unwrap();
|
||||
match section_kind {
|
||||
ObjSectionKind::Code => {
|
||||
let left_code = process_code_symbol(left_obj, left_symbol_ref, config)?;
|
||||
let right_code = process_code_symbol(right_obj, right_symbol_ref, config)?;
|
||||
let left_code =
|
||||
process_code_symbol(left_obj, left_symbol_ref, diff_config)?;
|
||||
let right_code =
|
||||
process_code_symbol(right_obj, right_symbol_ref, diff_config)?;
|
||||
let (left_diff, right_diff) = diff_code(
|
||||
left_obj,
|
||||
right_obj,
|
||||
@@ -418,14 +252,15 @@ pub fn diff_objs(
|
||||
&right_code,
|
||||
left_symbol_ref,
|
||||
right_symbol_ref,
|
||||
config,
|
||||
diff_config,
|
||||
)?;
|
||||
*left_out.symbol_diff_mut(left_symbol_ref) = left_diff;
|
||||
*right_out.symbol_diff_mut(right_symbol_ref) = right_diff;
|
||||
|
||||
if let Some(prev_symbol_ref) = prev_symbol_ref {
|
||||
let (prev_obj, prev_out) = prev.as_mut().unwrap();
|
||||
let prev_code = process_code_symbol(prev_obj, prev_symbol_ref, config)?;
|
||||
let prev_code =
|
||||
process_code_symbol(prev_obj, prev_symbol_ref, diff_config)?;
|
||||
let (_, prev_diff) = diff_code(
|
||||
left_obj,
|
||||
right_obj,
|
||||
@@ -433,7 +268,7 @@ pub fn diff_objs(
|
||||
&prev_code,
|
||||
right_symbol_ref,
|
||||
prev_symbol_ref,
|
||||
config,
|
||||
diff_config,
|
||||
)?;
|
||||
*prev_out.symbol_diff_mut(prev_symbol_ref) = prev_diff;
|
||||
}
|
||||
@@ -464,7 +299,7 @@ pub fn diff_objs(
|
||||
let (left_obj, left_out) = left.as_mut().unwrap();
|
||||
match section_kind {
|
||||
ObjSectionKind::Code => {
|
||||
let code = process_code_symbol(left_obj, left_symbol_ref, config)?;
|
||||
let code = process_code_symbol(left_obj, left_symbol_ref, diff_config)?;
|
||||
*left_out.symbol_diff_mut(left_symbol_ref) =
|
||||
no_diff_code(&code, left_symbol_ref)?;
|
||||
}
|
||||
@@ -478,7 +313,7 @@ pub fn diff_objs(
|
||||
let (right_obj, right_out) = right.as_mut().unwrap();
|
||||
match section_kind {
|
||||
ObjSectionKind::Code => {
|
||||
let code = process_code_symbol(right_obj, right_symbol_ref, config)?;
|
||||
let code = process_code_symbol(right_obj, right_symbol_ref, diff_config)?;
|
||||
*right_out.symbol_diff_mut(right_symbol_ref) =
|
||||
no_diff_code(&code, right_symbol_ref)?;
|
||||
}
|
||||
@@ -522,6 +357,8 @@ pub fn diff_objs(
|
||||
let left_section_diff = left_out.section_diff(left_section_idx);
|
||||
let right_section_diff = right_out.section_diff(right_section_idx);
|
||||
let (left_diff, right_diff) = diff_data_section(
|
||||
left_obj,
|
||||
right_obj,
|
||||
left_section,
|
||||
right_section,
|
||||
left_section_diff,
|
||||
@@ -549,11 +386,11 @@ pub fn diff_objs(
|
||||
if let (Some((right_obj, right_out)), Some((left_obj, left_out))) =
|
||||
(right.as_mut(), left.as_mut())
|
||||
{
|
||||
if let Some(right_name) = &config.symbol_mappings.selecting_left {
|
||||
generate_mapping_symbols(right_obj, right_name, left_obj, left_out, config)?;
|
||||
if let Some(right_name) = &mapping_config.selecting_left {
|
||||
generate_mapping_symbols(right_obj, right_name, left_obj, left_out, diff_config)?;
|
||||
}
|
||||
if let Some(left_name) = &config.symbol_mappings.selecting_right {
|
||||
generate_mapping_symbols(left_obj, left_name, right_obj, right_out, config)?;
|
||||
if let Some(left_name) = &mapping_config.selecting_right {
|
||||
generate_mapping_symbols(left_obj, left_name, right_obj, right_out, diff_config)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -636,7 +473,9 @@ struct SectionMatch {
|
||||
section_kind: ObjSectionKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default, serde::Deserialize, serde::Serialize)]
|
||||
#[derive(Debug, Clone, Default, serde::Deserialize, serde::Serialize)]
|
||||
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
|
||||
#[serde(default)]
|
||||
pub struct MappingConfig {
|
||||
/// Manual symbol mappings
|
||||
pub mappings: SymbolMappings,
|
||||
|
||||
@@ -5,7 +5,6 @@ use time::OffsetDateTime;
|
||||
|
||||
use crate::{
|
||||
build::{run_make, BuildConfig, BuildStatus},
|
||||
config::SymbolMappings,
|
||||
diff::{diff_objs, DiffObjConfig, MappingConfig, ObjDiff},
|
||||
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState},
|
||||
obj::{read, ObjInfo},
|
||||
@@ -18,9 +17,7 @@ pub struct ObjDiffConfig {
|
||||
pub target_path: Option<PathBuf>,
|
||||
pub base_path: Option<PathBuf>,
|
||||
pub diff_obj_config: DiffObjConfig,
|
||||
pub symbol_mappings: SymbolMappings,
|
||||
pub selecting_left: Option<String>,
|
||||
pub selecting_right: Option<String>,
|
||||
pub mapping_config: MappingConfig,
|
||||
}
|
||||
|
||||
pub struct ObjDiffResult {
|
||||
@@ -34,15 +31,8 @@ pub struct ObjDiffResult {
|
||||
fn run_build(
|
||||
context: &JobContext,
|
||||
cancel: Receiver<()>,
|
||||
mut config: ObjDiffConfig,
|
||||
config: ObjDiffConfig,
|
||||
) -> Result<Box<ObjDiffResult>> {
|
||||
// Use the per-object symbol mappings, we don't set mappings globally
|
||||
config.diff_obj_config.symbol_mappings = MappingConfig {
|
||||
mappings: config.symbol_mappings,
|
||||
selecting_left: config.selecting_left,
|
||||
selecting_right: config.selecting_right,
|
||||
};
|
||||
|
||||
let mut target_path_rel = None;
|
||||
let mut base_path_rel = None;
|
||||
if config.build_target || config.build_base {
|
||||
@@ -180,7 +170,13 @@ fn run_build(
|
||||
|
||||
update_status(context, "Performing diff".to_string(), step_idx, total, &cancel)?;
|
||||
step_idx += 1;
|
||||
let result = diff_objs(&config.diff_obj_config, first_obj.as_ref(), second_obj.as_ref(), None)?;
|
||||
let result = diff_objs(
|
||||
&config.diff_obj_config,
|
||||
&config.mapping_config,
|
||||
first_obj.as_ref(),
|
||||
second_obj.as_ref(),
|
||||
None,
|
||||
)?;
|
||||
|
||||
update_status(context, "Complete".to_string(), step_idx, total, &cancel)?;
|
||||
Ok(Box::new(ObjDiffResult {
|
||||
|
||||
@@ -28,7 +28,7 @@ flags! {
|
||||
HasExtra,
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
#[derive(Debug, Copy, Clone, Default, PartialEq)]
|
||||
pub struct ObjSymbolFlagSet(pub FlagSet<ObjSymbolFlags>);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -132,7 +132,7 @@ pub enum ObjSymbolKind {
|
||||
Section,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ObjSymbol {
|
||||
pub name: String,
|
||||
pub demangled_name: Option<String>,
|
||||
@@ -161,7 +161,7 @@ pub struct ObjInfo {
|
||||
pub split_meta: Option<SplitMeta>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ObjReloc {
|
||||
pub flags: RelocationFlags,
|
||||
pub address: u64,
|
||||
|
||||
@@ -31,8 +31,8 @@ const_format = "0.2"
|
||||
cwdemangle = "1.0"
|
||||
cwextab = "1.0"
|
||||
dirs = "5.0"
|
||||
egui = "0.29"
|
||||
egui_extras = "0.29"
|
||||
egui = "0.30"
|
||||
egui_extras = "0.30"
|
||||
filetime = "0.2"
|
||||
float-ord = "0.3"
|
||||
font-kit = "0.14"
|
||||
@@ -54,7 +54,7 @@ time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||
|
||||
# Keep version in sync with egui
|
||||
[dependencies.eframe]
|
||||
version = "0.29"
|
||||
version = "0.30"
|
||||
features = [
|
||||
"default_fonts",
|
||||
"persistence",
|
||||
@@ -65,7 +65,7 @@ default-features = false
|
||||
|
||||
# Keep version in sync with eframe
|
||||
[dependencies.wgpu]
|
||||
version = "22.1"
|
||||
version = "23.0"
|
||||
features = [
|
||||
"dx12",
|
||||
"metal",
|
||||
@@ -93,4 +93,4 @@ tracing-wasm = "0.2"
|
||||
anyhow = "1.0"
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
tauri-winres = "0.1"
|
||||
tauri-winres = "0.2"
|
||||
|
||||
@@ -30,7 +30,8 @@ use crate::{
|
||||
views::{
|
||||
appearance::{appearance_window, Appearance},
|
||||
config::{
|
||||
arch_config_window, config_ui, project_window, ConfigViewState, CONFIG_DISABLED_TEXT,
|
||||
arch_config_window, config_ui, general_config_ui, project_window, ConfigViewState,
|
||||
CONFIG_DISABLED_TEXT,
|
||||
},
|
||||
data_diff::data_diff_ui,
|
||||
debug::debug_window,
|
||||
@@ -293,7 +294,7 @@ impl AppState {
|
||||
let Some(object) = self.config.selected_obj.as_mut() else {
|
||||
return;
|
||||
};
|
||||
object.symbol_mappings.remove_by_right(right);
|
||||
object.symbol_mappings.retain(|_, r| r != right);
|
||||
self.selecting_left = Some(right.to_string());
|
||||
self.queue_reload = true;
|
||||
self.save_config();
|
||||
@@ -303,7 +304,7 @@ impl AppState {
|
||||
let Some(object) = self.config.selected_obj.as_mut() else {
|
||||
return;
|
||||
};
|
||||
object.symbol_mappings.remove_by_left(left);
|
||||
object.symbol_mappings.retain(|l, _| l != left);
|
||||
self.selecting_right = Some(left.to_string());
|
||||
self.queue_reload = true;
|
||||
self.save_config();
|
||||
@@ -316,10 +317,8 @@ impl AppState {
|
||||
};
|
||||
self.selecting_left = None;
|
||||
self.selecting_right = None;
|
||||
if left == right {
|
||||
object.symbol_mappings.remove_by_left(&left);
|
||||
object.symbol_mappings.remove_by_right(&right);
|
||||
} else {
|
||||
object.symbol_mappings.retain(|l, r| l != &left && r != &right);
|
||||
if left != right {
|
||||
object.symbol_mappings.insert(left.clone(), right.clone());
|
||||
}
|
||||
self.queue_reload = true;
|
||||
@@ -728,37 +727,9 @@ impl eframe::App for App {
|
||||
&mut diff_state.symbol_state.show_hidden_symbols,
|
||||
"Show hidden symbols",
|
||||
);
|
||||
if ui
|
||||
.checkbox(
|
||||
&mut state.config.diff_obj_config.relax_reloc_diffs,
|
||||
"Relax relocation diffs",
|
||||
)
|
||||
.on_hover_text(
|
||||
"Ignores differences in relocation targets. (Address, name, etc)",
|
||||
)
|
||||
.changed()
|
||||
{
|
||||
state.queue_reload = true;
|
||||
}
|
||||
if ui
|
||||
.checkbox(
|
||||
&mut state.config.diff_obj_config.space_between_args,
|
||||
"Space between args",
|
||||
)
|
||||
.changed()
|
||||
{
|
||||
state.queue_reload = true;
|
||||
}
|
||||
if ui
|
||||
.checkbox(
|
||||
&mut state.config.diff_obj_config.combine_data_sections,
|
||||
"Combine data sections",
|
||||
)
|
||||
.on_hover_text("Combines data sections with equal names.")
|
||||
.changed()
|
||||
{
|
||||
state.queue_reload = true;
|
||||
}
|
||||
ui.separator();
|
||||
general_config_ui(ui, &mut state);
|
||||
ui.separator();
|
||||
if ui.button("Clear custom symbol mappings").clicked() {
|
||||
state.clear_mappings();
|
||||
diff_state.post_build_nav = Some(DiffViewNavigation::symbol_diff());
|
||||
|
||||
@@ -3,8 +3,11 @@ use std::path::PathBuf;
|
||||
use eframe::Storage;
|
||||
use globset::Glob;
|
||||
use objdiff_core::{
|
||||
config::ScratchConfig,
|
||||
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig, MipsAbi, MipsInstrCategory, X86Formatter},
|
||||
config::{ScratchConfig, SymbolMappings},
|
||||
diff::{
|
||||
ArmArchVersion, ArmR9Usage, DiffObjConfig, FunctionRelocDiffs, MipsAbi, MipsInstrCategory,
|
||||
X86Formatter,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::app::{AppConfig, ObjectConfig, CONFIG_KEY};
|
||||
@@ -15,7 +18,7 @@ pub struct AppConfigVersion {
|
||||
}
|
||||
|
||||
impl Default for AppConfigVersion {
|
||||
fn default() -> Self { Self { version: 2 } }
|
||||
fn default() -> Self { Self { version: 3 } }
|
||||
}
|
||||
|
||||
/// Deserialize the AppConfig from storage, handling upgrades from older versions.
|
||||
@@ -23,7 +26,8 @@ pub fn deserialize_config(storage: &dyn Storage) -> Option<AppConfig> {
|
||||
let str = storage.get_string(CONFIG_KEY)?;
|
||||
match ron::from_str::<AppConfigVersion>(&str) {
|
||||
Ok(version) => match version.version {
|
||||
2 => from_str::<AppConfig>(&str),
|
||||
3 => from_str::<AppConfig>(&str),
|
||||
2 => from_str::<AppConfigV2>(&str).map(|c| c.into_config()),
|
||||
1 => from_str::<AppConfigV1>(&str).map(|c| c.into_config()),
|
||||
_ => {
|
||||
log::warn!("Unknown config version: {}", version.version);
|
||||
@@ -49,6 +53,119 @@ where T: serde::de::DeserializeOwned {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct ScratchConfigV2 {
|
||||
#[serde(default)]
|
||||
pub platform: Option<String>,
|
||||
#[serde(default)]
|
||||
pub compiler: Option<String>,
|
||||
#[serde(default)]
|
||||
pub c_flags: Option<String>,
|
||||
#[serde(default)]
|
||||
pub ctx_path: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub build_ctx: Option<bool>,
|
||||
#[serde(default)]
|
||||
pub preset_id: Option<u32>,
|
||||
}
|
||||
|
||||
impl ScratchConfigV2 {
|
||||
fn into_config(self) -> ScratchConfig {
|
||||
ScratchConfig {
|
||||
platform: self.platform,
|
||||
compiler: self.compiler,
|
||||
c_flags: self.c_flags,
|
||||
ctx_path: self.ctx_path,
|
||||
build_ctx: self.build_ctx,
|
||||
preset_id: self.preset_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct ObjectConfigV2 {
|
||||
pub name: String,
|
||||
pub target_path: Option<PathBuf>,
|
||||
pub base_path: Option<PathBuf>,
|
||||
pub reverse_fn_order: Option<bool>,
|
||||
pub complete: Option<bool>,
|
||||
pub scratch: Option<ScratchConfigV2>,
|
||||
pub source_path: Option<String>,
|
||||
#[serde(default)]
|
||||
pub symbol_mappings: SymbolMappings,
|
||||
}
|
||||
|
||||
impl ObjectConfigV2 {
|
||||
fn into_config(self) -> ObjectConfig {
|
||||
ObjectConfig {
|
||||
name: self.name,
|
||||
target_path: self.target_path,
|
||||
base_path: self.base_path,
|
||||
reverse_fn_order: self.reverse_fn_order,
|
||||
complete: self.complete,
|
||||
scratch: self.scratch.map(|scratch| scratch.into_config()),
|
||||
source_path: self.source_path,
|
||||
symbol_mappings: self.symbol_mappings,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct AppConfigV2 {
|
||||
pub version: u32,
|
||||
#[serde(default)]
|
||||
pub custom_make: Option<String>,
|
||||
#[serde(default)]
|
||||
pub custom_args: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
pub selected_wsl_distro: Option<String>,
|
||||
#[serde(default)]
|
||||
pub project_dir: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub target_obj_dir: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub base_obj_dir: Option<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub selected_obj: Option<ObjectConfigV2>,
|
||||
#[serde(default = "bool_true")]
|
||||
pub build_base: bool,
|
||||
#[serde(default)]
|
||||
pub build_target: bool,
|
||||
#[serde(default = "bool_true")]
|
||||
pub rebuild_on_changes: bool,
|
||||
#[serde(default)]
|
||||
pub auto_update_check: bool,
|
||||
#[serde(default)]
|
||||
pub watch_patterns: Vec<Glob>,
|
||||
#[serde(default)]
|
||||
pub recent_projects: Vec<PathBuf>,
|
||||
#[serde(default)]
|
||||
pub diff_obj_config: DiffObjConfigV1,
|
||||
}
|
||||
|
||||
impl AppConfigV2 {
|
||||
fn into_config(self) -> AppConfig {
|
||||
log::info!("Upgrading configuration from v2");
|
||||
AppConfig {
|
||||
custom_make: self.custom_make,
|
||||
custom_args: self.custom_args,
|
||||
selected_wsl_distro: self.selected_wsl_distro,
|
||||
project_dir: self.project_dir,
|
||||
target_obj_dir: self.target_obj_dir,
|
||||
base_obj_dir: self.base_obj_dir,
|
||||
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
|
||||
build_base: self.build_base,
|
||||
build_target: self.build_target,
|
||||
rebuild_on_changes: self.rebuild_on_changes,
|
||||
auto_update_check: self.auto_update_check,
|
||||
watch_patterns: self.watch_patterns,
|
||||
recent_projects: self.recent_projects,
|
||||
diff_obj_config: self.diff_obj_config.into_config(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct ScratchConfigV1 {
|
||||
#[serde(default)]
|
||||
@@ -147,7 +264,11 @@ impl Default for DiffObjConfigV1 {
|
||||
impl DiffObjConfigV1 {
|
||||
fn into_config(self) -> DiffObjConfig {
|
||||
DiffObjConfig {
|
||||
relax_reloc_diffs: self.relax_reloc_diffs,
|
||||
function_reloc_diffs: if self.relax_reloc_diffs {
|
||||
FunctionRelocDiffs::None
|
||||
} else {
|
||||
FunctionRelocDiffs::default()
|
||||
},
|
||||
space_between_args: self.space_between_args,
|
||||
combine_data_sections: self.combine_data_sections,
|
||||
x86_formatter: self.x86_formatter,
|
||||
@@ -160,7 +281,6 @@ impl DiffObjConfigV1 {
|
||||
arm_sl_usage: self.arm_sl_usage,
|
||||
arm_fp_usage: self.arm_fp_usage,
|
||||
arm_ip_usage: self.arm_ip_usage,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,9 +99,15 @@ pub fn load_project_config(state: &mut AppState) -> Result<()> {
|
||||
state.config.base_obj_dir = project_config.base_dir.as_deref().map(|p| project_dir.join(p));
|
||||
state.config.build_base = project_config.build_base.unwrap_or(true);
|
||||
state.config.build_target = project_config.build_target.unwrap_or(false);
|
||||
state.config.watch_patterns = project_config.watch_patterns.clone().unwrap_or_else(|| {
|
||||
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
|
||||
});
|
||||
if let Some(watch_patterns) = &project_config.watch_patterns {
|
||||
state.config.watch_patterns = watch_patterns
|
||||
.iter()
|
||||
.map(|s| Glob::new(s))
|
||||
.collect::<Result<Vec<Glob>, globset::Error>>()?;
|
||||
} else {
|
||||
state.config.watch_patterns =
|
||||
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect();
|
||||
}
|
||||
state.watcher_change = true;
|
||||
state.objects = project_config.units.clone().unwrap_or_default();
|
||||
state.object_nodes = build_nodes(
|
||||
|
||||
@@ -96,7 +96,7 @@ pub fn load_font_if_needed(
|
||||
let default_font = family.handles.get(family.default_index).unwrap();
|
||||
let default_font_data = load_font(default_font).unwrap();
|
||||
log::info!("Loaded font family '{}'", family.family_name);
|
||||
fonts.font_data.insert(default_font_ref.full_name(), default_font_data.font_data);
|
||||
fonts.font_data.insert(default_font_ref.full_name(), Arc::new(default_font_data.font_data));
|
||||
fonts
|
||||
.families
|
||||
.entry(egui::FontFamily::Name(Arc::from(family.family_name)))
|
||||
|
||||
@@ -7,6 +7,7 @@ use anyhow::{bail, Result};
|
||||
use jobs::create_scratch;
|
||||
use objdiff_core::{
|
||||
build::BuildConfig,
|
||||
diff::MappingConfig,
|
||||
jobs,
|
||||
jobs::{check_update::CheckUpdateConfig, objdiff, update::UpdateConfig, Job, JobQueue},
|
||||
};
|
||||
@@ -106,7 +107,8 @@ pub fn create_objdiff_config(state: &AppState) -> objdiff::ObjDiffConfig {
|
||||
.and_then(|obj| obj.base_path.as_ref())
|
||||
.cloned(),
|
||||
diff_obj_config: state.config.diff_obj_config.clone(),
|
||||
symbol_mappings: state
|
||||
mapping_config: MappingConfig {
|
||||
mappings: state
|
||||
.config
|
||||
.selected_obj
|
||||
.as_ref()
|
||||
@@ -115,6 +117,7 @@ pub fn create_objdiff_config(state: &AppState) -> objdiff::ObjDiffConfig {
|
||||
.unwrap_or_default(),
|
||||
selecting_left: state.selecting_left.clone(),
|
||||
selecting_right: state.selecting_right.clone(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,15 +88,30 @@ fn main() -> ExitCode {
|
||||
}
|
||||
#[cfg(feature = "wgpu")]
|
||||
{
|
||||
use eframe::egui_wgpu::wgpu::Backends;
|
||||
use eframe::egui_wgpu::{wgpu::Backends, WgpuSetup};
|
||||
if graphics_config.desired_backend.is_supported() {
|
||||
native_options.wgpu_options.supported_backends = match graphics_config.desired_backend {
|
||||
GraphicsBackend::Auto => native_options.wgpu_options.supported_backends,
|
||||
native_options.wgpu_options.wgpu_setup = match native_options.wgpu_options.wgpu_setup {
|
||||
WgpuSetup::CreateNew {
|
||||
supported_backends: backends,
|
||||
power_preference,
|
||||
device_descriptor,
|
||||
} => {
|
||||
let backend = match graphics_config.desired_backend {
|
||||
GraphicsBackend::Auto => backends,
|
||||
GraphicsBackend::Dx12 => Backends::DX12,
|
||||
GraphicsBackend::Metal => Backends::METAL,
|
||||
GraphicsBackend::Vulkan => Backends::VULKAN,
|
||||
GraphicsBackend::OpenGL => Backends::GL,
|
||||
};
|
||||
WgpuSetup::CreateNew {
|
||||
supported_backends: backend,
|
||||
power_preference,
|
||||
device_descriptor,
|
||||
}
|
||||
}
|
||||
// WgpuConfiguration::Default is CreateNew until we call run_eframe()
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
let mut eframe_error = None;
|
||||
@@ -112,6 +127,8 @@ fn main() -> ExitCode {
|
||||
}
|
||||
#[cfg(feature = "wgpu")]
|
||||
if let Some(e) = eframe_error {
|
||||
use eframe::egui_wgpu::WgpuConfiguration;
|
||||
|
||||
// Attempt to relaunch using wgpu auto backend if the desired backend failed
|
||||
#[allow(unused_mut)]
|
||||
let mut should_relaunch = graphics_config.desired_backend != GraphicsBackend::Auto;
|
||||
@@ -123,7 +140,7 @@ fn main() -> ExitCode {
|
||||
if should_relaunch {
|
||||
log::warn!("Failed to launch application: {e:?}");
|
||||
log::warn!("Attempting to relaunch using auto-detected backend");
|
||||
native_options.wgpu_options.supported_backends = Default::default();
|
||||
native_options.wgpu_options.wgpu_setup = WgpuConfiguration::default().wgpu_setup;
|
||||
if let Err(e) = run_eframe(
|
||||
native_options.clone(),
|
||||
utc_offset,
|
||||
|
||||
@@ -14,10 +14,12 @@ use egui::{
|
||||
use globset::Glob;
|
||||
use objdiff_core::{
|
||||
config::{ProjectObject, DEFAULT_WATCH_PATTERNS},
|
||||
diff::{ArmArchVersion, ArmR9Usage, MipsAbi, MipsInstrCategory, X86Formatter},
|
||||
diff::{
|
||||
ConfigEnum, ConfigEnumVariantInfo, ConfigPropertyId, ConfigPropertyKind,
|
||||
ConfigPropertyValue, CONFIG_GROUPS,
|
||||
},
|
||||
jobs::{check_update::CheckUpdateResult, Job, JobQueue, JobResult},
|
||||
};
|
||||
use strum::{EnumMessage, VariantArray};
|
||||
|
||||
use crate::{
|
||||
app::{AppConfig, AppState, AppStateRef, ObjectConfig},
|
||||
@@ -874,121 +876,102 @@ pub fn arch_config_window(
|
||||
});
|
||||
}
|
||||
|
||||
fn config_property_ui(
|
||||
ui: &mut egui::Ui,
|
||||
state: &mut AppState,
|
||||
property_id: ConfigPropertyId,
|
||||
) -> bool {
|
||||
let mut changed = false;
|
||||
let current_value = state.config.diff_obj_config.get_property_value(property_id);
|
||||
match (property_id.kind(), current_value) {
|
||||
(ConfigPropertyKind::Boolean, ConfigPropertyValue::Boolean(mut checked)) => {
|
||||
let mut response = ui.checkbox(&mut checked, property_id.name());
|
||||
if let Some(description) = property_id.description() {
|
||||
response = response.on_hover_text(description);
|
||||
}
|
||||
if response.changed() {
|
||||
state
|
||||
.config
|
||||
.diff_obj_config
|
||||
.set_property_value(property_id, ConfigPropertyValue::Boolean(checked))
|
||||
.expect("Failed to set property value");
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
(ConfigPropertyKind::Choice(variants), ConfigPropertyValue::Choice(selected)) => {
|
||||
fn variant_name(variant: &ConfigEnumVariantInfo) -> String {
|
||||
if variant.is_default {
|
||||
format!("{} (default)", variant.name)
|
||||
} else {
|
||||
variant.name.to_string()
|
||||
}
|
||||
}
|
||||
let selected_variant = variants
|
||||
.iter()
|
||||
.find(|v| v.value == selected)
|
||||
.or_else(|| variants.iter().find(|v| v.is_default))
|
||||
.expect("Invalid choice variant");
|
||||
let response = egui::ComboBox::new(property_id.name(), property_id.name())
|
||||
.selected_text(variant_name(selected_variant))
|
||||
.show_ui(ui, |ui| {
|
||||
for variant in variants {
|
||||
let mut response =
|
||||
ui.selectable_label(selected == variant.value, variant_name(variant));
|
||||
if let Some(description) = variant.description {
|
||||
response = response.on_hover_text(description);
|
||||
}
|
||||
if response.clicked() {
|
||||
state
|
||||
.config
|
||||
.diff_obj_config
|
||||
.set_property_value(
|
||||
property_id,
|
||||
ConfigPropertyValue::Choice(variant.value),
|
||||
)
|
||||
.expect("Failed to set property value");
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
})
|
||||
.response;
|
||||
if let Some(description) = property_id.description() {
|
||||
response.on_hover_text(description);
|
||||
}
|
||||
}
|
||||
_ => panic!("Incompatible property kind and value"),
|
||||
}
|
||||
changed
|
||||
}
|
||||
|
||||
fn arch_config_ui(ui: &mut egui::Ui, state: &mut AppState, _appearance: &Appearance) {
|
||||
ui.heading("x86");
|
||||
egui::ComboBox::new("x86_formatter", "Format")
|
||||
.selected_text(state.config.diff_obj_config.x86_formatter.get_message().unwrap())
|
||||
.show_ui(ui, |ui| {
|
||||
for &formatter in X86Formatter::VARIANTS {
|
||||
if ui
|
||||
.selectable_label(
|
||||
state.config.diff_obj_config.x86_formatter == formatter,
|
||||
formatter.get_message().unwrap(),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
state.config.diff_obj_config.x86_formatter = formatter;
|
||||
state.queue_reload = true;
|
||||
let mut first = true;
|
||||
let mut changed = false;
|
||||
for group in CONFIG_GROUPS {
|
||||
if group.id == "general" {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
});
|
||||
if first {
|
||||
first = false;
|
||||
} else {
|
||||
ui.separator();
|
||||
ui.heading("MIPS");
|
||||
egui::ComboBox::new("mips_abi", "ABI")
|
||||
.selected_text(state.config.diff_obj_config.mips_abi.get_message().unwrap())
|
||||
.show_ui(ui, |ui| {
|
||||
for &abi in MipsAbi::VARIANTS {
|
||||
if ui
|
||||
.selectable_label(
|
||||
state.config.diff_obj_config.mips_abi == abi,
|
||||
abi.get_message().unwrap(),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
state.config.diff_obj_config.mips_abi = abi;
|
||||
}
|
||||
ui.heading(group.name);
|
||||
for property_id in group.properties.iter().cloned() {
|
||||
changed |= config_property_ui(ui, state, property_id);
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
state.queue_reload = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
egui::ComboBox::new("mips_instr_category", "Instruction Category")
|
||||
.selected_text(state.config.diff_obj_config.mips_instr_category.get_message().unwrap())
|
||||
.show_ui(ui, |ui| {
|
||||
for &category in MipsInstrCategory::VARIANTS {
|
||||
if ui
|
||||
.selectable_label(
|
||||
state.config.diff_obj_config.mips_instr_category == category,
|
||||
category.get_message().unwrap(),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
state.config.diff_obj_config.mips_instr_category = category;
|
||||
state.queue_reload = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.separator();
|
||||
ui.heading("ARM");
|
||||
egui::ComboBox::new("arm_arch_version", "Architecture Version")
|
||||
.selected_text(state.config.diff_obj_config.arm_arch_version.get_message().unwrap())
|
||||
.show_ui(ui, |ui| {
|
||||
for &version in ArmArchVersion::VARIANTS {
|
||||
if ui
|
||||
.selectable_label(
|
||||
state.config.diff_obj_config.arm_arch_version == version,
|
||||
version.get_message().unwrap(),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
state.config.diff_obj_config.arm_arch_version = version;
|
||||
state.queue_reload = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
let response = ui
|
||||
.checkbox(&mut state.config.diff_obj_config.arm_unified_syntax, "Unified syntax")
|
||||
.on_hover_text("Disassemble as unified assembly language (UAL).");
|
||||
if response.changed() {
|
||||
state.queue_reload = true;
|
||||
}
|
||||
let response = ui
|
||||
.checkbox(&mut state.config.diff_obj_config.arm_av_registers, "Use A/V registers")
|
||||
.on_hover_text("Display R0-R3 as A1-A4 and R4-R11 as V1-V8");
|
||||
if response.changed() {
|
||||
state.queue_reload = true;
|
||||
}
|
||||
egui::ComboBox::new("arm_r9_usage", "Display R9 as")
|
||||
.selected_text(state.config.diff_obj_config.arm_r9_usage.get_message().unwrap())
|
||||
.show_ui(ui, |ui| {
|
||||
for &usage in ArmR9Usage::VARIANTS {
|
||||
if ui
|
||||
.selectable_label(
|
||||
state.config.diff_obj_config.arm_r9_usage == usage,
|
||||
usage.get_message().unwrap(),
|
||||
)
|
||||
.on_hover_text(usage.get_detailed_message().unwrap())
|
||||
.clicked()
|
||||
{
|
||||
state.config.diff_obj_config.arm_r9_usage = usage;
|
||||
state.queue_reload = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
let response = ui
|
||||
.checkbox(&mut state.config.diff_obj_config.arm_sl_usage, "Display R10 as SL")
|
||||
.on_hover_text("Used for explicit stack limits.");
|
||||
if response.changed() {
|
||||
state.queue_reload = true;
|
||||
}
|
||||
let response = ui
|
||||
.checkbox(&mut state.config.diff_obj_config.arm_fp_usage, "Display R11 as FP")
|
||||
.on_hover_text("Used for frame pointers.");
|
||||
if response.changed() {
|
||||
state.queue_reload = true;
|
||||
}
|
||||
let response = ui
|
||||
.checkbox(&mut state.config.diff_obj_config.arm_ip_usage, "Display R12 as IP")
|
||||
.on_hover_text("Used for interworking and long branches.");
|
||||
if response.changed() {
|
||||
|
||||
pub fn general_config_ui(ui: &mut egui::Ui, state: &mut AppState) {
|
||||
let mut changed = false;
|
||||
let group = CONFIG_GROUPS.iter().find(|group| group.id == "general").unwrap();
|
||||
for property_id in group.properties.iter().cloned() {
|
||||
changed |= config_property_ui(ui, state, property_id);
|
||||
}
|
||||
if changed {
|
||||
state.queue_reload = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
use std::{cmp::min, default::Default, mem::take};
|
||||
use std::{
|
||||
cmp::{min, Ordering},
|
||||
default::Default,
|
||||
mem::take,
|
||||
};
|
||||
|
||||
use egui::{text::LayoutJob, Id, Label, RichText, Sense, Widget};
|
||||
use objdiff_core::{
|
||||
diff::{ObjDataDiff, ObjDataDiffKind, ObjDiff},
|
||||
diff::{ObjDataDiff, ObjDataDiffKind, ObjDataRelocDiff, ObjDiff},
|
||||
obj::ObjInfo,
|
||||
};
|
||||
use time::format_description;
|
||||
@@ -23,8 +27,111 @@ fn find_section(obj: &ObjInfo, section_name: &str) -> Option<usize> {
|
||||
obj.sections.iter().position(|section| section.name == section_name)
|
||||
}
|
||||
|
||||
fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], appearance: &Appearance) {
|
||||
if diffs.iter().any(|d| d.kind != ObjDataDiffKind::None) {
|
||||
fn data_row_hover_ui(
|
||||
ui: &mut egui::Ui,
|
||||
obj: &ObjInfo,
|
||||
diffs: &[(ObjDataDiff, Vec<ObjDataRelocDiff>)],
|
||||
appearance: &Appearance,
|
||||
) {
|
||||
ui.scope(|ui| {
|
||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||
|
||||
let reloc_diffs = diffs.iter().flat_map(|(_, reloc_diffs)| reloc_diffs);
|
||||
let mut prev_reloc = None;
|
||||
for reloc_diff in reloc_diffs {
|
||||
let reloc = &reloc_diff.reloc;
|
||||
if prev_reloc == Some(reloc) {
|
||||
// Avoid showing consecutive duplicate relocations.
|
||||
// We do this because a single relocation can span across multiple diffs if the
|
||||
// bytes in the relocation changed (e.g. first byte is added, second is unchanged).
|
||||
continue;
|
||||
}
|
||||
prev_reloc = Some(reloc);
|
||||
|
||||
let color = get_color_for_diff_kind(reloc_diff.kind, appearance);
|
||||
|
||||
// TODO: Most of this code is copy-pasted from ins_hover_ui.
|
||||
// Try to separate this out into a shared function.
|
||||
ui.label(format!("Relocation type: {}", obj.arch.display_reloc(reloc.flags)));
|
||||
ui.label(format!("Relocation address: {:x}", reloc.address));
|
||||
let addend_str = match reloc.addend.cmp(&0i64) {
|
||||
Ordering::Greater => format!("+{:x}", reloc.addend),
|
||||
Ordering::Less => format!("-{:x}", -reloc.addend),
|
||||
_ => "".to_string(),
|
||||
};
|
||||
ui.colored_label(color, format!("Name: {}{}", reloc.target.name, addend_str));
|
||||
if let Some(orig_section_index) = reloc.target.orig_section_index {
|
||||
if let Some(section) =
|
||||
obj.sections.iter().find(|s| s.orig_index == orig_section_index)
|
||||
{
|
||||
ui.colored_label(color, format!("Section: {}", section.name));
|
||||
}
|
||||
ui.colored_label(
|
||||
color,
|
||||
format!("Address: {:x}{}", reloc.target.address, addend_str),
|
||||
);
|
||||
ui.colored_label(color, format!("Size: {:x}", reloc.target.size));
|
||||
if reloc.addend >= 0 && reloc.target.bytes.len() > reloc.addend as usize {}
|
||||
} else {
|
||||
ui.colored_label(color, "Extern".to_string());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn data_row_context_menu(ui: &mut egui::Ui, diffs: &[(ObjDataDiff, Vec<ObjDataRelocDiff>)]) {
|
||||
ui.scope(|ui| {
|
||||
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
|
||||
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
|
||||
|
||||
let reloc_diffs = diffs.iter().flat_map(|(_, reloc_diffs)| reloc_diffs);
|
||||
let mut prev_reloc = None;
|
||||
for reloc_diff in reloc_diffs {
|
||||
let reloc = &reloc_diff.reloc;
|
||||
if prev_reloc == Some(reloc) {
|
||||
// Avoid showing consecutive duplicate relocations.
|
||||
// We do this because a single relocation can span across multiple diffs if the
|
||||
// bytes in the relocation changed (e.g. first byte is added, second is unchanged).
|
||||
continue;
|
||||
}
|
||||
prev_reloc = Some(reloc);
|
||||
|
||||
// TODO: This code is copy-pasted from ins_context_menu.
|
||||
// Try to separate this out into a shared function.
|
||||
if let Some(name) = &reloc.target.demangled_name {
|
||||
if ui.button(format!("Copy \"{name}\"")).clicked() {
|
||||
ui.output_mut(|output| output.copied_text.clone_from(name));
|
||||
ui.close_menu();
|
||||
}
|
||||
}
|
||||
if ui.button(format!("Copy \"{}\"", reloc.target.name)).clicked() {
|
||||
ui.output_mut(|output| output.copied_text.clone_from(&reloc.target.name));
|
||||
ui.close_menu();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn get_color_for_diff_kind(diff_kind: ObjDataDiffKind, appearance: &Appearance) -> egui::Color32 {
|
||||
match diff_kind {
|
||||
ObjDataDiffKind::None => appearance.text_color,
|
||||
ObjDataDiffKind::Replace => appearance.replace_color,
|
||||
ObjDataDiffKind::Delete => appearance.delete_color,
|
||||
ObjDataDiffKind::Insert => appearance.insert_color,
|
||||
}
|
||||
}
|
||||
|
||||
fn data_row_ui(
|
||||
ui: &mut egui::Ui,
|
||||
obj: Option<&ObjInfo>,
|
||||
address: usize,
|
||||
diffs: &[(ObjDataDiff, Vec<ObjDataRelocDiff>)],
|
||||
appearance: &Appearance,
|
||||
) {
|
||||
if diffs.iter().any(|(dd, rds)| {
|
||||
dd.kind != ObjDataDiffKind::None || rds.iter().any(|rd| rd.kind != ObjDataDiffKind::None)
|
||||
}) {
|
||||
ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color);
|
||||
}
|
||||
let mut job = LayoutJob::default();
|
||||
@@ -34,29 +141,34 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], appeara
|
||||
&mut job,
|
||||
appearance.code_font.clone(),
|
||||
);
|
||||
// The offset shown on the side of the GUI, shifted by insertions/deletions.
|
||||
let mut cur_addr = 0usize;
|
||||
for diff in diffs {
|
||||
let base_color = match diff.kind {
|
||||
ObjDataDiffKind::None => appearance.text_color,
|
||||
ObjDataDiffKind::Replace => appearance.replace_color,
|
||||
ObjDataDiffKind::Delete => appearance.delete_color,
|
||||
ObjDataDiffKind::Insert => appearance.insert_color,
|
||||
};
|
||||
// The offset into the actual bytes of the section on this side, ignoring differences.
|
||||
let mut cur_addr_actual = address;
|
||||
for (diff, reloc_diffs) in diffs {
|
||||
let base_color = get_color_for_diff_kind(diff.kind, appearance);
|
||||
if diff.data.is_empty() {
|
||||
let mut str = " ".repeat(diff.len);
|
||||
str.push_str(" ".repeat(diff.len / 8).as_str());
|
||||
write_text(str.as_str(), base_color, &mut job, appearance.code_font.clone());
|
||||
cur_addr += diff.len;
|
||||
} else {
|
||||
let mut text = String::new();
|
||||
for byte in &diff.data {
|
||||
text.push_str(format!("{byte:02x} ").as_str());
|
||||
let mut byte_color = base_color;
|
||||
if let Some(reloc_diff) = reloc_diffs.iter().find(|reloc_diff| {
|
||||
reloc_diff.kind != ObjDataDiffKind::None
|
||||
&& reloc_diff.range.contains(&cur_addr_actual)
|
||||
}) {
|
||||
byte_color = get_color_for_diff_kind(reloc_diff.kind, appearance);
|
||||
}
|
||||
let byte_text = format!("{byte:02x} ");
|
||||
write_text(byte_text.as_str(), byte_color, &mut job, appearance.code_font.clone());
|
||||
cur_addr += 1;
|
||||
cur_addr_actual += 1;
|
||||
if cur_addr % 8 == 0 {
|
||||
text.push(' ');
|
||||
write_text(" ", base_color, &mut job, appearance.code_font.clone());
|
||||
}
|
||||
}
|
||||
write_text(text.as_str(), base_color, &mut job, appearance.code_font.clone());
|
||||
}
|
||||
}
|
||||
if cur_addr < BYTES_PER_ROW {
|
||||
@@ -67,13 +179,8 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], appeara
|
||||
write_text(str.as_str(), appearance.text_color, &mut job, appearance.code_font.clone());
|
||||
}
|
||||
write_text(" ", appearance.text_color, &mut job, appearance.code_font.clone());
|
||||
for diff in diffs {
|
||||
let base_color = match diff.kind {
|
||||
ObjDataDiffKind::None => appearance.text_color,
|
||||
ObjDataDiffKind::Replace => appearance.replace_color,
|
||||
ObjDataDiffKind::Delete => appearance.delete_color,
|
||||
ObjDataDiffKind::Insert => appearance.insert_color,
|
||||
};
|
||||
for (diff, _) in diffs {
|
||||
let base_color = get_color_for_diff_kind(diff.kind, appearance);
|
||||
if diff.data.is_empty() {
|
||||
write_text(
|
||||
" ".repeat(diff.len).as_str(),
|
||||
@@ -94,22 +201,33 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], appeara
|
||||
write_text(text.as_str(), base_color, &mut job, appearance.code_font.clone());
|
||||
}
|
||||
}
|
||||
Label::new(job).sense(Sense::click()).ui(ui);
|
||||
// .on_hover_ui_at_pointer(|ui| ins_hover_ui(ui, ins))
|
||||
// .context_menu(|ui| ins_context_menu(ui, ins));
|
||||
|
||||
let response = Label::new(job).sense(Sense::click()).ui(ui);
|
||||
if let Some(obj) = obj {
|
||||
response
|
||||
.on_hover_ui_at_pointer(|ui| data_row_hover_ui(ui, obj, diffs, appearance))
|
||||
.context_menu(|ui| data_row_context_menu(ui, diffs));
|
||||
}
|
||||
}
|
||||
|
||||
fn split_diffs(diffs: &[ObjDataDiff]) -> Vec<Vec<ObjDataDiff>> {
|
||||
let mut split_diffs = Vec::<Vec<ObjDataDiff>>::new();
|
||||
let mut row_diffs = Vec::<ObjDataDiff>::new();
|
||||
fn split_diffs(
|
||||
diffs: &[ObjDataDiff],
|
||||
reloc_diffs: &[ObjDataRelocDiff],
|
||||
) -> Vec<Vec<(ObjDataDiff, Vec<ObjDataRelocDiff>)>> {
|
||||
let mut split_diffs = Vec::<Vec<(ObjDataDiff, Vec<ObjDataRelocDiff>)>>::new();
|
||||
let mut row_diffs = Vec::<(ObjDataDiff, Vec<ObjDataRelocDiff>)>::new();
|
||||
// The offset shown on the side of the GUI, shifted by insertions/deletions.
|
||||
let mut cur_addr = 0usize;
|
||||
// The offset into the actual bytes of the section on this side, ignoring differences.
|
||||
let mut cur_addr_actual = 0usize;
|
||||
for diff in diffs {
|
||||
let mut cur_len = 0usize;
|
||||
while cur_len < diff.len {
|
||||
let remaining_len = diff.len - cur_len;
|
||||
let mut remaining_in_row = BYTES_PER_ROW - (cur_addr % BYTES_PER_ROW);
|
||||
let len = min(remaining_len, remaining_in_row);
|
||||
row_diffs.push(ObjDataDiff {
|
||||
|
||||
let data_diff = ObjDataDiff {
|
||||
data: if diff.data.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
@@ -117,9 +235,28 @@ fn split_diffs(diffs: &[ObjDataDiff]) -> Vec<Vec<ObjDataDiff>> {
|
||||
},
|
||||
kind: diff.kind,
|
||||
len,
|
||||
// TODO
|
||||
symbol: String::new(),
|
||||
});
|
||||
symbol: String::new(), // TODO
|
||||
};
|
||||
let row_reloc_diffs: Vec<ObjDataRelocDiff> = if diff.data.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
let diff_range = cur_addr_actual + cur_len..cur_addr_actual + cur_len + len;
|
||||
reloc_diffs
|
||||
.iter()
|
||||
.filter_map(|reloc_diff| {
|
||||
if reloc_diff.range.start < diff_range.end
|
||||
&& diff_range.start < reloc_diff.range.end
|
||||
{
|
||||
Some(reloc_diff.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
let row_diff = (data_diff, row_reloc_diffs);
|
||||
|
||||
row_diffs.push(row_diff);
|
||||
remaining_in_row -= len;
|
||||
cur_len += len;
|
||||
cur_addr += len;
|
||||
@@ -127,6 +264,7 @@ fn split_diffs(diffs: &[ObjDataDiff]) -> Vec<Vec<ObjDataDiff>> {
|
||||
split_diffs.push(take(&mut row_diffs));
|
||||
}
|
||||
}
|
||||
cur_addr_actual += diff.data.len();
|
||||
}
|
||||
if !row_diffs.is_empty() {
|
||||
split_diffs.push(take(&mut row_diffs));
|
||||
@@ -161,6 +299,8 @@ fn data_table_ui(
|
||||
right_ctx: Option<SectionDiffContext<'_>>,
|
||||
config: &Appearance,
|
||||
) -> Option<()> {
|
||||
let left_obj = left_ctx.map(|ctx| ctx.obj);
|
||||
let right_obj = right_ctx.map(|ctx| ctx.obj);
|
||||
let left_section = left_ctx
|
||||
.and_then(|ctx| ctx.section_index.map(|i| (&ctx.obj.sections[i], &ctx.diff.sections[i])));
|
||||
let right_section = right_ctx
|
||||
@@ -176,8 +316,10 @@ fn data_table_ui(
|
||||
}
|
||||
let total_rows = (total_bytes - 1) / BYTES_PER_ROW + 1;
|
||||
|
||||
let left_diffs = left_section.map(|(_, section)| split_diffs(§ion.data_diff));
|
||||
let right_diffs = right_section.map(|(_, section)| split_diffs(§ion.data_diff));
|
||||
let left_diffs =
|
||||
left_section.map(|(_, section)| split_diffs(§ion.data_diff, §ion.reloc_diff));
|
||||
let right_diffs =
|
||||
right_section.map(|(_, section)| split_diffs(§ion.data_diff, §ion.reloc_diff));
|
||||
|
||||
hotkeys::check_scroll_hotkeys(ui, true);
|
||||
|
||||
@@ -187,11 +329,11 @@ fn data_table_ui(
|
||||
row.col(|ui| {
|
||||
if column == 0 {
|
||||
if let Some(left_diffs) = &left_diffs {
|
||||
data_row_ui(ui, address, &left_diffs[i], config);
|
||||
data_row_ui(ui, left_obj, address, &left_diffs[i], config);
|
||||
}
|
||||
} else if column == 1 {
|
||||
if let Some(right_diffs) = &right_diffs {
|
||||
data_row_ui(ui, address, &right_diffs[i], config);
|
||||
data_row_ui(ui, right_obj, address, &right_diffs[i], config);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{cmp::Ordering, default::Default};
|
||||
|
||||
use egui::{text::LayoutJob, Id, Label, Response, RichText, Sense, Widget};
|
||||
use egui::{text::LayoutJob, Id, Label, Layout, Response, RichText, Sense, Widget};
|
||||
use egui_extras::TableRow;
|
||||
use objdiff_core::{
|
||||
diff::{
|
||||
@@ -149,13 +149,9 @@ fn ins_hover_ui(
|
||||
appearance.highlight_color,
|
||||
format!("Size: {:x}", reloc.target.size),
|
||||
);
|
||||
if reloc.addend >= 0 && reloc.target.bytes.len() > reloc.addend as usize {
|
||||
if let Some(s) = obj.arch.guess_data_type(ins).and_then(|ty| {
|
||||
obj.arch.display_data_type(ty, &reloc.target.bytes[reloc.addend as usize..])
|
||||
}) {
|
||||
if let Some(s) = obj.arch.display_ins_data(ins) {
|
||||
ui.colored_label(appearance.highlight_color, s);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ui.colored_label(appearance.highlight_color, "Extern".to_string());
|
||||
}
|
||||
@@ -414,6 +410,7 @@ fn asm_col_ui(
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn asm_table_ui(
|
||||
ui: &mut egui::Ui,
|
||||
available_width: f32,
|
||||
@@ -422,6 +419,7 @@ fn asm_table_ui(
|
||||
appearance: &Appearance,
|
||||
ins_view_state: &FunctionViewState,
|
||||
symbol_state: &SymbolViewState,
|
||||
open_sections: (Option<bool>, Option<bool>),
|
||||
) -> Option<DiffViewAction> {
|
||||
let mut ret = None;
|
||||
let left_len = left_ctx.and_then(|ctx| {
|
||||
@@ -512,6 +510,7 @@ fn asm_table_ui(
|
||||
SymbolFilter::Mapping(right_symbol_ref),
|
||||
appearance,
|
||||
column,
|
||||
open_sections.0,
|
||||
) {
|
||||
match action {
|
||||
DiffViewAction::Navigate(DiffViewNavigation {
|
||||
@@ -570,6 +569,7 @@ fn asm_table_ui(
|
||||
SymbolFilter::Mapping(left_symbol_ref),
|
||||
appearance,
|
||||
column,
|
||||
open_sections.1,
|
||||
) {
|
||||
match action {
|
||||
DiffViewAction::Navigate(DiffViewNavigation {
|
||||
@@ -683,6 +683,7 @@ pub fn function_diff_ui(
|
||||
|
||||
// Header
|
||||
let available_width = ui.available_width();
|
||||
let mut open_sections = (None, None);
|
||||
render_header(ui, available_width, 2, |ui, column| {
|
||||
if column == 0 {
|
||||
// Left column
|
||||
@@ -736,11 +737,24 @@ pub fn function_diff_ui(
|
||||
.font(appearance.code_font.clone())
|
||||
.color(appearance.replace_color),
|
||||
);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(
|
||||
RichText::new("Choose target symbol")
|
||||
.font(appearance.code_font.clone())
|
||||
.color(appearance.highlight_color),
|
||||
);
|
||||
|
||||
ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| {
|
||||
if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() {
|
||||
open_sections.0 = Some(true);
|
||||
}
|
||||
if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked()
|
||||
{
|
||||
open_sections.0 = Some(false);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
} else if column == 1 {
|
||||
// Right column
|
||||
@@ -812,11 +826,24 @@ pub fn function_diff_ui(
|
||||
.font(appearance.code_font.clone())
|
||||
.color(appearance.replace_color),
|
||||
);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(
|
||||
RichText::new("Choose base symbol")
|
||||
.font(appearance.code_font.clone())
|
||||
.color(appearance.highlight_color),
|
||||
);
|
||||
|
||||
ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| {
|
||||
if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() {
|
||||
open_sections.1 = Some(true);
|
||||
}
|
||||
if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked()
|
||||
{
|
||||
open_sections.1 = Some(false);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -834,6 +861,7 @@ pub fn function_diff_ui(
|
||||
appearance,
|
||||
&state.function_state,
|
||||
&state.symbol_state,
|
||||
open_sections,
|
||||
)
|
||||
})
|
||||
.inner
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::{collections::BTreeMap, mem::take, ops::Bound};
|
||||
|
||||
use egui::{
|
||||
style::ScrollAnimation, text::LayoutJob, CollapsingHeader, Color32, Id, OpenUrl, ScrollArea,
|
||||
SelectableLabel, TextEdit, Ui, Widget,
|
||||
style::ScrollAnimation, text::LayoutJob, CollapsingHeader, Color32, Id, Layout, OpenUrl,
|
||||
ScrollArea, SelectableLabel, TextEdit, Ui, Widget,
|
||||
};
|
||||
use objdiff_core::{
|
||||
arch::ObjArch,
|
||||
@@ -605,6 +605,7 @@ pub enum SymbolFilter<'a> {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub fn symbol_list_ui(
|
||||
ui: &mut Ui,
|
||||
ctx: SymbolDiffContext<'_>,
|
||||
@@ -613,6 +614,7 @@ pub fn symbol_list_ui(
|
||||
filter: SymbolFilter<'_>,
|
||||
appearance: &Appearance,
|
||||
column: usize,
|
||||
open_sections: Option<bool>,
|
||||
) -> Option<DiffViewAction> {
|
||||
let mut ret = None;
|
||||
ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| {
|
||||
@@ -766,6 +768,7 @@ pub fn symbol_list_ui(
|
||||
CollapsingHeader::new(header)
|
||||
.id_salt(Id::new(section.name.clone()).with(section.orig_index))
|
||||
.default_open(true)
|
||||
.open(open_sections)
|
||||
.show(ui, |ui| {
|
||||
if section.kind == ObjSectionKind::Code && state.reverse_fn_order {
|
||||
for (symbol, symbol_diff) in mapping
|
||||
@@ -873,6 +876,7 @@ pub fn symbol_diff_ui(
|
||||
|
||||
// Header
|
||||
let available_width = ui.available_width();
|
||||
let mut open_sections = (None, None);
|
||||
render_header(ui, available_width, 2, |ui, column| {
|
||||
if column == 0 {
|
||||
// Left column
|
||||
@@ -891,6 +895,7 @@ pub fn symbol_diff_ui(
|
||||
}
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let mut search = state.search.clone();
|
||||
let response = TextEdit::singleline(&mut search).hint_text("Filter symbols").ui(ui);
|
||||
if hotkeys::consume_symbol_filter_shortcut(ui.ctx()) {
|
||||
@@ -899,6 +904,16 @@ pub fn symbol_diff_ui(
|
||||
if response.changed() {
|
||||
ret = Some(DiffViewAction::SetSearch(search));
|
||||
}
|
||||
|
||||
ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| {
|
||||
if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() {
|
||||
open_sections.0 = Some(true);
|
||||
}
|
||||
if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked() {
|
||||
open_sections.0 = Some(false);
|
||||
}
|
||||
})
|
||||
});
|
||||
} else if column == 1 {
|
||||
// Right column
|
||||
ui.horizontal(|ui| {
|
||||
@@ -930,9 +945,20 @@ pub fn symbol_diff_ui(
|
||||
}
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
if ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked() {
|
||||
ret = Some(DiffViewAction::Build);
|
||||
}
|
||||
|
||||
ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| {
|
||||
if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() {
|
||||
open_sections.1 = Some(true);
|
||||
}
|
||||
if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked() {
|
||||
open_sections.1 = Some(false);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -957,6 +983,7 @@ pub fn symbol_diff_ui(
|
||||
filter,
|
||||
appearance,
|
||||
column,
|
||||
open_sections.0,
|
||||
) {
|
||||
ret = Some(result);
|
||||
}
|
||||
@@ -981,6 +1008,7 @@ pub fn symbol_diff_ui(
|
||||
filter,
|
||||
appearance,
|
||||
column,
|
||||
open_sections.1,
|
||||
) {
|
||||
ret = Some(result);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "objdiff-wasm",
|
||||
"version": "2.0.0",
|
||||
"version": "2.6.0",
|
||||
"description": "A local diffing tool for decompilation projects.",
|
||||
"author": {
|
||||
"name": "Luke Street",
|
||||
@@ -21,7 +21,7 @@
|
||||
"build": "tsup",
|
||||
"build:all": "npm run build:wasm && npm run build:proto && npm run build",
|
||||
"build:proto": "protoc --ts_out=gen --ts_opt add_pb_suffix,eslint_disable,ts_nocheck,use_proto_field_name --proto_path=../objdiff-core/protos ../objdiff-core/protos/*.proto",
|
||||
"build:wasm": "cd ../objdiff-core && wasm-pack build --out-dir ../objdiff-wasm/pkg --target web -- --features arm,dwarf,ppc,x86,wasm"
|
||||
"build:wasm": "cd ../objdiff-core && wasm-pack build --out-dir ../objdiff-wasm/pkg --target web -- --features arm,arm64,dwarf,config,ppc,x86,wasm"
|
||||
},
|
||||
"dependencies": {
|
||||
"@protobuf-ts/runtime": "^2.9.4"
|
||||
|
||||
@@ -111,12 +111,12 @@ async function defer<T>(message: AnyHandlerData, worker?: Worker): Promise<T> {
|
||||
return promise;
|
||||
}
|
||||
|
||||
export async function runDiff(left: Uint8Array | undefined, right: Uint8Array | undefined, config?: DiffObjConfig): Promise<DiffResult> {
|
||||
export async function runDiff(left: Uint8Array | undefined, right: Uint8Array | undefined, diff_config?: DiffObjConfig): Promise<DiffResult> {
|
||||
const data = await defer<Uint8Array>({
|
||||
type: 'run_diff_proto',
|
||||
left,
|
||||
right,
|
||||
config
|
||||
diff_config
|
||||
});
|
||||
const parseStart = performance.now();
|
||||
const result = DiffResult.fromBinary(data, {readUnknownField: false});
|
||||
@@ -194,12 +194,17 @@ export function displayDiff(diff: InstructionDiff, baseAddr: bigint, cb: (text:
|
||||
cb({type: 'spacing', count: 4});
|
||||
}
|
||||
cb({type: 'opcode', mnemonic: ins.mnemonic, opcode: ins.opcode});
|
||||
let arg_diff_idx = 0; // non-PlainText argument index
|
||||
for (let i = 0; i < ins.arguments.length; i++) {
|
||||
if (i === 0) {
|
||||
cb({type: 'spacing', count: 1});
|
||||
}
|
||||
const arg = ins.arguments[i].value;
|
||||
const diff_index = diff.arg_diff[i]?.diff_index;
|
||||
let diff_index: number | undefined;
|
||||
if (arg.oneofKind !== 'plain_text') {
|
||||
diff_index = diff.arg_diff[arg_diff_idx]?.diff_index;
|
||||
arg_diff_idx++;
|
||||
}
|
||||
switch (arg.oneofKind) {
|
||||
case "plain_text":
|
||||
cb({type: 'basic', text: arg.plain_text, diff_index});
|
||||
|
||||
@@ -38,13 +38,15 @@ async function initIfNeeded() {
|
||||
// return exports.run_diff_json(left, right, cfg);
|
||||
// }
|
||||
|
||||
async function run_diff_proto({left, right, config}: {
|
||||
async function run_diff_proto({left, right, diff_config, mapping_config}: {
|
||||
left: Uint8Array | undefined,
|
||||
right: Uint8Array | undefined,
|
||||
config?: exports.DiffObjConfig,
|
||||
diff_config?: exports.DiffObjConfig,
|
||||
mapping_config?: exports.MappingConfig,
|
||||
}): Promise<Uint8Array> {
|
||||
config = config || {};
|
||||
return exports.run_diff_proto(left, right, config);
|
||||
diff_config = diff_config || {};
|
||||
mapping_config = mapping_config || {};
|
||||
return exports.run_diff_proto(left, right, diff_config, mapping_config);
|
||||
}
|
||||
|
||||
export type AnyHandlerData = HandlerData[keyof HandlerData];
|
||||
@@ -73,12 +75,19 @@ self.onmessage = (event: MessageEvent<InMessage>) => {
|
||||
const result = await handler(data as never);
|
||||
const end = performance.now();
|
||||
console.debug(`Worker message ${data.messageId} took ${end - start}ms`);
|
||||
let transfer: Transferable[] = [];
|
||||
if (result instanceof Uint8Array) {
|
||||
console.log("Transferring!", result.byteLength);
|
||||
transfer = [result.buffer];
|
||||
} else {
|
||||
console.log("Didn't transfer", typeof result);
|
||||
}
|
||||
self.postMessage({
|
||||
type: 'result',
|
||||
result: result,
|
||||
error: null,
|
||||
messageId,
|
||||
} as OutMessage);
|
||||
} as OutMessage, {transfer});
|
||||
} else {
|
||||
throw new Error(`No handler for ${data.type}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user