mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a015971c20 | |||
| e67d5998b3 | |||
| 91bc23edfc | |||
| c9c3b32376 | |||
| 0dc123b064 | |||
| 1e62d4664c | |||
| 1205e8ceb4 | |||
| c917cad5f0 | |||
| dd653329f5 | |||
| f5d3d5f10a | |||
| c327ed3ea8 | |||
| 85fb18a21a | |||
| 00ad0d8094 | |||
| 8fac63c42c | |||
| 0fb7f3901c | |||
| 3385f58341 | |||
| 60b227f45e | |||
| 127ae5ae44 | |||
| 5f48e69775 | |||
| 8756eee07b | |||
| bd3ed0d5ad | |||
| e638d0b17a | |||
| f58616b6dd | |||
| e9762e24c2 | |||
| dab79d96a1 | |||
| a57e5db983 | |||
| d0afd3b83e | |||
| a367af612b | |||
| 22052ea10b | |||
| f7c3501eae | |||
| 07ef93f16a | |||
| 8e8ab6bef8 | |||
| e865f3d598 | |||
| 2b13e9886a | |||
| 1750af736a | |||
| 731b604c24 | |||
| 2d643eb071 | |||
| 0c48d711c7 | |||
| 34220a8e26 | |||
| 0c9e5526d4 | |||
| 3db0727469 | |||
| 8b5bf21f38 | |||
| b77df77000 | |||
| 7e08f9715b | |||
| 3c05852d00 | |||
| a51ff44be1 | |||
| d225cac205 | |||
| 737b3782db | |||
| 1d782243e0 | |||
| a1499f475d | |||
| f263e490e3 | |||
| d0e6c5c057 | |||
| e1c51ac297 | |||
| 39b1b49985 | |||
| 6c7160ab7e | |||
| 644d4762f0 | |||
| b40fae5140 | |||
| fbf85632ab | |||
| 73a89d2768 | |||
| a162c2f840 | |||
| a474b27d55 | |||
| 3d7f2b70dc | |||
| fe886f862d | |||
| 2bcbc34850 |
+4
-5
@@ -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"]
|
||||
|
||||
@@ -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
+1283
-877
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -14,9 +14,9 @@ strip = "debuginfo"
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.package]
|
||||
version = "3.0.0-beta.5"
|
||||
version = "3.0.0-beta.13"
|
||||
authors = ["Luke Street <luke@street.dev>"]
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/encounter/objdiff"
|
||||
rust-version = "1.85"
|
||||
rust-version = "1.88"
|
||||
|
||||
@@ -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, PS3, Xbox 360)
|
||||
- SuperH (Saturn, Dreamcast)
|
||||
- x86, x86_64 (PC)
|
||||
|
||||
See [Usage](#usage) for more information.
|
||||
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -15,11 +15,11 @@ publish = false
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
argp = "0.4"
|
||||
crossterm = "0.28"
|
||||
crossterm = "0.29"
|
||||
enable-ansi-support = "0.2"
|
||||
memmap2 = "0.9"
|
||||
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
||||
prost = "0.13"
|
||||
prost = "0.14"
|
||||
ratatui = "0.29"
|
||||
rayon = "1.10"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
@@ -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"
|
||||
|
||||
@@ -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(),
|
||||
@@ -227,14 +203,10 @@ fn run_oneshot(
|
||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||
let (diff_config, mapping_config) = build_config_from_args(args)?;
|
||||
let target = target_path
|
||||
.map(|p| {
|
||||
obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {}", p))
|
||||
})
|
||||
.map(|p| obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {p}")))
|
||||
.transpose()?;
|
||||
let base = base_path
|
||||
.map(|p| {
|
||||
obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {}", p))
|
||||
})
|
||||
.map(|p| obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {p}")))
|
||||
.transpose()?;
|
||||
let result =
|
||||
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
|
||||
@@ -423,7 +395,7 @@ fn run_interactive(
|
||||
stdout(),
|
||||
EnterAlternateScreen,
|
||||
EnableMouseCapture,
|
||||
SetTitle(format!("{} - objdiff", symbol_name)),
|
||||
SetTitle(format!("{symbol_name} - objdiff")),
|
||||
)?;
|
||||
let backend = CrosstermBackend::new(stdout());
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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,29 +172,23 @@ 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)
|
||||
.with_context(|| format!("Failed to open {}", p))
|
||||
obj::read::read(p.as_ref(), diff_config).with_context(|| format!("Failed to open {p}"))
|
||||
})
|
||||
.transpose()?;
|
||||
let base = object
|
||||
.base_path
|
||||
.as_ref()
|
||||
.map(|p| {
|
||||
obj::read::read(p.as_ref(), &diff_config)
|
||||
.with_context(|| format!("Failed to open {}", p))
|
||||
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 +226,7 @@ fn report_object(
|
||||
demangled_name: None,
|
||||
virtual_address: section.virtual_address,
|
||||
}),
|
||||
address: None,
|
||||
});
|
||||
|
||||
match section.kind {
|
||||
@@ -239,13 +248,12 @@ fn report_object(
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Some(existing_functions) = &mut existing_functions {
|
||||
if (symbol.flags.contains(SymbolFlag::Global)
|
||||
if let Some(existing_functions) = &mut existing_functions
|
||||
&& (symbol.flags.contains(SymbolFlag::Global)
|
||||
|| symbol.flags.contains(SymbolFlag::Weak))
|
||||
&& !existing_functions.insert(symbol.name.clone())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
&& !existing_functions.insert(symbol.name.clone())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
|
||||
// Support cases where we don't have a target object,
|
||||
@@ -265,6 +273,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 +281,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;
|
||||
@@ -411,8 +430,8 @@ fn read_report(path: &Utf8PlatformPath) -> Result<Report> {
|
||||
std::io::stdin().read_to_end(&mut data)?;
|
||||
return Report::parse(&data).with_context(|| "Failed to load report from stdin");
|
||||
}
|
||||
let file = File::open(path).with_context(|| format!("Failed to open {}", path))?;
|
||||
let file = File::open(path).with_context(|| format!("Failed to open {path}"))?;
|
||||
let mmap =
|
||||
unsafe { memmap2::Mmap::map(&file) }.with_context(|| format!("Failed to map {}", path))?;
|
||||
Report::parse(mmap.as_ref()).with_context(|| format!("Failed to load report {}", path))
|
||||
unsafe { memmap2::Mmap::map(&file) }.with_context(|| format!("Failed to map {path}"))?;
|
||||
Report::parse(mmap.as_ref()).with_context(|| format!("Failed to load report {path}"))
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ impl UiView for FunctionDiffUi {
|
||||
.and_then(|(_, _, d)| d.match_percent)
|
||||
{
|
||||
line_r.spans.push(Span::styled(
|
||||
format!("{:.2}% ", percent),
|
||||
format!("{percent:.2}% "),
|
||||
Style::new().fg(match_percent_color(percent)),
|
||||
));
|
||||
}
|
||||
@@ -97,7 +97,7 @@ impl UiView for FunctionDiffUi {
|
||||
.and_then(|t| t.format(&state.time_format).ok())
|
||||
.unwrap_or_else(|| "N/A".to_string());
|
||||
line_r.spans.push(Span::styled(
|
||||
format!("Last reload: {}", reload_time),
|
||||
format!("Last reload: {reload_time}"),
|
||||
Style::new().fg(Color::White),
|
||||
));
|
||||
line_r.spans.push(Span::styled(
|
||||
@@ -170,32 +170,31 @@ impl UiView for FunctionDiffUi {
|
||||
|
||||
let mut prev_text = None;
|
||||
let mut prev_margin_text = None;
|
||||
if self.three_way {
|
||||
if let Some((obj, symbol_idx, symbol_diff)) =
|
||||
if self.three_way
|
||||
&& let Some((obj, symbol_idx, symbol_diff)) =
|
||||
get_symbol(state.prev_obj.as_ref(), self.prev_sym)
|
||||
{
|
||||
let mut text = Text::default();
|
||||
let rect = content_chunks[4].inner(Margin::new(0, 1));
|
||||
self.print_sym(
|
||||
&mut text,
|
||||
obj,
|
||||
symbol_idx,
|
||||
symbol_diff,
|
||||
&state.diff_obj_config,
|
||||
rect,
|
||||
&self.right_highlight,
|
||||
result,
|
||||
true,
|
||||
);
|
||||
max_width = max_width.max(text.width());
|
||||
prev_text = Some(text);
|
||||
{
|
||||
let mut text = Text::default();
|
||||
let rect = content_chunks[4].inner(Margin::new(0, 1));
|
||||
self.print_sym(
|
||||
&mut text,
|
||||
obj,
|
||||
symbol_idx,
|
||||
symbol_diff,
|
||||
&state.diff_obj_config,
|
||||
rect,
|
||||
&self.right_highlight,
|
||||
result,
|
||||
true,
|
||||
);
|
||||
max_width = max_width.max(text.width());
|
||||
prev_text = Some(text);
|
||||
|
||||
// Render margin
|
||||
let mut text = Text::default();
|
||||
let rect = content_chunks[3].inner(Margin::new(1, 1));
|
||||
self.print_margin(&mut text, symbol_diff, rect);
|
||||
prev_margin_text = Some(text);
|
||||
}
|
||||
// Render margin
|
||||
let mut text = Text::default();
|
||||
let rect = content_chunks[3].inner(Margin::new(1, 1));
|
||||
self.print_margin(&mut text, symbol_diff, rect);
|
||||
prev_margin_text = Some(text);
|
||||
}
|
||||
|
||||
let max_scroll_x =
|
||||
@@ -450,11 +449,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),
|
||||
@@ -538,7 +537,7 @@ impl FunctionDiffUi {
|
||||
let label_text = match segment.text {
|
||||
DiffText::Basic(text) => text.to_string(),
|
||||
DiffText::Line(num) => format!("{num} "),
|
||||
DiffText::Address(addr) => format!("{:x}:", addr),
|
||||
DiffText::Address(addr) => format!("{addr:x}:"),
|
||||
DiffText::Opcode(mnemonic, _op) => format!("{mnemonic} "),
|
||||
DiffText::Argument(arg) => arg.to_string(),
|
||||
DiffText::BranchDest(addr) => format!("{addr:x}"),
|
||||
@@ -546,7 +545,7 @@ impl FunctionDiffUi {
|
||||
sym.demangled_name.as_ref().unwrap_or(&sym.name).clone()
|
||||
}
|
||||
DiffText::Addend(addend) => match addend.cmp(&0i64) {
|
||||
Ordering::Greater => format!("+{:#x}", addend),
|
||||
Ordering::Greater => format!("+{addend:#x}"),
|
||||
Ordering::Less => format!("-{:#x}", -addend),
|
||||
_ => String::new(),
|
||||
},
|
||||
@@ -561,15 +560,18 @@ impl FunctionDiffUi {
|
||||
let len = label_text.len();
|
||||
let highlighted =
|
||||
highlight_kind != HighlightKind::None && *highlight == highlight_kind;
|
||||
if let Some((cx, cy)) = result.click_xy {
|
||||
if cx >= sx && cx < sx + len as u16 && cy == sy {
|
||||
new_highlight = Some(highlight_kind);
|
||||
}
|
||||
if let Some((cx, cy)) = result.click_xy
|
||||
&& cx >= sx
|
||||
&& cx < sx + len as u16
|
||||
&& cy == sy
|
||||
{
|
||||
new_highlight = Some(highlight_kind);
|
||||
}
|
||||
let mut style = Style::new().fg(match segment.color {
|
||||
DiffTextColor::Normal => Color::Gray,
|
||||
DiffTextColor::Dim => Color::DarkGray,
|
||||
DiffTextColor::Bright => Color::White,
|
||||
DiffTextColor::DataFlow => Color::LightCyan,
|
||||
DiffTextColor::Replace => Color::Cyan,
|
||||
DiffTextColor::Delete => Color::Red,
|
||||
DiffTextColor::Insert => Color::Green,
|
||||
@@ -650,12 +652,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
|
||||
}
|
||||
|
||||
+32
-20
@@ -27,6 +27,7 @@ all = [
|
||||
"mips",
|
||||
"ppc",
|
||||
"x86",
|
||||
"superh"
|
||||
]
|
||||
# Implicit, used to check if any arch is enabled
|
||||
any-arch = [
|
||||
@@ -40,6 +41,7 @@ any-arch = [
|
||||
"dep:regex",
|
||||
"dep:similar",
|
||||
"dep:syn",
|
||||
"dep:encoding_rs"
|
||||
]
|
||||
bindings = [
|
||||
"dep:prost",
|
||||
@@ -60,7 +62,10 @@ config = [
|
||||
"dep:semver",
|
||||
"dep:typed-path",
|
||||
]
|
||||
dwarf = ["dep:gimli"]
|
||||
dwarf = [
|
||||
"dep:gimli",
|
||||
"dep:typed-arena",
|
||||
]
|
||||
serde = [
|
||||
"dep:pbjson",
|
||||
"dep:pbjson-build",
|
||||
@@ -75,6 +80,8 @@ std = [
|
||||
"object/std",
|
||||
"prost?/std",
|
||||
"serde?/std",
|
||||
"similar?/std",
|
||||
"typed-arena?/std",
|
||||
"typed-path?/std",
|
||||
"dep:filetime",
|
||||
"dep:memmap2",
|
||||
@@ -89,7 +96,7 @@ ppc = [
|
||||
"any-arch",
|
||||
"dep:cwdemangle",
|
||||
"dep:cwextab",
|
||||
"dep:ppc750cl",
|
||||
"dep:powerpc",
|
||||
"dep:rlwinmdec",
|
||||
]
|
||||
x86 = [
|
||||
@@ -110,6 +117,9 @@ arm64 = [
|
||||
"dep:yaxpeax-arch",
|
||||
"dep:yaxpeax-arm",
|
||||
]
|
||||
superh = [
|
||||
"any-arch",
|
||||
]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["all"]
|
||||
@@ -117,18 +127,18 @@ features = ["all"]
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0", default-features = false }
|
||||
filetime = { version = "0.2", optional = true }
|
||||
flagset = { version = "0.4", default-features = false, optional = true, git = "https://github.com/enarx/flagset.git", rev = "a1fe9369b3741e43fec45da1998e83b9d78966a2" }
|
||||
flagset = { version = "0.4", default-features = false, optional = true }
|
||||
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"] }
|
||||
pbjson = { version = "0.7", default-features = false, optional = true }
|
||||
prost = { version = "0.13", default-features = false, features = ["prost-derive"], optional = true }
|
||||
object = { git = "https://github.com/gimli-rs/object", rev = "16ff70aa6fbd97d6bb7b92375929f4d72414c32b", default-features = false, features = ["read_core", "elf", "coff"] }
|
||||
pbjson = { version = "0.8", default-features = false, optional = true }
|
||||
prost = { version = "0.14", default-features = false, features = ["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 }
|
||||
@@ -136,16 +146,17 @@ semver = { version = "1.0", default-features = false, optional = true }
|
||||
serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true }
|
||||
|
||||
# dwarf
|
||||
gimli = { version = "0.31", default-features = false, features = ["read"], optional = true }
|
||||
gimli = { version = "0.32", default-features = false, features = ["read"], optional = true }
|
||||
typed-arena = { version = "2.0", default-features = false, optional = true }
|
||||
|
||||
# ppc
|
||||
cwdemangle = { version = "1.0", optional = true }
|
||||
cwextab = { version = "1.0", optional = true, git = "https://github.com/CelestialAmber/cwextab.git" }
|
||||
ppc750cl = { version = "0.3", optional = true }
|
||||
rlwinmdec = { version = "1.1", optional = true, git = "https://github.com/CelestialAmber/rlwinmdec.git" }
|
||||
cwextab = { version = "1.1", optional = true }
|
||||
powerpc = { version = "0.4", optional = true }
|
||||
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.4", default-features = false, features = ["all_extensions"], optional = true }
|
||||
|
||||
# x86
|
||||
cpp_demangle = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
|
||||
@@ -153,7 +164,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.9", optional = true }
|
||||
arm-attr = { version = "0.2", optional = true }
|
||||
|
||||
# arm64
|
||||
@@ -161,14 +172,15 @@ yaxpeax-arch = { version = "0.3", default-features = false, optional = true }
|
||||
yaxpeax-arm = { version = "0.3", default-features = false, optional = true }
|
||||
|
||||
# build
|
||||
notify = { version = "8.0.0", optional = true }
|
||||
notify = { version = "8.1.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.20", optional = true }
|
||||
time = { version = "0.3", optional = true }
|
||||
encoding_rs = { version = "0.8.35", optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version = "0.3", optional = true }
|
||||
winapi = { version = "0.3", optional = true, features = ["winbase"] }
|
||||
|
||||
# For Linux static binaries, use rustls
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
@@ -182,10 +194,10 @@ self_update = { version = "0.42", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
heck = { version = "0.5", optional = true }
|
||||
pbjson-build = { version = "0.7", optional = true }
|
||||
pbjson-build = { version = "0.8", optional = true }
|
||||
prettyplease = { version = "0.2", optional = true }
|
||||
proc-macro2 = { version = "1.0", optional = true }
|
||||
prost-build = { version = "0.13", optional = true }
|
||||
prost-build = { version = "0.14", optional = true }
|
||||
quote = { version = "1.0", optional = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = { version = "1.0" }
|
||||
@@ -194,4 +206,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"
|
||||
|
||||
@@ -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 [powerpc](https://github.com/encounter/powerpc-rs).
|
||||
- **`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).
|
||||
|
||||
@@ -25,6 +25,20 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "analyzeDataFlow",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "(Experimental) Perform data flow analysis",
|
||||
"description": "Use data flow analysis to display known information about register contents where possible"
|
||||
},
|
||||
{
|
||||
"id": "showDataFlow",
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"name": "Show data flow",
|
||||
"description": "Show data flow analysis results in place of register name where present"
|
||||
},
|
||||
{
|
||||
"id": "spaceBetweenArgs",
|
||||
"type": "boolean",
|
||||
@@ -264,7 +278,8 @@
|
||||
"id": "ppc",
|
||||
"name": "PowerPC",
|
||||
"properties": [
|
||||
"ppc.calculatePoolRelocations"
|
||||
"ppc.calculatePoolRelocations",
|
||||
"analyzeDataFlow"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -60,10 +60,10 @@ pub struct ConfigGroup {
|
||||
}
|
||||
|
||||
fn build_doc(name: &str, description: Option<&str>) -> TokenStream {
|
||||
let mut doc = format!(" {}", name);
|
||||
let mut doc = format!(" {name}");
|
||||
let mut out = quote! { #[doc = #doc] };
|
||||
if let Some(description) = description {
|
||||
doc = format!(" {}", description);
|
||||
doc = format!(" {description}");
|
||||
out.extend(quote! { #[doc = ""] });
|
||||
out.extend(quote! { #[doc = #doc] });
|
||||
}
|
||||
@@ -443,9 +443,9 @@ pub fn generate_diff_config() {
|
||||
}
|
||||
impl core::fmt::Display for ConfigPropertyValue {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
ConfigPropertyValue::Boolean(value) => write!(f, "{}", value),
|
||||
ConfigPropertyValue::Choice(value) => write!(f, "{}", value),
|
||||
match *self {
|
||||
ConfigPropertyValue::Boolean(value) => write!(f, "{value}"),
|
||||
ConfigPropertyValue::Choice(value) => f.write_str(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
|
||||
+120
-70
@@ -11,11 +11,11 @@ use object::{Endian as _, Object as _, ObjectSection as _, ObjectSymbol as _, el
|
||||
use unarm::{args, arm, thumb};
|
||||
|
||||
use crate::{
|
||||
arch::Arch,
|
||||
arch::{Arch, RelocationOverride, RelocationOverrideTarget},
|
||||
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,
|
||||
@@ -348,47 +356,57 @@ impl Arch for ArchArm {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn implcit_addend(
|
||||
fn relocation_override(
|
||||
&self,
|
||||
_file: &object::File<'_>,
|
||||
section: &object::Section,
|
||||
address: u64,
|
||||
_relocation: &object::Relocation,
|
||||
flags: RelocationFlags,
|
||||
) -> Result<i64> {
|
||||
let section_data = section.data()?;
|
||||
let address = address as usize;
|
||||
Ok(match flags {
|
||||
// ARM calls
|
||||
RelocationFlags::Elf(elf::R_ARM_PC24)
|
||||
| RelocationFlags::Elf(elf::R_ARM_XPC25)
|
||||
| RelocationFlags::Elf(elf::R_ARM_CALL) => {
|
||||
let data = section_data[address..address + 4].try_into()?;
|
||||
let addend = self.endianness.read_i32_bytes(data);
|
||||
let imm24 = addend & 0xffffff;
|
||||
(imm24 << 2) << 8 >> 8
|
||||
relocation: &object::Relocation,
|
||||
) -> Result<Option<RelocationOverride>> {
|
||||
match relocation.flags() {
|
||||
// Handle ELF implicit relocations
|
||||
object::RelocationFlags::Elf { r_type } => {
|
||||
if relocation.has_implicit_addend() {
|
||||
let section_data = section.data()?;
|
||||
let address = address as usize;
|
||||
let addend = match r_type {
|
||||
// ARM calls
|
||||
elf::R_ARM_PC24 | elf::R_ARM_XPC25 | elf::R_ARM_CALL => {
|
||||
let data = section_data[address..address + 4].try_into()?;
|
||||
let addend = self.endianness.read_i32_bytes(data);
|
||||
let imm24 = addend & 0xffffff;
|
||||
(imm24 << 2) << 8 >> 8
|
||||
}
|
||||
|
||||
// Thumb calls
|
||||
elf::R_ARM_THM_PC22 | elf::R_ARM_THM_XPC22 => {
|
||||
let data = section_data[address..address + 2].try_into()?;
|
||||
let high = self.endianness.read_i16_bytes(data) as i32;
|
||||
let data = section_data[address + 2..address + 4].try_into()?;
|
||||
let low = self.endianness.read_i16_bytes(data) as i32;
|
||||
|
||||
let imm22 = ((high & 0x7ff) << 11) | (low & 0x7ff);
|
||||
(imm22 << 1) << 9 >> 9
|
||||
}
|
||||
|
||||
// Data
|
||||
elf::R_ARM_ABS32 => {
|
||||
let data = section_data[address..address + 4].try_into()?;
|
||||
self.endianness.read_i32_bytes(data)
|
||||
}
|
||||
|
||||
flags => bail!("Unsupported ARM implicit relocation {flags:?}"),
|
||||
};
|
||||
Ok(Some(RelocationOverride {
|
||||
target: RelocationOverrideTarget::Keep,
|
||||
addend: addend as i64,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
// Thumb calls
|
||||
RelocationFlags::Elf(elf::R_ARM_THM_PC22)
|
||||
| RelocationFlags::Elf(elf::R_ARM_THM_XPC22) => {
|
||||
let data = section_data[address..address + 2].try_into()?;
|
||||
let high = self.endianness.read_i16_bytes(data) as i32;
|
||||
let data = section_data[address + 2..address + 4].try_into()?;
|
||||
let low = self.endianness.read_i16_bytes(data) as i32;
|
||||
|
||||
let imm22 = ((high & 0x7ff) << 11) | (low & 0x7ff);
|
||||
(imm22 << 1) << 9 >> 9
|
||||
}
|
||||
|
||||
// Data
|
||||
RelocationFlags::Elf(elf::R_ARM_ABS32) => {
|
||||
let data = section_data[address..address + 4].try_into()?;
|
||||
self.endianness.read_i32_bytes(data)
|
||||
}
|
||||
|
||||
flags => bail!("Unsupported ARM implicit relocation {flags:?}"),
|
||||
} as i64)
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn demangle(&self, name: &str) -> Option<String> {
|
||||
@@ -441,7 +459,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 +473,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,
|
||||
@@ -561,7 +585,7 @@ fn push_args(
|
||||
arg_cb(InstructionPart::basic("}"))?;
|
||||
}
|
||||
args::Argument::CoprocNum(value) => {
|
||||
arg_cb(InstructionPart::opaque(format!("p{}", value)))?;
|
||||
arg_cb(InstructionPart::opaque(format!("p{value}")))?;
|
||||
}
|
||||
args::Argument::ShiftImm(shift) => {
|
||||
arg_cb(InstructionPart::opaque(shift.op.to_string()))?;
|
||||
@@ -609,6 +633,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 +668,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,
|
||||
}
|
||||
}
|
||||
|
||||
+299
-293
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user