Compare commits

...

35 Commits

Author SHA1 Message Date
Luke Street f7c3501eae Version v3.0.0-beta.8 2025-05-13 23:15:46 -06:00
Luke Street 07ef93f16a Ignore extern symbols with symbol name lookups
When searching for a symbol by name, only look at
symbols that are defined within the object,
ignoring extern symbols (symbols without section).

Fixes #180
Fixes #181
2025-05-13 22:51:26 -06:00
Luke Street 8e8ab6bef8 Skip label symbols when inferring symbol sizes
COFF objects in particular don't contain the size of
symbols. We infer the size of these symbols by
extending them to the next symbol. If a tool emits
symbols for branch targets, this causes the inferred
size to be too small.

This checks if a symbol starts with a certain prefix
(right now, just .L or LAB_), and skips over it
during symbol size inference.

Resolves #174
2025-05-13 22:36:02 -06:00
Luke Street e865f3d598 Fix symbol mapping mismatched match %
We have specific diff logic that relies on knowing
which object is the target object, and which is the
base. generate_mapping_symbols was designed in such
a way that it would reverse the target/base, leading
to a match percent shown that's different when it
gets applied.

Fixes #200
2025-05-13 21:57:16 -06:00
Luke Street 2b13e9886a Fix hidden symbol regression
The flagset .contains check doesn't work like this.

