Compare commits

...

23 Commits

Author SHA1 Message Date
Luke Street 0a85f540f2 Version v3.4.0 2025-11-12 12:22:01 -07:00
Anghelo Carvajal d79b5b1233 Update gnuv2_demangle to 0.4 (#280) 2025-11-09 11:53:02 -07:00
Anghelo Carvajal 8f5519cb6a Update gnuv2_demangle to version 0.3 (#275) 2025-10-12 21:19:07 -06:00
Aetias 19ec08be9a Update unarm to 2.0 (#274)
* Migrate to unarm 2.0

* Update unarm to proper 2.0 release

* Deduplicate formatters for opaque instruction parts in ARM

* Add option to enable VFPv2 for ARM

* Update unarm to 2.0.1

* Fix read order for big-endian Thumb code

* Skip leading space in ARM instruction params

* Update ARM tests

* arm.rs: Use `alloc::borrow::Cow`
2025-10-12 21:13:29 -06:00
Luke Street 2ad0898efa Version v3.3.1 2025-10-08 21:47:36 -06:00
Ethan Lafrenais 03f2bcb8b1 R_MIPS_GPREL32 Support (#270)
* R_MIPS_GPREL32 Support

* cargo fmt

* Add name for R_MIPS_GPREL32 relocation
2025-10-08 20:34:35 -06:00
BR- 781071761a show symbol size in symbol list (#268)
* show symbol size in symbol list

* configs for show symbol size

* move symbol size config to config-schema
2025-10-08 20:32:07 -06:00
Luke Street 572afa8551 Support IDO .mdebug for line numbers (#264)
* Support IDO .mdebug for line numbers

Resolves #179

* Fix wasm build && cargo clippy --fix

* Resolve review comment
2025-09-25 10:00:40 -06:00
Luke Street 90e81fad7e Improve ARM function size inference
This allows 2-byte padding to be trimmed in ARM functions.

Resolves #253
2025-09-25 00:26:43 -06:00
Luke Street 7a8efb4c88 Version v3.3.0 2025-09-23 13:09:57 -06:00
Luke Street 56dac46280 Support overriding diff options in project config (& for individual units) (#263)
* Support loading diff options from project config

* Support per-unit option overrides
2025-09-23 12:11:40 -06:00
Luke Street 1866158092 Add 'None' demangler option 2025-09-22 23:31:57 -06:00
Anghelo Carvajal fe8e7029f6 Arch-independent demangling and add gnuv2_demangle for old g++ projects (#262) 2025-09-22 17:24:29 -06:00
Ethan Roseman e2c70342c9 Standardize the values for invalid and data opcodes (#261)
* Standardize the value for an invalid opcode
>
> This makes it so that all arches share the same value for an invalid opcode, so platform-specific logic isn't needed for checking whether instructions are valid.
Also updated dependencies

* OPCODE_DATA too
2025-09-13 12:33:06 -06:00
Anghelo Carvajal e6035b00df [MIPS] Add support for the other ABIs supported by rabbitizer (#259)
* [MIPS] Add support for the other abis supported by rabbitizer

* update test
2025-09-10 09:01:08 -06:00
Luke Street 97bcfe23d4 Update test snapshots 2025-09-08 22:40:51 -06:00
Luke Street d684b622b5 Version v3.2.1 2025-09-08 22:37:02 -06:00
Luke Street c698750068 Use dim color for address column in asm view 2025-09-08 22:32:52 -06:00
iFarbod a06d3455ae Ignore extern differences in relocations (#258) 2025-09-08 21:41:53 -06:00
Luke Street fbdaa89cc0 Refactor data diffing & expose WASM API (#256)
* Refactor data diffing & expose WASM API

* Update test snapshots
2025-09-07 18:59:46 -06:00
Luke Street f7cb494a62 Version v3.1.1 2025-09-07 11:32:43 -06:00
iFarbod 7cc6ed2b43 Add support for x86 indirect tables (#251)
* Add support for x86 indirect tables

Closes #239

* Fix formatting issues in x86.rs

* Add x86 indirect table test
2025-09-07 11:28:23 -06:00
Toms 532b684682 Implement automatic symbol pairing for MSVC generated static functions (#255) 2025-09-07 11:28:01 -06:00
65 changed files with 19542 additions and 1887 deletions
+44
View File
@@ -0,0 +1,44 @@
# Repository Guidelines
## Project Structure & Module Organization
- `objdiff-core`: core library with diffing logic and shared components
- `objdiff-cli`: CLI/TUI with `ratatui`
- `objdiff-gui`: GUI with `eframe`/`egui`
- `objdiff-wasm`: WebAssembly bindings
objdiff has three main frontends: GUI, CLI/TUI, and [web](https://github.com/encounter/objdiff-web) (utilizing `objdiff-wasm`).
`objdiff-gui` is the most fully-featured and is the primary target for new features.
`objdiff-cli` has an interactive TUI `diff` mode (powered by `ratatui`) and a non-interactive `report` mode to generate detailed progress reports (e.g. for <https://decomp.dev>).
`objdiff-wasm` is compiled into a [WebAssembly Component](https://component-model.bytecodealliance.org/). The [API](objdiff-wasm/wit/objdiff.wit) is defined using [WIT](https://component-model.bytecodealliance.org/design/wit.html). The web interface is separate, but mirrors the GUI in functionality.
## Build, Test, and Development Commands
Run `cargo build` for a debug build and `cargo build --release` for an optimized build.
The CLI can be exercised with `cargo run --release --bin objdiff-cli -- --help`.
The WASM build is not included in the workspace by default due to differences in toolchain and target; build it with `npm -C objdiff-wasm run build`. (nightly required)
In general, do NOT use `--workspace`, as it will include the WASM crate and likely fail. Formatting and linting commands are exempt.
Pre-commit tasks:
- `cargo test` to run the test suite
- `cargo +nightly fmt --all` to format code (nightly required)
- `cargo +nightly clippy --all-targets --all-features --workspace -- -D warnings` to lint code (nightly required)
- `cargo deny check` (`cargo install --locked cargo-deny` if needed) if dependencies change
## Testing Guidelines
Favor focused unit tests adjacent to the code, and integration scenarios in `objdiff-core/tests`.
Integration tests utilize snapshots with the `insta` crate; run `curl -LsSf https://insta.rs/install.sh | sh` if needed.
To generate updated snapshots, use `cargo insta test`, review diffs manually, then accept changes with `cargo insta accept` (or simply `mv` to accept specific snapshots).
## Configuration Tips
- `config.schema.json`: JSON schema for the user-facing project config file (`objdiff.json`)
- `objdiff-core/config-schema.json`: Internal options schema to generate `DiffObjConfig` (see `objdiff-core/config_gen.rs`)
The project configuration (`objdiff.json`) is intended to be generated by the projects' build system. It includes paths to the target (expected) and base (current) object files, along with build commands and file watch patterns. See `README.md` for a summary of key options.
Generated
+421 -304
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -14,7 +14,7 @@ default-members = [
resolver = "3" resolver = "3"
[workspace.package] [workspace.package]
version = "3.1.0" version = "3.4.0"
authors = ["Luke Street <luke@street.dev>"] authors = ["Luke Street <luke@street.dev>"]
edition = "2024" edition = "2024"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
+33
View File
@@ -111,6 +111,25 @@
"items": { "items": {
"$ref": "#/$defs/progress_category" "$ref": "#/$defs/progress_category"
} }
},
"options": {
"type": "object",
"description": "Diff configuration options that should be applied automatically when the project is loaded.",
"additionalProperties": {
"oneOf": [
{
"type": "boolean"
},
{
"type": "string"
}
]
},
"examples": [
{
"demangler": "gnu_legacy"
}
]
} }
}, },
"$defs": { "$defs": {
@@ -156,6 +175,20 @@
"additionalProperties": { "additionalProperties": {
"type": "string" "type": "string"
} }
},
"options": {
"type": "object",
"description": "Diff configuration options that should be applied when this unit is active.",
"additionalProperties": {
"oneOf": [
{
"type": "boolean"
},
{
"type": "string"
}
]
}
} }
} }
}, },
+39 -21
View File
@@ -24,7 +24,8 @@ use objdiff_core::{
watcher::{Watcher, create_watcher}, watcher::{Watcher, create_watcher},
}, },
config::{ config::{
ProjectConfig, ProjectObject, ProjectObjectMetadata, build_globset, ProjectConfig, ProjectObject, ProjectObjectMetadata, ProjectOptions, apply_project_options,
build_globset,
path::{check_path_buf, platform_path, platform_path_serde_option}, path::{check_path_buf, platform_path, platform_path_serde_option},
}, },
diff::{DiffObjConfig, MappingConfig, ObjectDiff}, diff::{DiffObjConfig, MappingConfig, ObjectDiff},
@@ -77,11 +78,11 @@ pub struct Args {
} }
pub fn run(args: Args) -> Result<()> { pub fn run(args: Args) -> Result<()> {
let (target_path, base_path, project_config) = let (target_path, base_path, project_config, unit_options) =
match (&args.target, &args.base, &args.project, &args.unit) { match (&args.target, &args.base, &args.project, &args.unit) {
(Some(_), Some(_), None, None) (Some(_), Some(_), None, None)
| (Some(_), None, None, None) | (Some(_), None, None, None)
| (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None), | (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None, None),
(None, None, p, u) => { (None, None, p, u) => {
let project = match p { let project = match p {
Some(project) => project.clone(), Some(project) => project.clone(),
@@ -106,28 +107,32 @@ pub fn run(args: Args) -> Result<()> {
.base_dir .base_dir
.as_ref() .as_ref()
.map(|p| project.join(p.with_platform_encoding())); .map(|p| project.join(p.with_platform_encoding()));
let objects = project_config let units = project_config.units.as_deref().unwrap_or_default();
.units let objects = units
.iter() .iter()
.flatten() .enumerate()
.map(|o| { .map(|(idx, o)| {
ObjectConfig::new( (
o, ObjectConfig::new(
&project, o,
target_obj_dir.as_deref(), &project,
base_obj_dir.as_deref(), target_obj_dir.as_deref(),
base_obj_dir.as_deref(),
),
idx,
) )
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let object = if let Some(u) = u { let (object, unit_idx) = if let Some(u) = u {
objects objects
.iter() .iter()
.find(|obj| obj.name == *u) .find(|(obj, _)| obj.name == *u)
.map(|(obj, idx)| (obj, *idx))
.ok_or_else(|| anyhow!("Unit not found: {}", u))? .ok_or_else(|| anyhow!("Unit not found: {}", u))?
} else if let Some(symbol_name) = &args.symbol { } else if let Some(symbol_name) = &args.symbol {
let mut idx = None; let mut idx = None;
let mut count = 0usize; let mut count = 0usize;
for (i, obj) in objects.iter().enumerate() { for (i, (obj, unit_idx)) in objects.iter().enumerate() {
if obj if obj
.target_path .target_path
.as_deref() .as_deref()
@@ -135,7 +140,7 @@ pub fn run(args: Args) -> Result<()> {
.transpose()? .transpose()?
.unwrap_or(false) .unwrap_or(false)
{ {
idx = Some(i); idx = Some((i, *unit_idx));
count += 1; count += 1;
if count > 1 { if count > 1 {
break; break;
@@ -144,7 +149,7 @@ pub fn run(args: Args) -> Result<()> {
} }
match (count, idx) { match (count, idx) {
(0, None) => bail!("Symbol not found: {}", symbol_name), (0, None) => bail!("Symbol not found: {}", symbol_name),
(1, Some(i)) => &objects[i], (1, Some((i, unit_idx))) => (&objects[i].0, unit_idx),
(2.., Some(_)) => bail!( (2.., Some(_)) => bail!(
"Multiple instances of {} were found, try specifying a unit", "Multiple instances of {} were found, try specifying a unit",
symbol_name symbol_name
@@ -154,18 +159,29 @@ pub fn run(args: Args) -> Result<()> {
} else { } else {
bail!("Must specify one of: symbol, project and unit, target and base objects") bail!("Must specify one of: symbol, project and unit, target and base objects")
}; };
let unit_options = units.get(unit_idx).and_then(|u| u.options().cloned());
let target_path = object.target_path.clone(); let target_path = object.target_path.clone();
let base_path = object.base_path.clone(); let base_path = object.base_path.clone();
(target_path, base_path, Some(project_config)) (target_path, base_path, Some(project_config), unit_options)
} }
_ => bail!("Either target and base or project and unit must be specified"), _ => bail!("Either target and base or project and unit must be specified"),
}; };
run_interactive(args, target_path, base_path, project_config) run_interactive(args, target_path, base_path, project_config, unit_options)
} }
fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)> { fn build_config_from_args(
args: &Args,
project_config: Option<&ProjectConfig>,
unit_options: Option<&ProjectOptions>,
) -> Result<(DiffObjConfig, MappingConfig)> {
let mut diff_config = DiffObjConfig::default(); let mut diff_config = DiffObjConfig::default();
if let Some(options) = project_config.and_then(|config| config.options.as_ref()) {
apply_project_options(&mut diff_config, options)?;
}
if let Some(options) = unit_options {
apply_project_options(&mut diff_config, options)?;
}
apply_config_args(&mut diff_config, &args.config)?; apply_config_args(&mut diff_config, &args.config)?;
let mut mapping_config = MappingConfig { let mut mapping_config = MappingConfig {
mappings: Default::default(), mappings: Default::default(),
@@ -316,11 +332,13 @@ fn run_interactive(
target_path: Option<Utf8PlatformPathBuf>, target_path: Option<Utf8PlatformPathBuf>,
base_path: Option<Utf8PlatformPathBuf>, base_path: Option<Utf8PlatformPathBuf>,
project_config: Option<ProjectConfig>, project_config: Option<ProjectConfig>,
unit_options: Option<ProjectOptions>,
) -> Result<()> { ) -> Result<()> {
let Some(symbol_name) = &args.symbol else { bail!("Interactive mode requires a symbol name") }; 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]") let time_format = time::format_description::parse_borrowed::<2>("[hour]:[minute]:[second]")
.context("Failed to parse time format")?; .context("Failed to parse time format")?;
let (diff_obj_config, mapping_config) = build_config_from_args(&args)?; let (diff_obj_config, mapping_config) =
build_config_from_args(&args, project_config.as_ref(), unit_options.as_ref())?;
let mut state = AppState { let mut state = AppState {
jobs: Default::default(), jobs: Default::default(),
waker: Default::default(), waker: Default::default(),
+54 -16
View File
@@ -7,7 +7,7 @@ use objdiff_core::{
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, REPORT_VERSION, ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, REPORT_VERSION,
Report, ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata, Report, ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
}, },
config::path::platform_path, config::{ProjectObject, ProjectOptions, apply_project_options, path::platform_path},
diff, diff,
obj::{self, SectionKind, SymbolFlag, SymbolKind}, obj::{self, SectionKind, SymbolFlag, SymbolKind},
}; };
@@ -83,14 +83,13 @@ pub fn run(args: Args) -> Result<()> {
} }
fn generate(args: GenerateArgs) -> Result<()> { fn generate(args: GenerateArgs) -> Result<()> {
let mut diff_config = diff::DiffObjConfig { let base_diff_config = diff::DiffObjConfig {
function_reloc_diffs: diff::FunctionRelocDiffs::None, function_reloc_diffs: diff::FunctionRelocDiffs::None,
combine_data_sections: true, combine_data_sections: true,
combine_text_sections: true, combine_text_sections: true,
ppc_calculate_pool_relocations: false, ppc_calculate_pool_relocations: false,
..Default::default() ..Default::default()
}; };
apply_config_args(&mut diff_config, &args.config)?;
let output_format = OutputFormat::from_option(args.format.as_deref())?; let output_format = OutputFormat::from_option(args.format.as_deref())?;
let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new(".")); let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new("."));
@@ -101,31 +100,44 @@ fn generate(args: GenerateArgs) -> Result<()> {
Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err), Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err),
None => bail!("No project configuration found"), None => bail!("No project configuration found"),
}; };
info!(
"Generating report for {} units (using {} threads)",
project.units().len(),
if args.deduplicate { 1 } else { rayon::current_num_threads() }
);
let target_obj_dir = let target_obj_dir =
project.target_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding())); project.target_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
let base_obj_dir = let base_obj_dir =
project.base_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding())); project.base_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
let objects = project let project_units = project.units.as_deref().unwrap_or_default();
.units let objects = project_units
.iter() .iter()
.flatten() .enumerate()
.map(|o| { .map(|(idx, o)| {
ObjectConfig::new(o, project_dir, target_obj_dir.as_deref(), base_obj_dir.as_deref()) (
ObjectConfig::new(
o,
project_dir,
target_obj_dir.as_deref(),
base_obj_dir.as_deref(),
),
idx,
)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
info!(
"Generating report for {} units (using {} threads)",
objects.len(),
if args.deduplicate { 1 } else { rayon::current_num_threads() }
);
let start = Instant::now(); let start = Instant::now();
let mut units = vec![]; let mut units = vec![];
let mut existing_functions: HashSet<String> = HashSet::new(); let mut existing_functions: HashSet<String> = HashSet::new();
if args.deduplicate { if args.deduplicate {
// If deduplicating, we need to run single-threaded // If deduplicating, we need to run single-threaded
for object in &objects { for (object, unit_idx) in &objects {
let diff_config = build_unit_diff_config(
&base_diff_config,
project.options.as_ref(),
project_units.get(*unit_idx).and_then(ProjectObject::options),
&args.config,
)?;
if let Some(unit) = report_object(object, &diff_config, Some(&mut existing_functions))? if let Some(unit) = report_object(object, &diff_config, Some(&mut existing_functions))?
{ {
units.push(unit); units.push(unit);
@@ -134,7 +146,15 @@ fn generate(args: GenerateArgs) -> Result<()> {
} else { } else {
let vec = objects let vec = objects
.par_iter() .par_iter()
.map(|object| report_object(object, &diff_config, None)) .map(|(object, unit_idx)| {
let diff_config = build_unit_diff_config(
&base_diff_config,
project.options.as_ref(),
project_units.get(*unit_idx).and_then(ProjectObject::options),
&args.config,
)?;
report_object(object, &diff_config, None)
})
.collect::<Result<Vec<Option<ReportUnit>>>>()?; .collect::<Result<Vec<Option<ReportUnit>>>>()?;
units = vec.into_iter().flatten().collect(); units = vec.into_iter().flatten().collect();
} }
@@ -156,6 +176,24 @@ fn generate(args: GenerateArgs) -> Result<()> {
Ok(()) Ok(())
} }
fn build_unit_diff_config(
base: &diff::DiffObjConfig,
project_options: Option<&ProjectOptions>,
unit_options: Option<&ProjectOptions>,
cli_args: &[String],
) -> Result<diff::DiffObjConfig> {
let mut diff_config = base.clone();
if let Some(options) = project_options {
apply_project_options(&mut diff_config, options)?;
}
if let Some(options) = unit_options {
apply_project_options(&mut diff_config, options)?;
}
// CLI args override project and unit options
apply_config_args(&mut diff_config, cli_args)?;
Ok(diff_config)
}
fn report_object( fn report_object(
object: &ObjectConfig, object: &ObjectConfig,
diff_config: &diff::DiffObjConfig, diff_config: &diff::DiffObjConfig,
+15 -12
View File
@@ -41,7 +41,8 @@ any-arch = [
"dep:regex", "dep:regex",
"dep:similar", "dep:similar",
"dep:syn", "dep:syn",
"dep:encoding_rs" "dep:encoding_rs",
"demangler",
] ]
bindings = [ bindings = [
"dep:prost", "dep:prost",
@@ -88,38 +89,37 @@ std = [
] ]
mips = [ mips = [
"any-arch", "any-arch",
"dep:cpp_demangle",
"dep:cwdemangle",
"dep:rabbitizer", "dep:rabbitizer",
] ]
ppc = [ ppc = [
"any-arch", "any-arch",
"dep:cwdemangle",
"dep:cwextab", "dep:cwextab",
"dep:powerpc", "dep:powerpc",
"dep:rlwinmdec", "dep:rlwinmdec",
] ]
x86 = [ x86 = [
"any-arch", "any-arch",
"dep:cpp_demangle",
"dep:iced-x86", "dep:iced-x86",
"dep:msvc-demangler",
] ]
arm = [ arm = [
"any-arch", "any-arch",
"dep:arm-attr", "dep:arm-attr",
"dep:cpp_demangle",
"dep:unarm", "dep:unarm",
] ]
arm64 = [ arm64 = [
"any-arch", "any-arch",
"dep:cpp_demangle",
"dep:yaxpeax-arch", "dep:yaxpeax-arch",
"dep:yaxpeax-arm", "dep:yaxpeax-arm",
] ]
superh = [ superh = [
"any-arch", "any-arch",
] ]
demangler = [
"dep:cpp_demangle",
"dep:cwdemangle",
"dep:gnuv2_demangle",
"dep:msvc-demangler",
]
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["all"] features = ["all"]
@@ -150,7 +150,6 @@ gimli = { git = "https://github.com/gimli-rs/gimli", rev = "7335f00e7c39fd501511
typed-arena = { version = "2.0", default-features = false, optional = true } typed-arena = { version = "2.0", default-features = false, optional = true }
# ppc # ppc
cwdemangle = { version = "1.0", optional = true }
cwextab = { version = "1.1", optional = true } cwextab = { version = "1.1", optional = true }
powerpc = { version = "0.4", optional = true } powerpc = { version = "0.4", optional = true }
rlwinmdec = { version = "1.1", optional = true } rlwinmdec = { version = "1.1", optional = true }
@@ -159,12 +158,10 @@ rlwinmdec = { version = "1.1", optional = true }
rabbitizer = { version = "2.0.0-alpha.4", default-features = false, features = ["all_extensions"], optional = true } rabbitizer = { version = "2.0.0-alpha.4", default-features = false, features = ["all_extensions"], optional = true }
# x86 # x86
cpp_demangle = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true } iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true }
msvc-demangler = { version = "0.11", optional = true }
# arm # arm
unarm = { version = "1.9", optional = true } unarm = { version = "2.0", optional = true }
arm-attr = { version = "0.2", optional = true } arm-attr = { version = "0.2", optional = true }
# arm64 # arm64
@@ -179,6 +176,12 @@ tempfile = { version = "3.20", optional = true }
time = { version = "0.3", optional = true } time = { version = "0.3", optional = true }
encoding_rs = { version = "0.8.35", optional = true } encoding_rs = { version = "0.8.35", optional = true }
# demangler
cpp_demangle = { version = "0.4", optional = true, default-features = false, features = ["alloc"] }
cwdemangle = { version = "1.0", optional = true }
gnuv2_demangle = { version = "0.4", optional = true }
msvc-demangler = { version = "0.11", optional = true }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", optional = true, features = ["winbase"] } winapi = { version = "0.3", optional = true, features = ["winbase"] }
+98 -12
View File
@@ -25,6 +25,42 @@
} }
] ]
}, },
{
"id": "demangler",
"type": "choice",
"default": "auto",
"name": "Demangler",
"description": "Which demangler should be used to demangle each symbol.",
"items": [
{
"value": "auto",
"name": "Auto",
"description": "Try to automatically guess the mangling format."
},
{
"value": "none",
"name": "None",
"description": "Disable demangling."
},
{
"value": "codewarrior",
"name": "CodeWarrior"
},
{
"value": "itanium",
"name": "Itanium"
},
{
"value": "msvc",
"name": "MSVC"
},
{
"value": "gnu_legacy",
"name": "GNU g++ (Legacy)",
"description": "Use the old GNU mangling ABI. Used up to g++ 2.9.x"
}
]
},
{ {
"id": "analyzeDataFlow", "id": "analyzeDataFlow",
"type": "boolean", "type": "boolean",
@@ -46,6 +82,27 @@
"name": "Space between args", "name": "Space between args",
"description": "Adds a space between arguments in the diff output." "description": "Adds a space between arguments in the diff output."
}, },
{
"id": "showSymbolSizes",
"type": "choice",
"default": "off",
"name": "Show symbol sizes",
"description": "Shows symbol sizes in the symbol view.",
"items": [
{
"value": "off",
"name": "Off"
},
{
"value": "hex",
"name": "Hex"
},
{
"value": "decimal",
"name": "Decimal"
}
]
},
{ {
"id": "combineDataSections", "id": "combineDataSections",
"type": "boolean", "type": "boolean",
@@ -71,20 +128,43 @@
"value": "auto", "value": "auto",
"name": "Auto" "name": "Auto"
}, },
{
"value": "v4",
"name": "ARMv4"
},
{ {
"value": "v4t", "value": "v4t",
"name": "ARMv4T (GBA)" "name": "ARMv4T (GBA)"
}, },
{
"value": "v5t",
"name": "ARMv5T"
},
{ {
"value": "v5te", "value": "v5te",
"name": "ARMv5TE (DS)" "name": "ARMv5TE (DS)"
}, },
{
"value": "v5tej",
"name": "ARMv5TEJ"
},
{
"value": "v6",
"name": "ARMv6"
},
{ {
"value": "v6k", "value": "v6k",
"name": "ARMv6K (3DS)" "name": "ARMv6K (3DS)"
} }
] ]
}, },
{
"id": "arm.vfpV2",
"type": "boolean",
"default": true,
"name": "VFPv2 instructions",
"description": "Adds floating-point instructions from VFPv2."
},
{ {
"id": "arm.unifiedSyntax", "id": "arm.unifiedSyntax",
"type": "boolean", "type": "boolean",
@@ -158,6 +238,10 @@
"value": "o32", "value": "o32",
"name": "O32" "name": "O32"
}, },
{
"value": "o64",
"name": "O64"
},
{ {
"value": "n32", "value": "n32",
"name": "N32" "name": "N32"
@@ -165,6 +249,14 @@
{ {
"value": "n64", "value": "n64",
"name": "N64" "name": "N64"
},
{
"value": "eabi32",
"name": "eabi32"
},
{
"value": "eabi64",
"name": "eabi64"
} }
] ]
}, },
@@ -247,6 +339,8 @@
"name": "General", "name": "General",
"properties": [ "properties": [
"functionRelocDiffs", "functionRelocDiffs",
"demangler",
"showSymbolSizes",
"spaceBetweenArgs", "spaceBetweenArgs",
"combineDataSections", "combineDataSections",
"combineTextSections" "combineTextSections"
@@ -257,6 +351,7 @@
"name": "ARM", "name": "ARM",
"properties": [ "properties": [
"arm.archVersion", "arm.archVersion",
"arm.vfpV2",
"arm.unifiedSyntax", "arm.unifiedSyntax",
"arm.avRegisters", "arm.avRegisters",
"arm.r9Usage", "arm.r9Usage",
@@ -268,26 +363,17 @@
{ {
"id": "mips", "id": "mips",
"name": "MIPS", "name": "MIPS",
"properties": [ "properties": ["mips.abi", "mips.instrCategory", "mips.registerPrefix"]
"mips.abi",
"mips.instrCategory",
"mips.registerPrefix"
]
}, },
{ {
"id": "ppc", "id": "ppc",
"name": "PowerPC", "name": "PowerPC",
"properties": [ "properties": ["ppc.calculatePoolRelocations", "analyzeDataFlow"]
"ppc.calculatePoolRelocations",
"analyzeDataFlow"
]
}, },
{ {
"id": "x86", "id": "x86",
"name": "x86", "name": "x86",
"properties": [ "properties": ["x86.formatter"]
"x86.formatter"
]
} }
] ]
} }
File diff suppressed because it is too large Load Diff
+5 -15
View File
@@ -1,8 +1,4 @@
use alloc::{ use alloc::{format, string::ToString, vec::Vec};
format,
string::{String, ToString},
vec::Vec,
};
use core::cmp::Ordering; use core::cmp::Ordering;
use anyhow::Result; use anyhow::Result;
@@ -14,7 +10,7 @@ use yaxpeax_arm::armv8::a64::{
}; };
use crate::{ use crate::{
arch::Arch, arch::{Arch, OPCODE_INVALID},
diff::{DiffObjConfig, display::InstructionPart}, diff::{DiffObjConfig, display::InstructionPart},
obj::{ obj::{
InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation, InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation,
@@ -60,7 +56,7 @@ impl Arch for ArchArm64 {
ops.push(InstructionRef { ops.push(InstructionRef {
address, address,
size: 4, size: 4,
opcode: u16::MAX, opcode: OPCODE_INVALID,
branch_dest: None, branch_dest: None,
}); });
continue; continue;
@@ -87,7 +83,7 @@ impl Arch for ArchArm64 {
let decoder = InstDecoder::default(); let decoder = InstDecoder::default();
let mut ins = Instruction::default(); let mut ins = Instruction::default();
if decoder.decode_into(&mut ins, &mut reader).is_err() { if decoder.decode_into(&mut ins, &mut reader).is_err() {
cb(InstructionPart::opcode("<invalid>", u16::MAX))?; cb(InstructionPart::opcode("<invalid>", OPCODE_INVALID))?;
return Ok(()); return Ok(());
} }
@@ -108,12 +104,6 @@ impl Arch for ArchArm64 {
Ok(()) Ok(())
} }
fn demangle(&self, name: &str) -> Option<String> {
cpp_demangle::Symbol::new(name)
.ok()
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
}
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> { fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
match flags { match flags {
RelocationFlags::Elf(r_type) => match r_type { RelocationFlags::Elf(r_type) => match r_type {
@@ -2295,7 +2285,7 @@ where Cb: FnMut(InstructionPart<'static>) {
// Opcode is #[repr(u16)], but the tuple variants negate that, so we have to do this instead. // Opcode is #[repr(u16)], but the tuple variants negate that, so we have to do this instead.
const fn opcode_to_u16(opcode: Opcode) -> u16 { const fn opcode_to_u16(opcode: Opcode) -> u16 {
match opcode { match opcode {
Opcode::Invalid => u16::MAX, Opcode::Invalid => OPCODE_INVALID,
Opcode::UDF => 0, Opcode::UDF => 0,
Opcode::MOVN => 1, Opcode::MOVN => 1,
Opcode::MOVK => 2, Opcode::MOVK => 2,
+12 -10
View File
@@ -1,6 +1,6 @@
use alloc::{ use alloc::{
collections::{BTreeMap, BTreeSet}, collections::{BTreeMap, BTreeSet},
string::{String, ToString}, string::ToString,
vec::Vec, vec::Vec,
}; };
@@ -46,8 +46,11 @@ impl ArchMips {
object::FileFlags::None => {} object::FileFlags::None => {}
object::FileFlags::Elf { e_flags, .. } => { object::FileFlags::Elf { e_flags, .. } => {
abi = match e_flags & EF_MIPS_ABI { abi = match e_flags & EF_MIPS_ABI {
elf::EF_MIPS_ABI_O32 | elf::EF_MIPS_ABI_O64 => Abi::O32, elf::EF_MIPS_ABI_O32 => Abi::O32,
elf::EF_MIPS_ABI_EABI32 | elf::EF_MIPS_ABI_EABI64 => Abi::N32, elf::EF_MIPS_ABI_O64 if e_flags & elf::EF_MIPS_ABI2 != 0 => Abi::N64,
elf::EF_MIPS_ABI_O64 => Abi::O64,
elf::EF_MIPS_ABI_EABI32 => Abi::EABI32,
elf::EF_MIPS_ABI_EABI64 => Abi::EABI64,
_ => { _ => {
if e_flags & elf::EF_MIPS_ABI2 != 0 { if e_flags & elf::EF_MIPS_ABI2 != 0 {
Abi::N32 Abi::N32
@@ -170,8 +173,11 @@ impl ArchMips {
.with_abi(match diff_config.mips_abi { .with_abi(match diff_config.mips_abi {
MipsAbi::Auto => self.abi, MipsAbi::Auto => self.abi,
MipsAbi::O32 => Abi::O32, MipsAbi::O32 => Abi::O32,
MipsAbi::O64 => Abi::O64,
MipsAbi::N32 => Abi::N32, MipsAbi::N32 => Abi::N32,
MipsAbi::N64 => Abi::N64, MipsAbi::N64 => Abi::N64,
MipsAbi::Eabi32 => Abi::EABI32,
MipsAbi::Eabi64 => Abi::EABI64,
}) })
} }
@@ -287,6 +293,7 @@ impl Arch for ArchMips {
} }
elf::R_MIPS_PC16 => 0, // PC-relative relocation elf::R_MIPS_PC16 => 0, // PC-relative relocation
R_MIPS15_S3 => ((code & 0x001FFFC0) >> 3) as i64, R_MIPS15_S3 => ((code & 0x001FFFC0) >> 3) as i64,
elf::R_MIPS_GPREL32 => (code as i32 as i64) + self.ri_gp_value as i64,
flags => bail!("Unsupported MIPS implicit relocation {flags:?}"), flags => bail!("Unsupported MIPS implicit relocation {flags:?}"),
}; };
Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Keep, addend })) Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Keep, addend }))
@@ -298,13 +305,6 @@ impl Arch for ArchMips {
} }
} }
fn demangle(&self, name: &str) -> Option<String> {
cpp_demangle::Symbol::new(name)
.ok()
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
.or_else(|| cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default()))
}
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> { fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
match flags { match flags {
RelocationFlags::Elf(r_type) => match r_type { RelocationFlags::Elf(r_type) => match r_type {
@@ -319,6 +319,7 @@ impl Arch for ArchMips {
elf::R_MIPS_GOT16 => Some("R_MIPS_GOT16"), elf::R_MIPS_GOT16 => Some("R_MIPS_GOT16"),
elf::R_MIPS_PC16 => Some("R_MIPS_PC16"), elf::R_MIPS_PC16 => Some("R_MIPS_PC16"),
elf::R_MIPS_CALL16 => Some("R_MIPS_CALL16"), elf::R_MIPS_CALL16 => Some("R_MIPS_CALL16"),
elf::R_MIPS_GPREL32 => Some("R_MIPS_GPREL32"),
R_MIPS15_S3 => Some("R_MIPS15_S3"), R_MIPS15_S3 => Some("R_MIPS15_S3"),
_ => None, _ => None,
}, },
@@ -356,6 +357,7 @@ impl Arch for ArchMips {
while new_address >= symbol.address + 4 while new_address >= symbol.address + 4
&& let Some(data) = section.data_range(new_address - 4, 4) && let Some(data) = section.data_range(new_address - 4, 4)
&& data == [0u8; 4] && data == [0u8; 4]
&& section.relocation_at(next_address - 4, 4).is_none()
{ {
new_address -= 4; new_address -= 4;
} }
+3 -2
View File
@@ -41,6 +41,9 @@ pub mod superh;
#[cfg(feature = "x86")] #[cfg(feature = "x86")]
pub mod x86; pub mod x86;
pub const OPCODE_INVALID: u16 = u16::MAX;
pub const OPCODE_DATA: u16 = u16::MAX - 1;
/// Represents the type of data associated with an instruction /// Represents the type of data associated with an instruction
#[derive(PartialEq)] #[derive(PartialEq)]
pub enum DataType { pub enum DataType {
@@ -368,8 +371,6 @@ pub trait Arch: Any + Debug + Send + Sync {
Ok(None) Ok(None)
} }
fn demangle(&self, _name: &str) -> Option<String> { None }
fn reloc_name(&self, _flags: RelocationFlags) -> Option<&'static str> { None } fn reloc_name(&self, _flags: RelocationFlags) -> Option<&'static str> { None }
fn data_reloc_size(&self, flags: RelocationFlags) -> usize; fn data_reloc_size(&self, flags: RelocationFlags) -> usize;
+1 -12
View File
@@ -308,18 +308,6 @@ impl Arch for ArchPpc {
} }
} }
fn demangle(&self, mut name: &str) -> Option<String> {
if name.starts_with('?') {
msvc_demangler::demangle(name, msvc_demangler::DemangleFlags::llvm()).ok()
} else {
name = name.trim_start_matches('.');
cpp_demangle::Symbol::new(name)
.ok()
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
.or_else(|| cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default()))
}
}
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> { fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
match flags { match flags {
RelocationFlags::Elf(r_type) => match r_type { RelocationFlags::Elf(r_type) => match r_type {
@@ -469,6 +457,7 @@ impl Arch for ArchPpc {
while next_address >= symbol.address + 4 while next_address >= symbol.address + 4
&& let Some(data) = section.data_range(next_address - 4, 4) && let Some(data) = section.data_range(next_address - 4, 4)
&& data == [0u8; 4] && data == [0u8; 4]
&& section.relocation_at(next_address - 4, 4).is_none()
{ {
next_address -= 4; next_address -= 4;
} }
+1 -7
View File
@@ -1,4 +1,4 @@
use alloc::{collections::BTreeMap, format, string::String, vec, vec::Vec}; use alloc::{collections::BTreeMap, format, vec, vec::Vec};
use anyhow::Result; use anyhow::Result;
use object::elf; use object::elf;
@@ -132,12 +132,6 @@ impl Arch for ArchSuperH {
Ok(()) Ok(())
} }
fn demangle(&self, name: &str) -> Option<String> {
cpp_demangle::Symbol::new(name)
.ok()
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
}
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> { fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
match flags { match flags {
RelocationFlags::Elf(r_type) => match r_type { RelocationFlags::Elf(r_type) => match r_type {
+68 -17
View File
@@ -1,4 +1,4 @@
use alloc::{boxed::Box, format, string::String, vec::Vec}; use alloc::{boxed::Box, format, vec::Vec};
use core::cmp::Ordering; use core::cmp::Ordering;
use anyhow::{Context, Result, anyhow, bail}; use anyhow::{Context, Result, anyhow, bail};
@@ -9,7 +9,7 @@ use iced_x86::{
use object::{Endian as _, Object as _, ObjectSection as _, elf, pe}; use object::{Endian as _, Object as _, ObjectSection as _, elf, pe};
use crate::{ use crate::{
arch::{Arch, RelocationOverride, RelocationOverrideTarget}, arch::{Arch, OPCODE_DATA, RelocationOverride, RelocationOverrideTarget},
diff::{DiffObjConfig, X86Formatter, display::InstructionPart}, diff::{DiffObjConfig, X86Formatter, display::InstructionPart},
obj::{InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, Section, Symbol}, obj::{InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, Section, Symbol},
}; };
@@ -89,8 +89,6 @@ impl ArchX86 {
} }
} }
const DATA_OPCODE: u16 = u16::MAX - 1;
impl Arch for ArchX86 { impl Arch for ArchX86 {
fn scan_instructions_internal( fn scan_instructions_internal(
&self, &self,
@@ -121,10 +119,43 @@ impl Arch for ArchX86 {
out.push(InstructionRef { out.push(InstructionRef {
address, address,
size: size as u8, size: size as u8,
opcode: DATA_OPCODE, opcode: OPCODE_DATA,
branch_dest: None, branch_dest: None,
}); });
reloc_iter.next(); reloc_iter.next();
// support .byte arrays after jump tables (they're typically known as indirect tables)
let indirect_array_address = address.wrapping_add(size as u64);
let indirect_array_pos = decoder.position();
let max_size = code.len().saturating_sub(indirect_array_pos);
let indirect_array_size = reloc_iter
.peek()
.map(|next_reloc| {
next_reloc.address.saturating_sub(indirect_array_address)
as usize
})
.unwrap_or(max_size)
.min(max_size);
if indirect_array_size > 0 {
for i in 0..indirect_array_size {
out.push(InstructionRef {
address: indirect_array_address + i as u64,
size: 1,
opcode: OPCODE_DATA,
branch_dest: None,
});
}
// move decoder to after the array (there can be multiple jump+indirect tables in one function)
let _ =
decoder.set_position(indirect_array_pos + indirect_array_size);
decoder.set_ip(indirect_array_address + indirect_array_size as u64);
}
continue 'outer; continue 'outer;
} }
} }
@@ -154,13 +185,14 @@ impl Arch for ArchX86 {
diff_config: &DiffObjConfig, diff_config: &DiffObjConfig,
cb: &mut dyn FnMut(InstructionPart) -> Result<()>, cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
) -> Result<()> { ) -> Result<()> {
if resolved.ins_ref.opcode == DATA_OPCODE { if resolved.ins_ref.opcode == OPCODE_DATA {
let (mnemonic, imm) = match resolved.ins_ref.size { let (mnemonic, imm) = match resolved.ins_ref.size {
1 => (".byte", resolved.code[0] as u64),
2 => (".word", self.endianness.read_u16_bytes(resolved.code.try_into()?) as u64), 2 => (".word", self.endianness.read_u16_bytes(resolved.code.try_into()?) as u64),
4 => (".dword", self.endianness.read_u32_bytes(resolved.code.try_into()?) as u64), 4 => (".dword", self.endianness.read_u32_bytes(resolved.code.try_into()?) as u64),
_ => bail!("Unsupported x86 inline data size {}", resolved.ins_ref.size), _ => bail!("Unsupported x86 inline data size {}", resolved.ins_ref.size),
}; };
cb(InstructionPart::opcode(mnemonic, DATA_OPCODE))?; cb(InstructionPart::opcode(mnemonic, OPCODE_DATA))?;
if resolved.relocation.is_some() { if resolved.relocation.is_some() {
cb(InstructionPart::reloc())?; cb(InstructionPart::reloc())?;
} else { } else {
@@ -268,16 +300,6 @@ impl Arch for ArchX86 {
Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Keep, addend })) Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Keep, addend }))
} }
fn demangle(&self, name: &str) -> Option<String> {
if name.starts_with('?') {
msvc_demangler::demangle(name, msvc_demangler::DemangleFlags::llvm()).ok()
} else {
cpp_demangle::Symbol::new(name)
.ok()
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
}
}
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> { fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
match self.arch { match self.arch {
Architecture::X86 => match flags { Architecture::X86 => match flags {
@@ -791,4 +813,33 @@ mod test {
.unwrap(); .unwrap();
assert_eq!(parts, &[InstructionPart::opcode("call", opcode), InstructionPart::reloc()]); assert_eq!(parts, &[InstructionPart::opcode("call", opcode), InstructionPart::reloc()]);
} }
#[test]
fn test_display_1_byte_inline_data() {
let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
let code = [0xAB];
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef {
address: 0x1234,
size: 1,
opcode: OPCODE_DATA,
branch_dest: None,
},
code: &code,
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
assert_eq!(parts, &[
InstructionPart::opcode(".byte", OPCODE_DATA),
InstructionPart::unsigned(0xABu64),
]);
}
} }
+60
View File
@@ -1,6 +1,7 @@
pub mod path; pub mod path;
use alloc::{ use alloc::{
borrow::Cow,
collections::BTreeMap, collections::BTreeMap,
string::{String, ToString}, string::{String, ToString},
vec::Vec, vec::Vec,
@@ -45,6 +46,8 @@ pub struct ProjectConfig {
pub units: Option<Vec<ProjectObject>>, pub units: Option<Vec<ProjectObject>>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub progress_categories: Option<Vec<ProjectProgressCategory>>, pub progress_categories: Option<Vec<ProjectProgressCategory>>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub options: Option<ProjectOptions>,
} }
impl ProjectConfig { impl ProjectConfig {
@@ -116,6 +119,8 @@ pub struct ProjectObject {
pub metadata: Option<ProjectObjectMetadata>, pub metadata: Option<ProjectObjectMetadata>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub symbol_mappings: Option<BTreeMap<String, String>>, pub symbol_mappings: Option<BTreeMap<String, String>>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub options: Option<ProjectOptions>,
} }
#[derive(Default, Clone)] #[derive(Default, Clone)]
@@ -143,6 +148,15 @@ pub struct ProjectProgressCategory {
pub name: String, pub name: String,
} }
pub type ProjectOptions = BTreeMap<String, ProjectOptionValue>;
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(untagged))]
pub enum ProjectOptionValue {
Bool(bool),
String(String),
}
impl ProjectObject { impl ProjectObject {
pub fn name(&self) -> &str { pub fn name(&self) -> &str {
if let Some(name) = &self.name { if let Some(name) = &self.name {
@@ -179,6 +193,8 @@ impl ProjectObject {
pub fn auto_generated(&self) -> Option<bool> { pub fn auto_generated(&self) -> Option<bool> {
self.metadata.as_ref().and_then(|m| m.auto_generated) self.metadata.as_ref().and_then(|m| m.auto_generated)
} }
pub fn options(&self) -> Option<&ProjectOptions> { self.options.as_ref() }
} }
#[derive(Default, Clone, Eq, PartialEq)] #[derive(Default, Clone, Eq, PartialEq)]
@@ -310,3 +326,47 @@ pub fn build_globset(vec: &[Glob]) -> Result<GlobSet, globset::Error> {
} }
builder.build() builder.build()
} }
#[cfg(feature = "any-arch")]
pub fn apply_project_options(
diff_config: &mut crate::diff::DiffObjConfig,
options: &ProjectOptions,
) -> Result<()> {
use core::str::FromStr;
use crate::diff::{ConfigEnum, ConfigPropertyId, ConfigPropertyKind};
let mut result = Ok(());
for (key, value) in options.iter() {
let property_id = ConfigPropertyId::from_str(key)
.map_err(|()| anyhow!("Invalid configuration property: {key}"))?;
let value = match value {
ProjectOptionValue::Bool(value) => Cow::Borrowed(if *value { "true" } else { "false" }),
ProjectOptionValue::String(value) => Cow::Borrowed(value.as_str()),
};
if diff_config.set_property_value_str(property_id, &value).is_err() {
if result.is_err() {
// Already returning an error, skip further errors
continue;
}
let mut expected = String::new();
match property_id.kind() {
ConfigPropertyKind::Boolean => expected.push_str("true, false"),
ConfigPropertyKind::Choice(variants) => {
for (idx, variant) in variants.iter().enumerate() {
if idx > 0 {
expected.push_str(", ");
}
expected.push_str(variant.value);
}
}
}
result = Err(anyhow!(
"Invalid value for {}. Expected one of: {}",
property_id.name(),
expected
));
}
}
result
}
+2 -6
View File
@@ -14,7 +14,7 @@ use super::{
}; };
use crate::obj::{ use crate::obj::{
InstructionArg, InstructionArgValue, InstructionRef, Object, ResolvedInstructionRef, InstructionArg, InstructionArgValue, InstructionRef, Object, ResolvedInstructionRef,
ResolvedRelocation, ResolvedSymbol, SymbolFlag, SymbolKind, ResolvedRelocation, ResolvedSymbol, SymbolKind,
}; };
pub fn no_diff_code( pub fn no_diff_code(
@@ -333,11 +333,7 @@ fn reloc_eq(
|| display_ins_data_literals(left_obj, left_ins) || display_ins_data_literals(left_obj, left_ins)
== display_ins_data_literals(right_obj, right_ins)) == display_ins_data_literals(right_obj, right_ins))
} }
(None, Some(_)) => { (Some(_), None) | (None, Some(_)) | (None, None) => symbol_name_addend_matches,
// Match if possibly stripped weak symbol
symbol_name_addend_matches && right_reloc.symbol.flags.contains(SymbolFlag::Weak)
}
(Some(_), None) | (None, None) => symbol_name_addend_matches,
} }
} }
+103 -41
View File
@@ -5,7 +5,7 @@ use anyhow::{Result, anyhow};
use similar::{Algorithm, capture_diff_slices, get_diff_ratio}; use similar::{Algorithm, capture_diff_slices, get_diff_ratio};
use super::{ use super::{
DataDiff, DataDiffKind, DataRelocationDiff, ObjectDiff, SectionDiff, SymbolDiff, DataDiff, DataDiffKind, DataDiffRow, DataRelocationDiff, ObjectDiff, SectionDiff, SymbolDiff,
code::{address_eq, section_name_eq}, code::{address_eq, section_name_eq},
}; };
use crate::obj::{Object, Relocation, ResolvedRelocation, Symbol, SymbolFlag, SymbolKind}; use crate::obj::{Object, Relocation, ResolvedRelocation, Symbol, SymbolFlag, SymbolKind};
@@ -67,11 +67,7 @@ fn reloc_eq(
section_name_eq(left_obj, right_obj, sl, sr) section_name_eq(left_obj, right_obj, sl, sr)
&& (symbol_name_addend_matches || address_eq(left, right)) && (symbol_name_addend_matches || address_eq(left, right))
} }
(None, Some(_)) => { (Some(_), None) | (None, Some(_)) | (None, None) => symbol_name_addend_matches,
// Match if possibly stripped weak symbol
symbol_name_addend_matches && right.symbol.flags.contains(SymbolFlag::Weak)
}
(Some(_), None) | (None, None) => symbol_name_addend_matches,
} }
} }
@@ -111,14 +107,12 @@ fn diff_data_range(left_data: &[u8], right_data: &[u8]) -> (f32, Vec<DataDiff>,
left_data_diff.push(DataDiff { left_data_diff.push(DataDiff {
data: left_data[..len.min(left_data.len())].to_vec(), data: left_data[..len.min(left_data.len())].to_vec(),
kind, kind,
len, size: len,
..Default::default()
}); });
right_data_diff.push(DataDiff { right_data_diff.push(DataDiff {
data: right_data[..len.min(right_data.len())].to_vec(), data: right_data[..len.min(right_data.len())].to_vec(),
kind, kind,
len, size: len,
..Default::default()
}); });
if kind == DataDiffKind::Replace { if kind == DataDiffKind::Replace {
match left_len.cmp(&right_len) { match left_len.cmp(&right_len) {
@@ -127,14 +121,12 @@ fn diff_data_range(left_data: &[u8], right_data: &[u8]) -> (f32, Vec<DataDiff>,
left_data_diff.push(DataDiff { left_data_diff.push(DataDiff {
data: vec![], data: vec![],
kind: DataDiffKind::Insert, kind: DataDiffKind::Insert,
len, size: len,
..Default::default()
}); });
right_data_diff.push(DataDiff { right_data_diff.push(DataDiff {
data: right_data[left_len..right_len].to_vec(), data: right_data[left_len..right_len].to_vec(),
kind: DataDiffKind::Insert, kind: DataDiffKind::Insert,
len, size: len,
..Default::default()
}); });
} }
Ordering::Greater => { Ordering::Greater => {
@@ -142,14 +134,12 @@ fn diff_data_range(left_data: &[u8], right_data: &[u8]) -> (f32, Vec<DataDiff>,
left_data_diff.push(DataDiff { left_data_diff.push(DataDiff {
data: left_data[right_len..left_len].to_vec(), data: left_data[right_len..left_len].to_vec(),
kind: DataDiffKind::Delete, kind: DataDiffKind::Delete,
len, size: len,
..Default::default()
}); });
right_data_diff.push(DataDiff { right_data_diff.push(DataDiff {
data: vec![], data: vec![],
kind: DataDiffKind::Delete, kind: DataDiffKind::Delete,
len, size: len,
..Default::default()
}); });
} }
Ordering::Equal => {} Ordering::Equal => {}
@@ -219,16 +209,17 @@ fn diff_data_relocs_for_range<'left, 'right>(
pub fn no_diff_data_section(obj: &Object, section_idx: usize) -> Result<SectionDiff> { pub fn no_diff_data_section(obj: &Object, section_idx: usize) -> Result<SectionDiff> {
let section = &obj.sections[section_idx]; let section = &obj.sections[section_idx];
let len = section.data.len();
let data = &section.data[0..len];
let data_diff = let data_diff = vec![DataDiff {
vec![DataDiff { data: data.to_vec(), kind: DataDiffKind::None, len, ..Default::default() }]; data: section.data.0.clone(),
kind: DataDiffKind::None,
size: section.data.len(),
}];
let mut reloc_diffs = Vec::new(); let mut reloc_diffs = Vec::new();
for reloc in section.relocations.iter() { for reloc in section.relocations.iter() {
let reloc_len = obj.arch.data_reloc_size(reloc.flags); let reloc_len = obj.arch.data_reloc_size(reloc.flags);
let range = reloc.address as usize..reloc.address as usize + reloc_len; let range = reloc.address..reloc.address + reloc_len as u64;
reloc_diffs.push(DataRelocationDiff { reloc_diffs.push(DataRelocationDiff {
reloc: reloc.clone(), reloc: reloc.clone(),
kind: DataDiffKind::None, kind: DataDiffKind::None,
@@ -279,8 +270,7 @@ pub fn diff_data_section(
) { ) {
if let Some(left_reloc) = left_reloc { if let Some(left_reloc) = left_reloc {
let len = left_obj.arch.data_reloc_size(left_reloc.relocation.flags); let len = left_obj.arch.data_reloc_size(left_reloc.relocation.flags);
let range = left_reloc.relocation.address as usize let range = left_reloc.relocation.address..left_reloc.relocation.address + len as u64;
..left_reloc.relocation.address as usize + len;
left_reloc_diffs.push(DataRelocationDiff { left_reloc_diffs.push(DataRelocationDiff {
reloc: left_reloc.relocation.clone(), reloc: left_reloc.relocation.clone(),
kind: diff_kind, kind: diff_kind,
@@ -289,8 +279,7 @@ pub fn diff_data_section(
} }
if let Some(right_reloc) = right_reloc { if let Some(right_reloc) = right_reloc {
let len = right_obj.arch.data_reloc_size(right_reloc.relocation.flags); let len = right_obj.arch.data_reloc_size(right_reloc.relocation.flags);
let range = right_reloc.relocation.address as usize let range = right_reloc.relocation.address..right_reloc.relocation.address + len as u64;
..right_reloc.relocation.address as usize + len;
right_reloc_diffs.push(DataRelocationDiff { right_reloc_diffs.push(DataRelocationDiff {
reloc: right_reloc.relocation.clone(), reloc: right_reloc.relocation.clone(),
kind: diff_kind, kind: diff_kind,
@@ -345,9 +334,11 @@ pub fn no_diff_data_symbol(obj: &Object, symbol_index: usize) -> Result<SymbolDi
let range = start as usize..end as usize; let range = start as usize..end as usize;
let data = &section.data[range.clone()]; let data = &section.data[range.clone()];
let len = symbol.size as usize; let data_diff = vec![DataDiff {
let data_diff = data: data.to_vec(),
vec![DataDiff { data: data.to_vec(), kind: DataDiffKind::None, len, ..Default::default() }]; kind: DataDiffKind::None,
size: symbol.size as usize,
}];
let mut reloc_diffs = Vec::new(); let mut reloc_diffs = Vec::new();
for reloc in section.relocations.iter() { for reloc in section.relocations.iter() {
@@ -355,7 +346,7 @@ pub fn no_diff_data_symbol(obj: &Object, symbol_index: usize) -> Result<SymbolDi
continue; continue;
} }
let reloc_len = obj.arch.data_reloc_size(reloc.flags); let reloc_len = obj.arch.data_reloc_size(reloc.flags);
let range = reloc.address as usize..reloc.address as usize + reloc_len; let range = reloc.address..reloc.address + reloc_len as u64;
reloc_diffs.push(DataRelocationDiff { reloc_diffs.push(DataRelocationDiff {
reloc: reloc.clone(), reloc: reloc.clone(),
kind: DataDiffKind::None, kind: DataDiffKind::None,
@@ -363,12 +354,12 @@ pub fn no_diff_data_symbol(obj: &Object, symbol_index: usize) -> Result<SymbolDi
}); });
} }
let data_rows = build_data_diff_rows(&data_diff, &reloc_diffs, symbol.address);
Ok(SymbolDiff { Ok(SymbolDiff {
target_symbol: None, target_symbol: None,
match_percent: None, match_percent: None,
diff_score: None, diff_score: None,
data_diff, data_rows,
data_reloc_diff: reloc_diffs,
..Default::default() ..Default::default()
}) })
} }
@@ -454,8 +445,8 @@ pub fn diff_data_symbol(
if let Some(left_reloc) = left_reloc { if let Some(left_reloc) = left_reloc {
let len = left_obj.arch.data_reloc_size(left_reloc.relocation.flags); let len = left_obj.arch.data_reloc_size(left_reloc.relocation.flags);
let range = left_reloc.relocation.address as usize let range =
..left_reloc.relocation.address as usize + len; left_reloc.relocation.address..left_reloc.relocation.address + len as u64;
left_reloc_diffs.push(DataRelocationDiff { left_reloc_diffs.push(DataRelocationDiff {
reloc: left_reloc.relocation.clone(), reloc: left_reloc.relocation.clone(),
kind: diff_kind, kind: diff_kind,
@@ -464,8 +455,8 @@ pub fn diff_data_symbol(
} }
if let Some(right_reloc) = right_reloc { if let Some(right_reloc) = right_reloc {
let len = right_obj.arch.data_reloc_size(right_reloc.relocation.flags); let len = right_obj.arch.data_reloc_size(right_reloc.relocation.flags);
let range = right_reloc.relocation.address as usize let range =
..right_reloc.relocation.address as usize + len; right_reloc.relocation.address..right_reloc.relocation.address + len as u64;
right_reloc_diffs.push(DataRelocationDiff { right_reloc_diffs.push(DataRelocationDiff {
reloc: right_reloc.relocation.clone(), reloc: right_reloc.relocation.clone(),
kind: diff_kind, kind: diff_kind,
@@ -486,23 +477,29 @@ pub fn diff_data_symbol(
} }
} }
left_reloc_diffs
.sort_by(|a, b| a.range.start.cmp(&b.range.start).then(a.range.end.cmp(&b.range.end)));
right_reloc_diffs
.sort_by(|a, b| a.range.start.cmp(&b.range.start).then(a.range.end.cmp(&b.range.end)));
let match_percent = match_ratio * 100.0; let match_percent = match_ratio * 100.0;
let left_rows = build_data_diff_rows(&left_data_diff, &left_reloc_diffs, left_symbol.address);
let right_rows =
build_data_diff_rows(&right_data_diff, &right_reloc_diffs, right_symbol.address);
Ok(( Ok((
SymbolDiff { SymbolDiff {
target_symbol: Some(right_symbol_idx), target_symbol: Some(right_symbol_idx),
match_percent: Some(match_percent), match_percent: Some(match_percent),
diff_score: None, diff_score: None,
data_diff: left_data_diff, data_rows: left_rows,
data_reloc_diff: left_reloc_diffs,
..Default::default() ..Default::default()
}, },
SymbolDiff { SymbolDiff {
target_symbol: Some(left_symbol_idx), target_symbol: Some(left_symbol_idx),
match_percent: Some(match_percent), match_percent: Some(match_percent),
diff_score: None, diff_score: None,
data_diff: right_data_diff, data_rows: right_rows,
data_reloc_diff: right_reloc_diffs,
..Default::default() ..Default::default()
}, },
)) ))
@@ -593,3 +590,68 @@ fn symbols_matching_section(
&& !s.flags.contains(SymbolFlag::Ignored) && !s.flags.contains(SymbolFlag::Ignored)
}) })
} }
pub const BYTES_PER_ROW: usize = 16;
fn build_data_diff_row(
data_diffs: &[DataDiff],
reloc_diffs: &[DataRelocationDiff],
symbol_address: u64,
row_index: usize,
) -> DataDiffRow {
let row_start = row_index * BYTES_PER_ROW;
let row_end = row_start + BYTES_PER_ROW;
let mut row_diff = DataDiffRow {
address: symbol_address + row_start as u64,
segments: Vec::new(),
relocations: Vec::new(),
};
// Collect all segments that overlap with this row
let mut current_offset = 0;
for diff in data_diffs {
let diff_end = current_offset + diff.size;
if current_offset < row_end && diff_end > row_start {
let start_in_diff = row_start.saturating_sub(current_offset);
let end_in_diff = row_end.min(diff_end) - current_offset;
if start_in_diff < end_in_diff {
let data_slice = if diff.data.is_empty() {
Vec::new()
} else {
diff.data[start_in_diff..end_in_diff.min(diff.data.len())].to_vec()
};
row_diff.segments.push(DataDiff {
data: data_slice,
kind: diff.kind,
size: end_in_diff - start_in_diff,
});
}
}
current_offset = diff_end;
if current_offset >= row_start + BYTES_PER_ROW {
break;
}
}
// Collect all relocations that overlap with this row
let row_end_absolute = row_diff.address + BYTES_PER_ROW as u64;
row_diff.relocations = reloc_diffs
.iter()
.filter(|rd| rd.range.start < row_end_absolute && rd.range.end > row_diff.address)
.cloned()
.collect();
row_diff
}
fn build_data_diff_rows(
segments: &[DataDiff],
relocations: &[DataRelocationDiff],
symbol_address: u64,
) -> Vec<DataDiffRow> {
let total_len = segments.iter().map(|s| s.size as u64).sum::<u64>();
let num_rows = total_len.div_ceil(BYTES_PER_ROW as u64) as usize;
(0..num_rows)
.map(|row_index| build_data_diff_row(segments, relocations, symbol_address, row_index))
.collect()
}
+51
View File
@@ -0,0 +1,51 @@
use alloc::string::String;
use crate::diff::Demangler;
#[cfg(feature = "demangler")]
impl Demangler {
pub fn demangle(&self, name: &str) -> Option<String> {
match self {
Demangler::None => None,
Demangler::Codewarrior => Self::demangle_codewarrior(name),
Demangler::Msvc => Self::demangle_msvc(name),
Demangler::Itanium => Self::demangle_itanium(name),
Demangler::GnuLegacy => Self::demangle_gnu_legacy(name),
Demangler::Auto => {
// Try to guess
if name.starts_with('?') {
Self::demangle_msvc(name)
} else {
Self::demangle_codewarrior(name)
.or_else(|| Self::demangle_gnu_legacy(name))
.or_else(|| Self::demangle_itanium(name))
}
}
}
}
fn demangle_codewarrior(name: &str) -> Option<String> {
cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default())
}
fn demangle_msvc(name: &str) -> Option<String> {
msvc_demangler::demangle(name, msvc_demangler::DemangleFlags::llvm()).ok()
}
fn demangle_itanium(name: &str) -> Option<String> {
let name = name.trim_start_matches('.');
cpp_demangle::Symbol::new(name)
.ok()
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
}
fn demangle_gnu_legacy(name: &str) -> Option<String> {
let name = name.trim_start_matches('.');
gnuv2_demangle::demangle(name, &gnuv2_demangle::DemangleConfig::new()).ok()
}
}
#[cfg(not(feature = "demangler"))]
impl Demangler {
pub fn demangle(&self, _name: &str) -> Option<String> { None }
}
+59 -2
View File
@@ -12,7 +12,10 @@ use itertools::Itertools;
use regex::Regex; use regex::Regex;
use crate::{ use crate::{
diff::{DiffObjConfig, InstructionDiffKind, InstructionDiffRow, ObjectDiff, SymbolDiff}, diff::{
DataDiffKind, DataDiffRow, DiffObjConfig, InstructionDiffKind, InstructionDiffRow,
ObjectDiff, SymbolDiff, data::resolve_relocation,
},
obj::{ obj::{
FlowAnalysisValue, InstructionArg, InstructionArgValue, Object, ParsedInstruction, FlowAnalysisValue, InstructionArg, InstructionArgValue, Object, ParsedInstruction,
ResolvedInstructionRef, ResolvedRelocation, SectionFlag, SectionKind, Symbol, SymbolFlag, ResolvedInstructionRef, ResolvedRelocation, SectionFlag, SectionKind, Symbol, SymbolFlag,
@@ -178,7 +181,7 @@ pub fn display_row(
} }
cb(DiffTextSegment { cb(DiffTextSegment {
text: DiffText::Address(ins_ref.address.saturating_sub(resolved.symbol.address)), text: DiffText::Address(ins_ref.address.saturating_sub(resolved.symbol.address)),
color: base_color, color: DiffTextColor::Dim,
pad_to: 5, pad_to: 5,
})?; })?;
if let Some(branch) = &ins_row.branch_from { if let Some(branch) = &ins_row.branch_from {
@@ -494,6 +497,57 @@ pub fn relocation_context(
out out
} }
pub fn data_row_hover(obj: &Object, diff_row: &DataDiffRow) -> Vec<HoverItem> {
let mut out = Vec::new();
let mut prev_reloc = None;
let mut first = true;
for reloc_diff in diff_row.relocations.iter() {
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);
if first {
first = false;
} else {
out.push(HoverItem::Separator);
}
let reloc = resolve_relocation(&obj.symbols, reloc);
let color = match reloc_diff.kind {
DataDiffKind::None => HoverItemColor::Normal,
DataDiffKind::Replace => HoverItemColor::Special,
DataDiffKind::Delete => HoverItemColor::Delete,
DataDiffKind::Insert => HoverItemColor::Insert,
};
out.append(&mut relocation_hover(obj, reloc, Some(color)));
}
out
}
pub fn data_row_context(obj: &Object, diff_row: &DataDiffRow) -> Vec<ContextItem> {
let mut out = Vec::new();
let mut prev_reloc = None;
for reloc_diff in diff_row.relocations.iter() {
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 reloc = resolve_relocation(&obj.symbols, reloc);
out.append(&mut relocation_context(obj, reloc, None));
}
out
}
pub fn relocation_hover( pub fn relocation_hover(
obj: &Object, obj: &Object,
reloc: ResolvedRelocation, reloc: ResolvedRelocation,
@@ -677,6 +731,7 @@ pub struct SectionDisplay {
pub size: u64, pub size: u64,
pub match_percent: Option<f32>, pub match_percent: Option<f32>,
pub symbols: Vec<SectionDisplaySymbol>, pub symbols: Vec<SectionDisplaySymbol>,
pub kind: SectionKind,
} }
pub fn display_sections( pub fn display_sections(
@@ -755,6 +810,7 @@ pub fn display_sections(
size: section.size, size: section.size,
match_percent: section_diff.match_percent, match_percent: section_diff.match_percent,
symbols, symbols,
kind: section.kind,
}); });
} else { } else {
// Don't sort, preserve order of absolute symbols // Don't sort, preserve order of absolute symbols
@@ -764,6 +820,7 @@ pub fn display_sections(
size: 0, size: 0,
match_percent: None, match_percent: None,
symbols, symbols,
kind: SectionKind::Common,
}); });
} }
} }

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