Fixes #199
2025-05-13 21:37:29 -06:00
Luke Street 1750af736a Try target-feature=+crt-static 2025-05-13 21:28:57 -06:00
LagoLunatic 731b604c24 Fix highlighting of signed vs unsigned arguments (#202)
* Fix signed and unsigned arguments not being considered equal when highlighting

* Remove unused Eq derive
2025-05-13 14:03:00 -06:00
Luke Street 2d643eb071 Add scratch.preset_id to config.schema.json 2025-05-09 12:51:18 -06:00
Luke Street 0c48d711c7 Improve local branch relocation handling
Reworks the local-branch handling logic to be more
unified: scan_instructions does all the work up front,
and process_instruction / display_instruction can
simply use the calculated branch destination instead
of performing their own is-relocation-target-
function-local checks.

(Hopefully) Fixes #192
2025-05-07 22:53:10 -06:00
Luke Street 34220a8e26 Add address to ReportItem, stabilize sections/functions ordering 2025-05-07 17:36:49 -06:00
Luke Street 0c9e5526d4 Combine sections when generating report 2025-05-07 16:47:20 -06:00
Luke Street 3db0727469 Omit match % for right sections, improve multi-section diffing
Fixes #120
2025-05-07 16:47:20 -06:00
Luke Street 8b5bf21f38 Mark combined sections as SectionKind::Unknown 2025-05-07 16:45:00 -06:00
Luke Street b77df77000 Minor cleanup, remove Section::symbol_data 2025-05-07 16:43:34 -06:00
Luke Street 7e08f9715b Update dependencies 2025-05-07 16:42:02 -06:00
Luke Street 3c05852d00 Document SuperH support 2025-05-06 23:25:29 -06:00
Luke Street a51ff44be1 Fix superh wasm (no_std) build 2025-05-06 23:21:07 -06:00
Luke Street d225cac205 Add superh feature to wasm build 2025-05-06 23:14:38 -06:00
Luke Street 737b3782db ci: Add id-token write permission 2025-05-06 22:51:11 -06:00
sozud 1d782243e0 Add data info for superh (#198)
* Add data info for superh

* fmt

* Fetch symbol data dynamically from resolved.section

* Remove unused var

---------

Co-authored-by: Luke Street <luke@street.dev>
2025-05-06 22:30:53 -06:00
Luke Street a1499f475d Update test snapshot 2025-05-06 22:07:21 -06:00
LagoLunatic f263e490e3 Combine data/text sections: Pad sections to alignment (#197)
* Combine data/text sections: Pad all sections to 4-byte minimum alignment

* Update x86 test snapshot

* Read and store object section alignment

* Combine data/text sections: Pad sections to more than 4-byte alignment if they have alignment specified
2025-05-06 21:47:08 -06:00
Aetias d0e6c5c057 Display branch destinations after PC-relative loads (#194) 2025-05-06 21:45:03 -06:00
LagoLunatic e1c51ac297 ARM: Fix "Combine text sections" confusing code and data (#195)
* ARM: Fix parsing of mapping symbols when "Combine text sections" is enabled

* Add test
2025-04-26 11:14:16 -06:00
Luke Street 39b1b49985 cli: Add --config arg to report generate 2025-04-22 18:13:12 -06:00
Luke Street 6c7160ab7e Use rabbitizer 2.0.0-alpha.1 2025-04-22 18:13:12 -06:00
sozud 644d4762f0 Preliminary SuperH support (#186)
* Preliminary SuperH support

* fixes

* clippy

* clippy
2025-04-17 14:20:30 -06:00
LagoLunatic b40fae5140 Allow copying Shift JIS encoded string literals (#189)
* Update openssl and tokio for Cargo deny

* Allow copying Shift JIS encoded strings

* Override data type label for Shift JIS strings
2025-04-17 10:07:05 -06:00
LagoLunatic fbf85632ab PPC: Detect unpooled string literal references (#188)
* Update openssl and tokio for Cargo deny

* PPC: Detect unpooled string literal references
2025-04-16 23:33:55 -06:00
Luke Street 73a89d2768 Version v3.0.0-beta.6 2025-04-02 11:03:40 -06:00
Luke Street a162c2f840 Fix to_string_in_format_args warning 2025-04-01 23:36:47 -06:00
Luke Street a474b27d55 Update dependencies 2025-04-01 23:33:17 -06:00
Luke Street 3d7f2b70dc Replace some git dependencies with cargo registry 2025-04-01 22:56:16 -06:00
Luke Street fe886f862d ci: Auto cargo publish / npm publish 2025-04-01 21:49:01 -06:00
Luke Street 2bcbc34850 wasm: Improve API error handling 2025-04-01 21:45:13 -06:00
69 changed files with 5511 additions and 1139 deletions
+4 -5
View File
@@ -1,5 +1,4 @@
[target.x86_64-pc-windows-msvc]
linker = "rust-lld"
[target.aarch64-pc-windows-msvc]
linker = "rust-lld"
# statically link the C runtime so the executable does not depend on
# that shared/dynamic library.
[target.'cfg(all(target_env = "msvc", target_os = "windows"))']
rustflags = ["-C", "target-feature=+crt-static"]
+85 -8
View File
@@ -8,6 +8,10 @@ on:
- "LICENSE*"
workflow_dispatch:
permissions:
# For npm publish provenance
id-token: write
env:
BUILD_PROFILE: release-lto
CARGO_INCREMENTAL: 0
@@ -72,7 +76,7 @@ jobs:
name: Test
strategy:
matrix:
platform: [ubuntu-latest, windows-latest, macos-latest]
platform: [ ubuntu-latest, windows-latest, macos-latest ]
fail-fast: false
runs-on: ${{ matrix.platform }}
steps:
@@ -249,22 +253,30 @@ jobs:
components: rust-src
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm -C objdiff-wasm install
run: npm -C objdiff-wasm ci
- name: Build
run: npm -C objdiff-wasm run build
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: wasm
path: objdiff-wasm/dist/
if-no-files-found: error
release:
name: Release
check-version:
name: Check package versions
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [build-cli, build-gui]
permissions:
contents: write
needs: [ build-cli, build-gui, build-wasm ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check git tag against Cargo version
- name: Check git tag against package versions
shell: bash
run: |
set -eou pipefail
@@ -276,9 +288,24 @@ jobs:
echo "::error::Git tag doesn't match the Cargo version! ($tag != $version)"
exit 1
fi
version="v$(jq -r .version objdiff-wasm/package.json)"
if [ "$tag" != "$version" ]; then
echo "::error::Git tag doesn't match the npm version! ($tag != $version)"
exit 1
fi
release-github:
name: Release (GitHub)
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [ check-version ]
permissions:
contents: write
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
pattern: objdiff-*
path: artifacts
- name: Rename artifacts
working-directory: artifacts
@@ -308,3 +335,53 @@ jobs:
files: out/*
draft: true
generate_release_notes: true
release-cargo:
name: Release (Cargo)
if: 'false' # TODO re-enable when all dependencies are published
runs-on: ubuntu-latest
needs: [ check-version ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish -p objdiff-core
release-npm:
name: Release (npm)
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [ check-version ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: lts/*
registry-url: 'https://registry.npmjs.org'
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: wasm
path: objdiff-wasm/dist
- name: Publish
working-directory: objdiff-wasm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -euo pipefail
version=$(jq -r '.version' package.json)
tag="latest"
# Check for prerelease by looking for a dash
case "$version" in
*-*)
tag=$(echo "$version" | sed -e 's/^[^-]*-//' -e 's/\..*$//')
;;
esac
echo "Publishing version $version with tag '$tag'..."
npm publish --provenance --access public --tag "$tag"
Generated
+782 -509
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -14,7 +14,7 @@ strip = "debuginfo"
codegen-units = 1
[workspace.package]
version = "3.0.0-beta.5"
version = "3.0.0-beta.8"
authors = ["Luke Street <luke@street.dev>"]
edition = "2024"
license = "MIT OR Apache-2.0"
+5 -4
View File
@@ -16,11 +16,12 @@ Features:
Supports:
- PowerPC 750CL (GameCube, Wii)
- MIPS (N64, PS1, PS2, PSP)
- x86 (COFF only at the moment)
- ARM (GBA, DS, 3DS)
- ARM64 (Switch, experimental)
- ARM64 (Switch)
- MIPS (N64, PS1, PS2, PSP)
- PowerPC (GameCube, Wii)
- SuperH (Saturn, Dreamcast)
- x86 (COFF only)
See [Usage](#usage) for more information.
+4
View File
@@ -175,6 +175,10 @@
"type": "boolean",
"description": "If true, objdiff will run the build command with the context file as an argument to generate it.",
"default": false
},
"preset_id": {
"type": "number",
"description": "The decomp.me preset ID to use for the scratch.\nCompiler and flags in the config will take precedence over the preset, but the preset is useful for organizational purposes."
}
},
"required": [
+1 -2
View File
@@ -103,6 +103,7 @@ allow = [
"0BSD",
"OFL-1.1",
"Ubuntu-font-1.0",
"CDLA-Permissive-2.0",
]
# The confidence threshold for detecting a license from license text.
# The higher the value, the more closely the license text must be to the
@@ -240,8 +241,6 @@ allow-git = []
[sources.allow-org]
# github.com organizations to allow git sources for
github = [
"CelestialAmber", # cwextab, rlwinmdec
"Decompollaborate", # rabbitizer
"enarx", # flagset
"encounter",
]
+1 -1
View File
@@ -28,7 +28,7 @@ supports-color = "3.0"
time = { version = "0.3", features = ["formatting", "local-offset"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
typed-path = "0.10"
typed-path = "0.11"
[target.'cfg(target_env = "musl")'.dependencies]
mimalloc = "0.1"
+3 -27
View File
@@ -1,7 +1,6 @@
use std::{
io::stdout,
mem,
str::FromStr,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
@@ -29,10 +28,7 @@ use objdiff_core::{
ProjectConfig, ProjectObject, ProjectObjectMetadata, build_globset,
path::{check_path_buf, platform_path, platform_path_serde_option},
},
diff::{
self, ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig, MappingConfig,
ObjectDiff,
},
diff::{self, DiffObjConfig, MappingConfig, ObjectDiff},
jobs::{
Job, JobQueue, JobResult,
objdiff::{ObjDiffConfig, start_build},
@@ -43,6 +39,7 @@ use ratatui::prelude::*;
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
use crate::{
cmd::apply_config_args,
util::{
output::{OutputFormat, write_output},
term::crossterm_panic_handler,
@@ -183,28 +180,7 @@ pub fn run(args: Args) -> Result<()> {
fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)> {
let mut diff_config = DiffObjConfig::default();
for config in &args.config {
let (key, value) = config.split_once('=').context("--config expects \"key=value\"")?;
let property_id = ConfigPropertyId::from_str(key)
.map_err(|()| anyhow!("Invalid configuration property: {}", key))?;
diff_config.set_property_value_str(property_id, value).map_err(|()| {
let mut options = String::new();
match property_id.kind() {
ConfigPropertyKind::Boolean => {
options = "true, false".to_string();
}
ConfigPropertyKind::Choice(variants) => {
for (i, variant) in variants.iter().enumerate() {
if i > 0 {
options.push_str(", ");
}
options.push_str(variant.value);
}
}
}
anyhow!("Invalid value for {}. Expected one of: {}", property_id.name(), options)
})?;
}
apply_config_args(&mut diff_config, &args.config)?;
let mut mapping_config = MappingConfig {
mappings: Default::default(),
selecting_left: args.selecting_left.clone(),
+31
View File
@@ -1,2 +1,33 @@
pub mod diff;
pub mod report;
use std::str::FromStr;
use anyhow::{Context, Result, anyhow};
use objdiff_core::diff::{ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig};
pub fn apply_config_args(diff_config: &mut DiffObjConfig, args: &[String]) -> Result<()> {
for config in args {
let (key, value) = config.split_once('=').context("--config expects \"key=value\"")?;
let property_id = ConfigPropertyId::from_str(key)
.map_err(|()| anyhow!("Invalid configuration property: {}", key))?;
diff_config.set_property_value_str(property_id, value).map_err(|()| {
let mut options = String::new();
match property_id.kind() {
ConfigPropertyKind::Boolean => {
options = "true, false".to_string();
}
ConfigPropertyKind::Choice(variants) => {
for (i, variant) in variants.iter().enumerate() {
if i > 0 {
options.push_str(", ");
}
options.push_str(variant.value);
}
}
}
anyhow!("Invalid value for {}. Expected one of: {}", property_id.name(), options)
})?;
}
Ok(())
}
+32 -10
View File
@@ -17,7 +17,7 @@ use tracing::{info, warn};
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
use crate::{
cmd::diff::ObjectConfig,
cmd::{apply_config_args, diff::ObjectConfig},
util::output::{OutputFormat, write_output},
};
@@ -52,6 +52,9 @@ pub struct GenerateArgs {
#[argp(option, short = 'f')]
/// Output format (json, json-pretty, proto) (default: json)
format: Option<String>,
#[argp(option, short = 'c')]
/// Configuration property (key=value)
config: Vec<String>,
}
#[derive(FromArgs, PartialEq, Debug)]
@@ -80,6 +83,15 @@ pub fn run(args: Args) -> Result<()> {
}
fn generate(args: GenerateArgs) -> Result<()> {
let mut diff_config = diff::DiffObjConfig {
function_reloc_diffs: diff::FunctionRelocDiffs::None,
combine_data_sections: true,
combine_text_sections: true,
ppc_calculate_pool_relocations: false,
..Default::default()
};
apply_config_args(&mut diff_config, &args.config)?;
let output_format = OutputFormat::from_option(args.format.as_deref())?;
let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new("."));
info!("Loading project {}", project_dir);
@@ -114,14 +126,15 @@ fn generate(args: GenerateArgs) -> Result<()> {
if args.deduplicate {
// If deduplicating, we need to run single-threaded
for object in &objects {
if let Some(unit) = report_object(object, Some(&mut existing_functions))? {
if let Some(unit) = report_object(object, &diff_config, Some(&mut existing_functions))?
{
units.push(unit);
}
}
} else {
let vec = objects
.par_iter()
.map(|object| report_object(object, None))
.map(|object| report_object(object, &diff_config, None))
.collect::<Result<Vec<Option<ReportUnit>>>>()?;
units = vec.into_iter().flatten().collect();
}
@@ -145,6 +158,7 @@ fn generate(args: GenerateArgs) -> Result<()> {
fn report_object(
object: &ObjectConfig,
diff_config: &diff::DiffObjConfig,
mut existing_functions: Option<&mut HashSet<String>>,
) -> Result<Option<ReportUnit>> {
match (&object.target_path, &object.base_path) {
@@ -158,16 +172,12 @@ fn report_object(
}
_ => {}
}
let diff_config = diff::DiffObjConfig {
function_reloc_diffs: diff::FunctionRelocDiffs::None,
..Default::default()
};
let mapping_config = diff::MappingConfig::default();
let target = object
.target_path
.as_ref()
.map(|p| {
obj::read::read(p.as_ref(), &diff_config)
obj::read::read(p.as_ref(), diff_config)
.with_context(|| format!("Failed to open {}", p))
})
.transpose()?;
@@ -175,12 +185,12 @@ fn report_object(
.base_path
.as_ref()
.map(|p| {
obj::read::read(p.as_ref(), &diff_config)
obj::read::read(p.as_ref(), diff_config)
.with_context(|| format!("Failed to open {}", p))
})
.transpose()?;
let result =
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
diff::diff_objs(target.as_ref(), base.as_ref(), None, diff_config, &mapping_config)?;
let metadata = ReportUnitMetadata {
complete: object.metadata.complete,
@@ -218,6 +228,7 @@ fn report_object(
demangled_name: None,
virtual_address: section.virtual_address,
}),
address: None,
});
match section.kind {
@@ -265,6 +276,7 @@ fn report_object(
demangled_name: symbol.demangled_name.clone(),
virtual_address: symbol.virtual_address,
}),
address: symbol.address.checked_sub(section.address),
});
if match_percent == 100.0 {
measures.matched_functions += 1;
@@ -272,6 +284,16 @@ fn report_object(
measures.total_functions += 1;
}
}
sections.sort_by(|a, b| a.name.cmp(&b.name));
let reverse_fn_order = object.metadata.reverse_fn_order.unwrap_or(false);
functions.sort_by(|a, b| {
if reverse_fn_order {
b.address.unwrap_or(0).cmp(&a.address.unwrap_or(0))
} else {
a.address.unwrap_or(u64::MAX).cmp(&b.address.unwrap_or(u64::MAX))
}
.then_with(|| a.size.cmp(&b.size))
});
if metadata.complete.unwrap_or(false) {
measures.complete_code = measures.total_code;
measures.complete_data = measures.total_data;
+3 -12
View File
@@ -450,11 +450,11 @@ impl UiView for FunctionDiffUi {
fn reload(&mut self, state: &AppState) -> Result<()> {
let left_sym =
state.left_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name));
state.left_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
let right_sym =
state.right_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name));
state.right_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
let prev_sym =
state.prev_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name));
state.prev_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
self.num_rows = match (
get_symbol(state.left_obj.as_ref(), left_sym),
get_symbol(state.right_obj.as_ref(), right_sym),
@@ -650,12 +650,3 @@ fn get_symbol(
let sym = sym?;
Some((obj, sym, &diff.symbols[sym]))
}
fn find_function(obj: &Object, name: &str) -> Option<usize> {
for (symbol_idx, symbol) in obj.symbols.iter().enumerate() {
if symbol.name == name {
return Some(symbol_idx);
}
}
None
}
+15 -9
View File
@@ -27,6 +27,7 @@ all = [
"mips",
"ppc",
"x86",
"superh"
]
# Implicit, used to check if any arch is enabled
any-arch = [
@@ -75,6 +76,7 @@ std = [
"object/std",
"prost?/std",
"serde?/std",
"similar?/std",
"typed-path?/std",
"dep:filetime",
"dep:memmap2",
@@ -110,6 +112,9 @@ arm64 = [
"dep:yaxpeax-arch",
"dep:yaxpeax-arm",
]
superh = [
"any-arch",
]
[package.metadata.docs.rs]
features = ["all"]
@@ -122,13 +127,13 @@ itertools = { version = "0.14", default-features = false, features = ["use_alloc
log = { version = "0.4", default-features = false, optional = true }
memmap2 = { version = "0.9", optional = true }
num-traits = { version = "0.2", default-features = false, optional = true }
object = { version = "0.36", default-features = false, features = ["read_core", "elf", "pe"] }
object = { git = "https://github.com/gimli-rs/object", rev = "a74579249e21ab8fcd3a86be588de336f18297cb", default-features = false, features = ["read_core", "elf", "pe"] }
pbjson = { version = "0.7", default-features = false, optional = true }
prost = { version = "0.13", default-features = false, features = ["prost-derive"], optional = true }
regex = { version = "1.11", default-features = false, features = [], optional = true }
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
similar = { version = "2.7", default-features = false, optional = true, git = "https://github.com/encounter/similar.git", branch = "no_std" }
typed-path = { version = "0.10", default-features = false, optional = true }
similar = { version = "2.7", default-features = false, features = ["hashbrown"], optional = true, git = "https://github.com/encounter/similar.git", branch = "no_std" }
typed-path = { version = "0.11", default-features = false, optional = true }
# config
globset = { version = "0.4", default-features = false, optional = true }
@@ -140,12 +145,12 @@ gimli = { version = "0.31", default-features = false, features = ["read"], optio
# ppc
cwdemangle = { version = "1.0", optional = true }
cwextab = { version = "1.0", optional = true, git = "https://github.com/CelestialAmber/cwextab.git" }
cwextab = { version = "1.0", optional = true }
ppc750cl = { version = "0.3", optional = true }
rlwinmdec = { version = "1.1", optional = true, git = "https://github.com/CelestialAmber/rlwinmdec.git" }
rlwinmdec = { version = "1.1", optional = true }
# mips
rabbitizer = { git = "https://github.com/Decompollaborate/rabbitizer.git", branch = "🦀", default-features = false, features = ["all_extensions"], optional = true }
rabbitizer = { version = "2.0.0-alpha.1", default-features = false, features = ["all_extensions"], optional = true }
# x86
cpp_demangle = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
@@ -153,7 +158,7 @@ iced-x86 = { version = "1.21", default-features = false, features = ["decoder",
msvc-demangler = { version = "0.11", optional = true }
# arm
unarm = { version = "1.7", optional = true }
unarm = { version = "1.8", optional = true }
arm-attr = { version = "0.2", optional = true }
# arm64
@@ -164,8 +169,9 @@ yaxpeax-arm = { version = "0.3", default-features = false, optional = true }
notify = { version = "8.0.0", optional = true }
notify-debouncer-full = { version = "0.5.0", optional = true }
shell-escape = { version = "0.1", optional = true }
tempfile = { version = "3.17", optional = true }
tempfile = { version = "3.19", optional = true }
time = { version = "0.3", optional = true }
encoding_rs = "0.8.35"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", optional = true }
@@ -194,4 +200,4 @@ syn = { version = "2.0", optional = true }
[dev-dependencies]
# Enable all features for tests
objdiff-core = { path = ".", features = ["all"] }
insta = "1.42"
insta = "1.43"
+6 -5
View File
@@ -5,11 +5,12 @@ objdiff-core contains the core functionality of [objdiff](https://github.com/enc
## Crate feature flags
- **`all`**: Enables all main features.
- **`bindings`**: Enables serialization and deserialization of objdiff data structures.
- **`config`**: Enables objdiff configuration file support.
- **`dwarf`**: Enables extraction of line number information from DWARF debug sections.
- **`mips`**: Enables the MIPS backend powered by [rabbitizer](https://github.com/Decompollaborate/rabbitizer). (Note: C library with Rust bindings)
- **`ppc`**: Enables the PowerPC backend powered by [ppc750cl](https://github.com/encounter/ppc750cl).
- **`x86`**: Enables the x86 backend powered by [iced-x86](https://crates.io/crates/iced-x86).
- **`arm`**: Enables the ARM backend powered by [unarm](https://github.com/AetiasHax/unarm).
- **`arm64`**: Enables the ARM64 backend powered by [yaxpeax-arm](https://github.com/iximeow/yaxpeax-arm).
- **`bindings`**: Enables serialization and deserialization of objdiff data structures.
- **`arm`**: Enables the ARM backend powered by [unarm](https://github.com/AetiasHax/unarm).
- **`mips`**: Enables the MIPS backend powered by [rabbitizer](https://github.com/Decompollaborate/rabbitizer).
- **`ppc`**: Enables the PowerPC backend powered by [ppc750cl](https://github.com/encounter/ppc750cl).
- **`superh`**: Enables the SuperH backend powered by an included disassembler.
- **`x86`**: Enables the x86 backend powered by [iced-x86](https://crates.io/crates/iced-x86).
Binary file not shown.
+2
View File
@@ -99,6 +99,8 @@ message ReportItem {
float fuzzy_match_percent = 3;
// Extra metadata for this item
optional ReportItemMetadata metadata = 4;
// Address of the item (section-relative offset)
optional uint64 address = 5;
}
// Extra metadata for an item
+72 -32
View File
@@ -15,7 +15,7 @@ use crate::{
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig, display::InstructionPart},
obj::{
InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation,
ScannedInstruction, SymbolFlag, SymbolFlagSet, SymbolKind,
Section, SectionKind, Symbol, SymbolFlag, SymbolFlagSet, SymbolKind,
},
};
@@ -32,7 +32,8 @@ impl ArchArm {
let endianness = file.endianness();
match file {
object::File::Elf32(_) => {
let disasm_modes = Self::elf_get_mapping_symbols(file);
// The disasm_modes mapping is populated later in the post_init step so that we have access to merged sections.
let disasm_modes = BTreeMap::new();
let detected_version = Self::elf_detect_arm_version(file)?;
Ok(Self { disasm_modes, detected_version, endianness })
}
@@ -73,18 +74,22 @@ impl ArchArm {
Ok(None)
}
fn elf_get_mapping_symbols(file: &object::File) -> BTreeMap<usize, Vec<DisasmMode>> {
file.sections()
.filter(|s| s.kind() == object::SectionKind::Text)
.map(|s| {
let index = s.index();
let mut mapping_symbols: Vec<_> = file
.symbols()
.filter(|s| s.section_index().map(|i| i == index).unwrap_or(false))
.filter_map(|s| DisasmMode::from_symbol(&s))
fn get_mapping_symbols(
sections: &[Section],
symbols: &[Symbol],
) -> BTreeMap<usize, Vec<DisasmMode>> {
sections
.iter()
.enumerate()
.filter(|(_, section)| section.kind == SectionKind::Code)
.map(|(index, _)| {
let mut mapping_symbols: Vec<_> = symbols
.iter()
.filter(|s| s.section.map(|i| i == index).unwrap_or(false))
.filter_map(DisasmMode::from_symbol)
.collect();
mapping_symbols.sort_unstable_by_key(|x| x.address);
(s.index().0 - 1, mapping_symbols)
(index, mapping_symbols)
})
.collect()
}
@@ -178,14 +183,18 @@ impl ArchArm {
}
impl Arch for ArchArm {
fn scan_instructions(
fn post_init(&mut self, sections: &[Section], symbols: &[Symbol]) {
self.disasm_modes = Self::get_mapping_symbols(sections, symbols);
}
fn scan_instructions_internal(
&self,
address: u64,
code: &[u8],
section_index: usize,
_relocations: &[Relocation],
diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>> {
) -> Result<Vec<InstructionRef>> {
let start_addr = address as u32;
let end_addr = start_addr + code.len() as u32;
@@ -210,13 +219,13 @@ impl Arch for ArchArm {
let mut next_mapping = mappings_iter.next();
let ins_count = code.len() / mode.instruction_size(start_addr);
let mut ops = Vec::<ScannedInstruction>::with_capacity(ins_count);
let mut ops = Vec::<InstructionRef>::with_capacity(ins_count);
let parse_flags = self.parse_flags(diff_config);
let mut address = start_addr;
while address < end_addr {
while let Some(next) = next_mapping.take_if(|x| address >= x.address) {
while let Some(next) = next_mapping.filter(|x| address >= x.address) {
// Change mapping
mode = next.mapping;
next_mapping = mappings_iter.next();
@@ -226,12 +235,10 @@ impl Arch for ArchArm {
let data = &code[(address - start_addr) as usize..];
if data.len() < ins_size {
// Push the remainder as data
ops.push(ScannedInstruction {
ins_ref: InstructionRef {
address: address as u64,
size: data.len() as u8,
opcode: u16::MAX,
},
ops.push(InstructionRef {
address: address as u64,
size: data.len() as u8,
opcode: u16::MAX,
branch_dest: None,
});
break;
@@ -247,12 +254,10 @@ impl Arch for ArchArm {
}
_ => {
// Invalid instruction size
ops.push(ScannedInstruction {
ins_ref: InstructionRef {
address: address as u64,
size: ins_size as u8,
opcode: u16::MAX,
},
ops.push(InstructionRef {
address: address as u64,
size: ins_size as u8,
opcode: u16::MAX,
branch_dest: None,
});
address += ins_size as u32;
@@ -316,8 +321,10 @@ impl Arch for ArchArm {
unarm::ParseMode::Data => (u16::MAX, None),
};
ops.push(ScannedInstruction {
ins_ref: InstructionRef { address: address as u64, size: ins_size as u8, opcode },
ops.push(InstructionRef {
address: address as u64,
size: ins_size as u8,
opcode,
branch_dest: branch_dest.map(|x| x as u64),
});
address += ins_size as u32;
@@ -338,6 +345,7 @@ impl Arch for ArchArm {
cb(InstructionPart::reloc())?;
} else {
push_args(
ins,
&parsed_ins,
resolved.relocation,
resolved.ins_ref.address as u32,
@@ -441,7 +449,7 @@ impl Arch for ArchArm {
fn extra_symbol_flags(&self, symbol: &object::Symbol) -> SymbolFlagSet {
let mut flags = SymbolFlagSet::default();
if DisasmMode::from_symbol(symbol).is_some() {
if DisasmMode::from_object_symbol(symbol).is_some() {
flags |= SymbolFlag::Hidden;
}
flags
@@ -455,15 +463,21 @@ struct DisasmMode {
}
impl DisasmMode {
fn from_symbol<'a>(sym: &object::Symbol<'a, '_, &'a [u8]>) -> Option<Self> {
fn from_object_symbol<'a>(sym: &object::Symbol<'a, '_, &'a [u8]>) -> Option<Self> {
sym.name()
.ok()
.and_then(unarm::ParseMode::from_mapping_symbol)
.map(|mapping| DisasmMode { address: sym.address() as u32, mapping })
}
fn from_symbol(sym: &Symbol) -> Option<Self> {
unarm::ParseMode::from_mapping_symbol(&sym.name)
.map(|mapping| DisasmMode { address: sym.address as u32, mapping })
}
}
fn push_args(
ins: unarm::Ins,
parsed_ins: &unarm::ParsedIns,
relocation: Option<ResolvedRelocation>,
cur_addr: u32,
@@ -609,6 +623,14 @@ fn push_args(
arg_cb(InstructionPart::opaque("!"))?;
}
}
let branch_dest = get_pc_relative_load_address(ins, cur_addr);
if let Some(branch_dest) = branch_dest {
arg_cb(InstructionPart::basic(" (->"))?;
arg_cb(InstructionPart::branch_dest(branch_dest))?;
arg_cb(InstructionPart::basic(")"))?;
}
Ok(())
}
@@ -636,3 +658,21 @@ fn find_reloc_arg(
None
}
}
fn get_pc_relative_load_address(ins: unarm::Ins, address: u32) -> Option<u32> {
match ins {
unarm::Ins::Arm(ins)
if ins.op == arm::Opcode::Ldr
&& ins.modifier_addr_ldr_str() == arm::AddrLdrStr::Imm
&& ins.field_rn_deref().reg == args::Register::Pc =>
{
let offset = ins.field_offset_12().value;
Some(address.wrapping_add_signed(offset + 8))
}
unarm::Ins::Thumb(ins) if ins.op == thumb::Opcode::LdrPc => {
let offset = ins.field_rel_immed_8().value;
Some((address & !3).wrapping_add_signed(offset + 4))
}
_ => None,
}
}
+15 -14
View File
@@ -18,7 +18,6 @@ use crate::{
diff::{DiffObjConfig, display::InstructionPart},
obj::{
InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation,
ScannedInstruction,
},
};
@@ -30,16 +29,16 @@ impl ArchArm64 {
}
impl Arch for ArchArm64 {
fn scan_instructions(
fn scan_instructions_internal(
&self,
address: u64,
code: &[u8],
_section_index: usize,
_relocations: &[Relocation],
_diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>> {
) -> Result<Vec<InstructionRef>> {
let start_address = address;
let mut ops = Vec::<ScannedInstruction>::with_capacity(code.len() / 4);
let mut ops = Vec::<InstructionRef>::with_capacity(code.len() / 4);
let mut reader = U8Reader::new(code);
let decoder = InstDecoder::default();
@@ -58,8 +57,10 @@ impl Arch for ArchArm64 {
DecodeError::InvalidOpcode
| DecodeError::InvalidOperand
| DecodeError::IncompleteDecoder => {
ops.push(ScannedInstruction {
ins_ref: InstructionRef { address, size: 4, opcode: u16::MAX },
ops.push(InstructionRef {
address,
size: 4,
opcode: u16::MAX,
branch_dest: None,
});
continue;
@@ -68,9 +69,9 @@ impl Arch for ArchArm64 {
}
let opcode = opcode_to_u16(ins.opcode);
let ins_ref = InstructionRef { address, size: 4, opcode };
let branch_dest = branch_dest(ins_ref, &code[offset as usize..offset as usize + 4]);
ops.push(ScannedInstruction { ins_ref, branch_dest });
let branch_dest =
branch_dest(opcode, address, &code[offset as usize..offset as usize + 4]);
ops.push(InstructionRef { address, size: 4, opcode, branch_dest });
}
Ok(ops)
@@ -163,7 +164,7 @@ impl Arch for ArchArm64 {
}
}
fn branch_dest(ins_ref: InstructionRef, code: &[u8]) -> Option<u64> {
fn branch_dest(opcode: u16, address: u64, code: &[u8]) -> Option<u64> {
const OPCODE_B: u16 = opcode_to_u16(Opcode::B);
const OPCODE_BL: u16 = opcode_to_u16(Opcode::BL);
const OPCODE_BCC: u16 = opcode_to_u16(Opcode::Bcc(0));
@@ -173,21 +174,21 @@ fn branch_dest(ins_ref: InstructionRef, code: &[u8]) -> Option<u64> {
const OPCODE_TBNZ: u16 = opcode_to_u16(Opcode::TBNZ);
let word = u32::from_le_bytes(code.try_into().ok()?);
match ins_ref.opcode {
match opcode {
OPCODE_B | OPCODE_BL => {
let offset = ((word & 0x03ff_ffff) << 2) as i32;
let extended_offset = (offset << 4) >> 4;
ins_ref.address.checked_add_signed(extended_offset as i64)
address.checked_add_signed(extended_offset as i64)
}
OPCODE_BCC | OPCODE_CBZ | OPCODE_CBNZ => {
let offset = (word as i32 & 0x00ff_ffe0) >> 3;
let extended_offset = (offset << 11) >> 11;
ins_ref.address.checked_add_signed(extended_offset as i64)
address.checked_add_signed(extended_offset as i64)
}
OPCODE_TBZ | OPCODE_TBNZ => {
let offset = (word as i32 & 0x0007_ffe0) >> 3;
let extended_offset = (offset << 16) >> 16;
ins_ref.address.checked_add_signed(extended_offset as i64)
address.checked_add_signed(extended_offset as i64)
}
_ => None,
}
+7 -38
View File
@@ -3,7 +3,6 @@ use alloc::{
string::{String, ToString},
vec::Vec,
};
use core::ops::Range;
use anyhow::{Result, bail};
use object::{Endian as _, Object as _, ObjectSection as _, ObjectSymbol as _, elf};
@@ -19,7 +18,7 @@ use crate::{
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory, display::InstructionPart},
obj::{
InstructionArg, InstructionArgValue, InstructionRef, Relocation, RelocationFlags,
ResolvedInstructionRef, ResolvedRelocation, ScannedInstruction, SymbolFlag, SymbolFlagSet,
ResolvedInstructionRef, ResolvedRelocation, SymbolFlag, SymbolFlagSet,
},
};
@@ -189,16 +188,16 @@ impl ArchMips {
}
impl Arch for ArchMips {
fn scan_instructions(
fn scan_instructions_internal(
&self,
address: u64,
code: &[u8],
_section_index: usize,
_relocations: &[Relocation],
diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>> {
) -> Result<Vec<InstructionRef>> {
let instruction_flags = self.instruction_flags(diff_config);
let mut ops = Vec::<ScannedInstruction>::with_capacity(code.len() / 4);
let mut ops = Vec::<InstructionRef>::with_capacity(code.len() / 4);
let mut cur_addr = address as u32;
for chunk in code.chunks_exact(4) {
let code = self.endianness.read_u32_bytes(chunk.try_into()?);
@@ -206,10 +205,7 @@ impl Arch for ArchMips {
rabbitizer::Instruction::new(code, Vram::new(cur_addr), instruction_flags);
let opcode = instruction.opcode() as u16;
let branch_dest = instruction.get_branch_vram_generic().map(|v| v.inner() as u64);
ops.push(ScannedInstruction {
ins_ref: InstructionRef { address: cur_addr as u64, size: 4, opcode },
branch_dest,
});
ops.push(InstructionRef { address: cur_addr as u64, size: 4, opcode, branch_dest });
cur_addr += 4;
}
Ok(ops)
@@ -225,16 +221,7 @@ impl Arch for ArchMips {
let display_flags = self.instruction_display_flags(diff_config);
let opcode = instruction.opcode();
cb(InstructionPart::opcode(opcode.name(), opcode as u16))?;
let start_address = resolved.symbol.address;
let function_range = start_address..start_address + resolved.symbol.size;
push_args(
&instruction,
resolved.relocation,
function_range,
resolved.section_index,
&display_flags,
cb,
)?;
push_args(&instruction, resolved.relocation, &display_flags, cb)?;
Ok(())
}
@@ -338,8 +325,6 @@ impl Arch for ArchMips {
fn push_args(
instruction: &rabbitizer::Instruction,
relocation: Option<ResolvedRelocation>,
function_range: Range<u64>,
section_index: usize,
display_flags: &rabbitizer::InstructionDisplayFlags,
mut arg_cb: impl FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
@@ -362,23 +347,7 @@ fn push_args(
}
ValuedOperand::core_label(..) | ValuedOperand::core_branch_target_label(..) => {
if let Some(resolved) = relocation {
// If the relocation target is within the current function, we can
// convert it into a relative branch target. Note that we check
// target_address > start_address instead of >= so that recursive
// tail calls are not considered branch targets.
let target_address =
resolved.symbol.address.checked_add_signed(resolved.relocation.addend);
if resolved.symbol.section == Some(section_index)
&& target_address.is_some_and(|addr| {
addr > function_range.start && addr < function_range.end
})
{
// TODO move this logic up a level
let target_address = target_address.unwrap();
arg_cb(InstructionPart::branch_dest(target_address))?;
} else {
push_reloc(resolved.relocation, &mut arg_cb)?;
}
push_reloc(resolved.relocation, &mut arg_cb)?;
} else if let Some(branch_dest) = instruction
.get_branch_offset_generic()
.map(|o| (instruction.vram() + o).inner() as u64)
+157 -40
View File
@@ -2,6 +2,7 @@ use alloc::{borrow::Cow, boxed::Box, format, string::String, vec::Vec};
use core::{ffi::CStr, fmt, fmt::Debug};
use anyhow::{Result, bail};
use encoding_rs::SHIFT_JIS;
use object::Endian as _;
use crate::{
@@ -10,8 +11,8 @@ use crate::{
display::{ContextItem, HoverItem, InstructionPart},
},
obj::{
InstructionArg, Object, ParsedInstruction, Relocation, RelocationFlags,
ResolvedInstructionRef, ScannedInstruction, Symbol, SymbolFlagSet, SymbolKind,
InstructionArg, InstructionRef, Object, ParsedInstruction, Relocation, RelocationFlags,
ResolvedInstructionRef, ResolvedSymbol, Section, Symbol, SymbolFlagSet, SymbolKind,
},
util::ReallySigned,
};
@@ -24,6 +25,8 @@ pub mod arm64;
pub mod mips;
#[cfg(feature = "ppc")]
pub mod ppc;
#[cfg(feature = "superh")]
pub mod superh;
#[cfg(feature = "x86")]
pub mod x86;
@@ -57,13 +60,18 @@ impl fmt::Display for DataType {
impl DataType {
pub fn display_labels(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<String> {
let mut strs = Vec::new();
for literal in self.display_literals(endian, bytes) {
strs.push(format!("{}: {}", self, literal))
for (literal, label_override) in self.display_literals(endian, bytes) {
let label = label_override.unwrap_or_else(|| format!("{}", self));
strs.push(format!("{}: {}", label, literal))
}
strs
}
pub fn display_literals(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<String> {
pub fn display_literals(
&self,
endian: object::Endianness,
bytes: &[u8],
) -> Vec<(String, Option<String>)> {
let mut strs = Vec::new();
if self.required_len().is_some_and(|l| bytes.len() < l) {
log::warn!(
@@ -87,56 +95,72 @@ impl DataType {
match self {
DataType::Int8 => {
let i = i8::from_ne_bytes(bytes.try_into().unwrap());
strs.push(format!("{:#x}", i));
strs.push((format!("{:#x}", i), None));
if i < 0 {
strs.push(format!("{:#x}", ReallySigned(i)));
strs.push((format!("{:#x}", ReallySigned(i)), None));
}
}
DataType::Int16 => {
let i = endian.read_i16_bytes(bytes.try_into().unwrap());
strs.push(format!("{:#x}", i));
strs.push((format!("{:#x}", i), None));
if i < 0 {
strs.push(format!("{:#x}", ReallySigned(i)));
strs.push((format!("{:#x}", ReallySigned(i)), None));
}
}
DataType::Int32 => {
let i = endian.read_i32_bytes(bytes.try_into().unwrap());
strs.push(format!("{:#x}", i));
strs.push((format!("{:#x}", i), None));
if i < 0 {
strs.push(format!("{:#x}", ReallySigned(i)));
strs.push((format!("{:#x}", ReallySigned(i)), None));
}
}
DataType::Int64 => {
let i = endian.read_i64_bytes(bytes.try_into().unwrap());
strs.push(format!("{:#x}", i));
strs.push((format!("{:#x}", i), None));
if i < 0 {
strs.push(format!("{:#x}", ReallySigned(i)));
strs.push((format!("{:#x}", ReallySigned(i)), None));
}
}
DataType::Float => {
let bytes: [u8; 4] = bytes.try_into().unwrap();
strs.push(format!("{:?}f", match endian {
object::Endianness::Little => f32::from_le_bytes(bytes),
object::Endianness::Big => f32::from_be_bytes(bytes),
}));
strs.push((
format!("{:?}f", match endian {
object::Endianness::Little => f32::from_le_bytes(bytes),
object::Endianness::Big => f32::from_be_bytes(bytes),
}),
None,
));
}
DataType::Double => {
let bytes: [u8; 8] = bytes.try_into().unwrap();
strs.push(format!("{:?}", match endian {
object::Endianness::Little => f64::from_le_bytes(bytes),
object::Endianness::Big => f64::from_be_bytes(bytes),
}));
strs.push((
format!("{:?}", match endian {
object::Endianness::Little => f64::from_le_bytes(bytes),
object::Endianness::Big => f64::from_be_bytes(bytes),
}),
None,
));
}
DataType::Bytes => {
strs.push(format!("{:#?}", bytes));
strs.push((format!("{:#?}", bytes), None));
}
DataType::String => {
if let Ok(cstr) = CStr::from_bytes_until_nul(bytes) {
strs.push(format!("{:?}", cstr));
strs.push((format!("{:?}", cstr), None));
}
if let Some(nul_idx) = bytes.iter().position(|&c| c == b'\0') {
let (cow, _, had_errors) = SHIFT_JIS.decode(&bytes[..nul_idx]);
if !had_errors {
let str = format!("{:?}", cow);
// Only add the Shift JIS string if it's different from the ASCII string.
if !strs.iter().any(|x| x.0 == str) {
strs.push((str, Some("Shift JIS".into())));
}
}
}
}
}
@@ -158,43 +182,108 @@ impl DataType {
}
}
pub trait Arch: Send + Sync + Debug {
impl dyn Arch {
/// Generate a list of instructions references (offset, size, opcode) from the given code.
///
/// The opcode IDs are used to generate the initial diff. Implementations should do as little
/// parsing as possible here: just enough to identify the base instruction opcode, size, and
/// possible branch destination (for visual representation). As needed, instructions are parsed
/// via `process_instruction` to compare their arguments.
fn scan_instructions(
/// See [`scan_instructions_internal`] for more details.
pub fn scan_instructions(
&self,
address: u64,
code: &[u8],
section_index: usize,
relocations: &[Relocation],
resolved: ResolvedSymbol,
diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>>;
) -> Result<Vec<InstructionRef>> {
let mut result = self.scan_instructions_internal(
resolved.symbol.address,
resolved.data,
resolved.section_index,
&resolved.section.relocations,
diff_config,
)?;
let function_start = resolved.symbol.address;
let function_end = function_start + resolved.symbol.size;
// Remove any branch destinations that are outside the function range
for ins in result.iter_mut() {
if let Some(branch_dest) = ins.branch_dest {
if branch_dest < function_start || branch_dest >= function_end {
ins.branch_dest = None;
}
}
}
// Resolve relocation targets within the same function to branch destinations
let mut ins_iter = result.iter_mut().peekable();
'outer: for reloc in resolved
.section
.relocations
.iter()
.skip_while(|r| r.address < function_start)
.take_while(|r| r.address < function_end)
{
let ins = loop {
let Some(ins) = ins_iter.peek_mut() else {
break 'outer;
};
if reloc.address < ins.address {
continue 'outer;
}
let ins = ins_iter.next().unwrap();
if reloc.address >= ins.address && reloc.address < ins.address + ins.size as u64 {
break ins;
}
};
// Clear existing branch destination for instructions with relocations
ins.branch_dest = None;
let Some(target) = resolved.obj.symbols.get(reloc.target_symbol) else {
continue;
};
if target.section != Some(resolved.section_index) {
continue;
}
let Some(target_address) = target.address.checked_add_signed(reloc.addend) else {
continue;
};
// If the target address is within the function range, set it as a branch destination
if target_address >= function_start && target_address < function_end {
ins.branch_dest = Some(target_address);
}
}
Ok(result)
}
/// Parse an instruction to gather its mnemonic and arguments for more detailed comparison.
///
/// This is called only when we need to compare the arguments of an instruction.
fn process_instruction(
pub fn process_instruction(
&self,
resolved: ResolvedInstructionRef,
diff_config: &DiffObjConfig,
) -> Result<ParsedInstruction> {
let mut mnemonic = None;
let mut args = Vec::with_capacity(8);
let mut relocation_emitted = false;
self.display_instruction(resolved, diff_config, &mut |part| {
match part {
InstructionPart::Opcode(m, _) => mnemonic = Some(Cow::Owned(m.into_owned())),
InstructionPart::Arg(arg) => args.push(arg.into_static()),
InstructionPart::Arg(arg) => {
if arg == InstructionArg::Reloc {
relocation_emitted = true;
// If the relocation was resolved to a branch destination, emit that instead.
if let Some(dest) = resolved.ins_ref.branch_dest {
args.push(InstructionArg::BranchDest(dest));
return Ok(());
}
}
args.push(arg.into_static());
}
_ => {}
}
Ok(())
})?;
// If the instruction has a relocation, but we didn't format it in the display, add it to
// the end of the arguments list.
if resolved.relocation.is_some() && !args.contains(&InstructionArg::Reloc) {
if resolved.relocation.is_some() && !relocation_emitted {
args.push(InstructionArg::Reloc);
}
Ok(ParsedInstruction {
@@ -203,6 +292,26 @@ pub trait Arch: Send + Sync + Debug {
args,
})
}
}
pub trait Arch: Send + Sync + Debug {
/// Finishes arch-specific initialization that must be done after sections have been combined.
fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol]) {}
/// Generate a list of instructions references (offset, size, opcode) from the given code.
///
/// The opcode IDs are used to generate the initial diff. Implementations should do as little
/// parsing as possible here: just enough to identify the base instruction opcode, size, and
/// possible branch destination (for visual representation). As needed, instructions are parsed
/// via `process_instruction` to compare their arguments.
fn scan_instructions_internal(
&self,
address: u64,
code: &[u8],
section_index: usize,
relocations: &[Relocation],
diff_config: &DiffObjConfig,
) -> Result<Vec<InstructionRef>>;
/// Format an instruction for display.
///
@@ -247,7 +356,13 @@ pub trait Arch: Send + Sync + Debug {
SymbolFlagSet::default()
}
fn guess_data_type(&self, _resolved: ResolvedInstructionRef) -> Option<DataType> { None }
fn guess_data_type(
&self,
_resolved: ResolvedInstructionRef,
_bytes: &[u8],
) -> Option<DataType> {
None
}
fn symbol_hover(&self, _obj: &Object, _symbol_index: usize) -> Vec<HoverItem> { Vec::new() }
@@ -285,6 +400,8 @@ pub fn new_arch(object: &object::File) -> Result<Box<dyn Arch>> {
object::Architecture::Arm => Box::new(arm::ArchArm::new(object)?),
#[cfg(feature = "arm64")]
object::Architecture::Aarch64 => Box::new(arm64::ArchArm64::new(object)?),
#[cfg(feature = "superh")]
object::Architecture::SuperH => Box::new(superh::ArchSuperH::new(object)?),
arch => bail!("Unsupported architecture: {arch:?}"),
})
}
@@ -297,14 +414,14 @@ impl ArchDummy {
}
impl Arch for ArchDummy {
fn scan_instructions(
fn scan_instructions_internal(
&self,
_address: u64,
_code: &[u8],
_section_index: usize,
_relocations: &[Relocation],
_diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>> {
) -> Result<Vec<InstructionRef>> {
Ok(Vec::new())
}

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