Compare commits

..

78 Commits

Author SHA1 Message Date
Luke Street 6bcac60df8 ci: Re-enable cargo release 2026-07-10 00:33:21 -06:00
Luke Street b34108fa24 Bump a few more deps & fix advisories 2026-07-10 00:29:27 -06:00
Luke Street 5f9e9063f8 clippy fixes & cargo update 2026-07-10 00:22:48 -06:00
Luke Street a3e11149f7 Version 3.7.3 2026-07-09 23:48:39 -06:00
Haydn Trigg d22c785610 Update GitHub Actions (#357)
* Update build.yaml

* Update build.yaml
2026-07-09 23:46:59 -06:00
Anghelo Carvajal 5035f8ec36 Update rabbitizer integration for 2.0.0-alpha.9 (#347) 2026-07-09 23:46:05 -06:00
Dávid Balatoni b8f401b0ba Make the .mdebug line parser support EE-GCC (PS2) (#362) 2026-07-09 23:45:53 -06:00
Luke Street 282783c081 Suppress unused warnings in objdiff-cli for now 2026-07-09 23:45:10 -06:00
LagoLunatic f5ae76e64d ARM: Fix index out of bounds error when there are no mapping symbols (#359) 2026-07-09 23:30:03 -06:00
LagoLunatic dbabdbff11 ARM: Fix trailing relocations being ignored when inferring function sizes (#360)
* ARM: Fix relocations being ignored when inferring function sizes

* ARM: Add test for trailing relocations

* ARM: Fix trailing relocations being cut in half
2026-07-09 23:29:51 -06:00
Bruno Macabeus 2fdba8842e Add support for R_ARM_V4BX (#377) 2026-07-09 23:28:01 -06:00
Darxoon 0fe4fadbc3 Fix armcc symbols always being hidden (#364) 2026-07-09 23:27:40 -06:00
Michael Rieder 04be189e51 Honor symbol mappings in CLI diffs (#378) 2026-07-09 23:26:50 -06:00
Ethan Roseman 4d0a273d65 Remove git-based dependencies and update several (#379) 2026-07-09 23:25:34 -06:00
Dávid Balatoni 73917c12a8 Fix PS2 MWCC line number parsing (#363) 2026-06-17 20:23:34 -04:00
Haydn Trigg 2cd1e2e0ce Add missing x64 relocation types (#355)
* Add missing x64 relocation types

* Format x86.rs

* Update x86.rs
2026-06-01 21:50:05 -06:00
Haydn Trigg a6ddc506f8 Bump Object Crate to 0.39.1 (#348)
* Bump Object Crate to 0.39.0

* Bump Object Crate to 0.39.1
2026-06-01 21:49:52 -06:00
Haydn Trigg b8d7b3bc1a Strip trailing int3 instructions (alignment padding) for x64 (#354)
* Strip trailing int3 instructions (alignment padding) for x64

* Improve strip trailing int3 instructions

* Improve strip trailing int3 instructions again
2026-06-01 20:58:45 -06:00
LagoLunatic 02daae417c Version 3.7.2 2026-05-31 23:25:27 -04:00
LagoLunatic 8b1b4a9a79 Add config option to limit which string literal encodings are used (#356)
* Fix annoying rust-analyzer error with a cfg

* Refactor literal info into a struct

* Add field hide a literal from the GUI but still use for diffing

* Add config option to limit string literal diffing

* Fix deprecation warning `float_literal_f32_fallback`

* Hide non-preferred string encodings from the GUI

* Fix new cargo check warning

* Clippy again

* Rename "None" -> "Auto"
2026-05-31 23:23:10 -04:00
LagoLunatic 13f1267eac Fix several issues with string literal detection (#351)
* Support detecting more symbol names for strings

* Fix string detectiong being broken for encodings that use null terminators

* Add test cases for string detection and decoding

* Fix test helper function sometimes being considered dead code

* Clippy fixes
2026-05-03 21:27:12 -04:00
Luke Street 3cebee6766 Version 3.7.1 2026-03-21 09:54:09 -06:00
Amber Brault ab0538e9a4 Fix extab hover window display issue (#344)
* Bleh

* Fix formatting

* Update arch_ppc__read_extab.snap

* Put code in separate function

* Make compiler shut up

* Update mod.rs
2026-03-21 09:38:01 -06:00
Luke Street b643265809 Disable advisories check in CI 2026-03-20 23:29:54 -06:00
Bruno Macabeus 7a65e07e9b Add support for R_ARM_THM_PC8 and R_ARM_THM_PC11 (#342)
* Add support for `R_ARM_THM_JUMP8` and `R_ARM_THM_JUMP11`

* Fix `scan_instructions_internal` when reading 2-bytes instruction
2026-03-16 22:08:03 -06:00
Bruno Macabeus 3c4092b965 Fix cache issue when parsing the same file with different side params (#341) 2026-03-15 20:21:05 -06:00
Luke Street 0f599bea2b Version 3.7.0 2026-03-08 13:30:17 -06:00
Luke Street 9927fc35b6 Update time and bytes crates (resolves advisories) 2026-03-08 13:07:20 -06:00
Sebastián Meljem 16fb6a491f Demangle function name on decomp.me scratch creation (#329) 2026-03-08 11:14:24 -06:00
Luke Street 2541aef71a Add symbol kind and structured flags to diff proto output (#337)
* Add symbol kind to diff proto output

Expose SymbolKind (Function, Object, Section, Unknown) in the
DiffSymbol proto message, allowing CLI oneshot diff consumers to
distinguish symbol types without inferring from instruction/data
presence.

* Stop filtering symbols from diff output, replace flags bitmask with message

Remove the symbol filter (size == 0 / Ignored) from DiffObject so all
symbols are emitted. This fixes target_symbol indices being invalid
after filtering.

Replace the DiffSymbolFlag bitmask enum + uint32 field with a
DiffSymbolFlags message of booleans, adding ignored and size_inferred
flags so consumers can filter on their end.
2026-03-08 11:13:40 -06:00
First Last 7ad1f95bf7 Fix symbol index-to-addend bug (#336) 2026-02-21 21:15:53 -07:00
LagoLunatic 66c879a95d Version 3.6.1 (#328) 2026-01-30 05:40:02 +00:00
rjkiv d53c358d2f automatically match MSVC symbols in anonymous namespaces (#325)
* automatically match MSVC symbols in anonymous namespaces

* tweak logic for detecting anonymous MSVC symbols

* fix package format

* address feedback

* lazylock the regex

* formatting

* use once_cell instead

* formatting

* remove regex
2026-01-30 00:13:10 -05:00
Avery Townsend f24c017545 merge gnu function and data sections (#327) 2026-01-29 16:58:52 -07:00
Luke Street 3b8603ab22 Version 3.6.0 2026-01-24 20:29:33 -07:00
Luke Street 86b7d6b229 Remove extra relocs in PPC data_reloc_size 2026-01-24 20:23:40 -07:00
Haydn Trigg 3f82781808 Better COFF String Detection and Big Endian Wide Strings (#321)
* Better COFF and Big Endian Strings

* Cargo Check and Format Fixes

* Rework DataType::String in display_literals

---------

Co-authored-by: Luke Street <luke@street.dev>
2026-01-25 03:23:06 +00:00
Haydn Trigg 926dd06ce9 Handle IMAGE_REL_PPC_ADDR32 size in Coff (#320) 2026-01-24 20:22:45 -07:00
Luke Street de70b6fca0 Restore objdiff-cli oneshot mode (JSON output) (#323) 2026-01-24 19:58:29 -07:00
LagoLunatic 79542a4725 Significantly improve diffing performance and fix minor bug with bss section match percents (#316)
* Move symbol name processing from diff step to read step

* Use min_by_key instead of sorted_unstable_by_key

* Sort all symbols by address when reading the object

This fixes an underreport bug in diff_bss_section.

* More symbol sorting logic on read

* Move is_name_compiler_generated into SymbolFlag & filter out File symbols

---------

Co-authored-by: Luke Street <luke@street.dev>
2026-01-24 19:41:03 -07:00
Avery Townsend 0ebd2ae459 add more i386 elf relocs (#319)
Co-authored-by: Avery Townsend <self@codeshaunted.com>
2026-01-24 19:33:42 -07:00
LagoLunatic 08ebea8bd2 Fix the symbol filter not working in the mapping view (#315)
* Fix the symbol filter not working in the mapping view

* Separate the mapping filter from the regular filter

* Make set/get search function names consistent with each other
2026-01-25 01:15:01 +00:00
Luke Street 46b8890159 Fix clippy & advisory warnings 2026-01-24 17:07:25 -07:00
LagoLunatic 8356fe670d Implement go to next/previous difference (#313) 2026-01-24 16:47:27 -07:00
Luke Street 7834185223 Version 3.5.1 2025-12-23 12:33:12 -07:00
LagoLunatic 216dd70d5b Fix mouse hovers overriding typing in the symbol search field (#308) 2025-12-23 12:32:07 -07:00
LagoLunatic 976e629f08 Fix non-strings also being escaped when copied (#307)
* Fix non-strings also being escaped when copied

* Move copy escape logic into objdiff-core
2025-12-23 12:31:36 -07:00
Luke Street 67b28b7da1 Version 3.5.0 2025-12-21 10:02:59 -07:00
LagoLunatic c0b9bfae21 Fix special ASCII characters not being escaped when copied to clipboard (#306) 2025-12-21 09:58:45 -07:00
LagoLunatic f3f4b0482a Combine .init sections separately from .text sections (#304) 2025-12-21 09:58:13 -07:00
LagoLunatic 278683f103 Improve automatic symbol pairing for functions with compiler-generated names (#303)
* Fix `__arraydtor$1234`s sometimes being swapped

The list of symbols is not necessarily sorted by address, so if there are multiple `__arraydtor$1234` functions it's possible that they're in reverse order on the right, which would cause them to be paired up incorrectly.

To fix this we sort by section index and symbol address after filtering down the list of symbols to ones that have matching names.

* Support pairing up symbols for anonymous classes

* Disallow exact-name pairups for compiler-generated symbols
2025-12-21 09:57:17 -07:00
LagoLunatic 6ad60de32a Allow clicking on branch arrows to scroll target into view (#302) 2025-12-21 09:55:55 -07:00
Luke Street 90c93164ab Update deny.toml 2025-12-21 09:36:27 -07:00
Luke Street c02eb31dbb Version 3.4.5 2025-12-03 18:03:11 -07:00
Darxoon 86d92866aa Fix "Unsupported ARM implicit relocation 29" on armcc binaries (#296) 2025-12-03 17:59:24 -07:00
Luke Street 51c3af2bbe Version 3.4.4 2025-11-26 19:33:00 -07:00
LagoLunatic d0b8b449d9 Ignore hidden symbols when diffing data sections (#291) 2025-11-26 18:49:36 -07:00
LagoLunatic 32f5f202f7 Update outdated extab test snapshot (#292) 2025-11-26 17:56:32 -05:00
LagoLunatic 481dbc185a Support more string encodings and allow copying unescaped strings (#288)
* Copy strings without escape sequences in them

* Add support for more encodings from encoding_rs

Also use encoding_rs instead of CStr for UTF-8.
2025-11-22 19:36:41 -07:00
Luke Street 26a4cc79cf Version 3.4.3 2025-11-22 12:47:03 -07:00
Luke Street 5c96c2ee51 Update cwextab 2025-11-22 12:45:58 -07:00
Luke Street d162fe847e Remove --mapping, --selecting-{left,right} from CLI 2025-11-21 23:55:26 -07:00
Luke Street 2a24eb5aec Version 3.4.2 2025-11-21 22:15:20 -07:00
Aetias f7c291bd55 arm: Fix .word reading 4 bytes when less than 4 remain (#285) 2025-11-20 15:38:55 -07:00
Luke Street 03a578c1bb Update test snapshots for new cwextab 2025-11-18 22:29:42 -07:00
Luke Street d09ef8e207 Version v3.4.1 2025-11-18 22:22:43 -07:00
Luke Street 63552a58ae Move copy button to first header row 2025-11-18 22:22:02 -07:00
Luke Street 827f4a42bd Upgrade all dependencies (incl. egui) 2025-11-18 22:21:46 -07:00
Franco M b2dcecc5d8 Copy button (#271)
* Update diff.rs

* Fixes

* More fixes

* More fixes...

* Cargo fmt

* fmt

* Trim extra

* cargo +nightly fmt

* Update 'Copy ASM' button text to include emoji
2025-11-18 22:00:20 -07:00
Dávid Balatoni 67b237eab6 Adjust symbol name matching logic for GCC (#278)
* Adjust symbol name matching logic for GCC

* Turn $ and . into a list

* Fix borrow issue
2025-11-18 21:55:55 -07:00
Aetias 66da80ff69 arm: Fix .word not on 4-byte boundary (#282) 2025-11-18 21:55:40 -07:00
Luke Street 0a85f540f2 Version v3.4.0 2025-11-12 12:22:01 -07:00
Anghelo Carvajal d79b5b1233 Update gnuv2_demangle to 0.4 (#280) 2025-11-09 11:53:02 -07:00
Anghelo Carvajal 8f5519cb6a Update gnuv2_demangle to version 0.3 (#275) 2025-10-12 21:19:07 -06:00
Aetias 19ec08be9a Update unarm to 2.0 (#274)
* Migrate to unarm 2.0

* Update unarm to proper 2.0 release

* Deduplicate formatters for opaque instruction parts in ARM

* Add option to enable VFPv2 for ARM

* Update unarm to 2.0.1

* Fix read order for big-endian Thumb code

* Skip leading space in ARM instruction params

* Update ARM tests

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

* cargo fmt

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

* configs for show symbol size

* move symbol size config to config-schema
2025-10-08 20:32:07 -06:00
99 changed files with 12144 additions and 9099 deletions
+18 -19
View File
@@ -28,7 +28,7 @@ jobs:
sudo apt-get update
sudo apt-get -y install libgtk-3-dev
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
@@ -47,7 +47,7 @@ jobs:
RUSTFLAGS: -D warnings
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Rust toolchain
# We use nightly options in rustfmt.toml
uses: dtolnay/rust-toolchain@nightly
@@ -62,12 +62,12 @@ jobs:
strategy:
matrix:
checks:
- advisories
# - advisories
- bans licenses sources
# Prevent new advisories from failing CI
continue-on-error: ${{ matrix.checks == 'advisories' }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check ${{ matrix.checks }}
@@ -86,7 +86,7 @@ jobs:
sudo apt-get update
sudo apt-get -y install libgtk-3-dev
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache Rust workspace
@@ -145,10 +145,10 @@ jobs:
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install uv
if: matrix.build == 'zigbuild'
uses: astral-sh/setup-uv@v6
uses: astral-sh/setup-uv@v7
- name: Install cargo-zigbuild
if: matrix.build == 'zigbuild'
run: |
@@ -167,7 +167,7 @@ jobs:
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
path: |
@@ -213,10 +213,10 @@ jobs:
sudo apt-get update
sudo apt-get -y install ${{ matrix.packages }}
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Install uv
if: matrix.build == 'zigbuild'
uses: astral-sh/setup-uv@v6
uses: astral-sh/setup-uv@v7
- name: Install cargo-zigbuild
if: matrix.build == 'zigbuild'
run: |
@@ -235,7 +235,7 @@ jobs:
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
path: |
@@ -248,7 +248,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@nightly
with:
@@ -256,7 +256,7 @@ jobs:
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6.4.0
with:
node-version: lts/*
- name: Install dependencies
@@ -264,7 +264,7 @@ jobs:
- name: Build
run: npm -C objdiff-wasm run build
- name: Upload artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: wasm
path: objdiff-wasm/dist/
@@ -277,7 +277,7 @@ jobs:
needs: [ build-cli, build-gui, build-wasm ]
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Check git tag against package versions
shell: bash
run: |
@@ -340,12 +340,11 @@ jobs:
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
uses: actions/checkout@v6
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Publish
@@ -360,9 +359,9 @@ jobs:
needs: [ check-version ]
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6.4.0
with:
node-version: lts/*
registry-url: 'https://registry.npmjs.org'
Generated
+2082 -1326
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -14,7 +14,7 @@ default-members = [
resolver = "3"
[workspace.package]
version = "3.3.0"
version = "3.7.3"
authors = ["Luke Street <luke@street.dev>"]
edition = "2024"
license = "MIT OR Apache-2.0"
+7 -2
View File
@@ -75,6 +75,13 @@ ignore = [
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
{ id = "RUSTSEC-2024-0436", reason = "Unmaintained paste crate is an indirect dependency" },
{ id = "RUSTSEC-2025-0052", reason = "Unmaintained async-std crate is an indirect dependency" },
{ id = "RUSTSEC-2025-0134", reason = "Unmaintained rustls-pemfile crate is an indirect dependency" },
{ id = "RUSTSEC-2026-0098", reason = "Build-time objdiff-wasm wit-deps dependency still uses reqwest 0.11; latest wit-deps has no safe upgrade path" },
{ id = "RUSTSEC-2026-0099", reason = "Build-time objdiff-wasm wit-deps dependency still uses reqwest 0.11; latest wit-deps has no safe upgrade path" },
{ id = "RUSTSEC-2026-0104", reason = "Build-time objdiff-wasm wit-deps dependency still uses reqwest 0.11; latest wit-deps has no safe upgrade path" },
{ id = "RUSTSEC-2026-0192", reason = "Unmaintained ttf-parser is an egui 0.33 indirect dependency; egui 0.35 requires raising MSRV from 1.88 to 1.92" },
{ id = "RUSTSEC-2026-0194", reason = "Build-time wayland-scanner dependency parses vendored protocol XML; no newer stable wayland-scanner release is available" },
{ id = "RUSTSEC-2026-0195", reason = "Build-time wayland-scanner dependency parses vendored protocol XML; no newer stable wayland-scanner release is available" },
]
# If this is true, then cargo deny will use the git executable to fetch advisory database.
# If this is false, then it uses a built-in git library.
@@ -242,8 +249,6 @@ allow-git = []
[sources.allow-org]
# github.com organizations to allow git sources for
github = [
"encounter",
"gimli-rs", # gimli
]
# gitlab.com organizations to allow git sources for
gitlab = []
+4 -4
View File
@@ -16,19 +16,19 @@ publish = false
anyhow = "1.0"
argp = "0.4"
crossterm = "0.29"
enable-ansi-support = "0.2"
enable-ansi-support = "0.3"
memmap2 = "0.9"
objdiff-core = { path = "../objdiff-core", features = ["all"] }
prost = "0.14"
ratatui = "0.29"
rayon = "1.10"
ratatui = "0.30"
rayon = "1.11"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
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.11"
typed-path = "0.12"
[target.'cfg(target_env = "musl")'.dependencies]
mimalloc = "0.1"
+82 -29
View File
@@ -1,4 +1,6 @@
#![allow(unused)] // TODO
use std::{
collections::BTreeMap,
io::stdout,
mem,
sync::{
@@ -19,6 +21,7 @@ use crossterm::{
},
};
use objdiff_core::{
bindings::diff::DiffResult,
build::{
BuildConfig, BuildStatus,
watcher::{Watcher, create_watcher},
@@ -28,7 +31,7 @@ use objdiff_core::{
build_globset,
path::{check_path_buf, platform_path, platform_path_serde_option},
},
diff::{DiffObjConfig, MappingConfig, ObjectDiff},
diff::{self, DiffObjConfig, DiffSide, MappingConfig, ObjectDiff},
jobs::{
Job, JobQueue, JobResult,
objdiff::{ObjDiffConfig, start_build},
@@ -40,7 +43,10 @@ use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
use crate::{
cmd::apply_config_args,
util::term::crossterm_panic_handler,
util::{
output::{OutputFormat, write_output},
term::crossterm_panic_handler,
},
views::{EventControlFlow, EventResult, UiView, function_diff::FunctionDiffUi},
};
@@ -60,29 +66,28 @@ pub struct Args {
#[argp(option, short = 'u')]
/// Unit name within project
unit: Option<String>,
#[argp(option, short = 'o', from_str_fn(platform_path))]
/// Output file (one-shot mode) ("-" for stdout)
output: Option<Utf8PlatformPathBuf>,
#[argp(option)]
/// Output format (json, json-pretty, proto) (default: json)
format: Option<String>,
#[argp(positional)]
/// Function symbol to diff
symbol: Option<String>,
#[argp(option, short = 'c')]
/// Configuration property (key=value)
config: Vec<String>,
#[argp(option, short = 'm')]
/// Symbol mapping (target=base)
mapping: Vec<String>,
#[argp(option)]
/// Left symbol name for selection
selecting_left: Option<String>,
#[argp(option)]
/// Right symbol name for selection
selecting_right: Option<String>,
}
pub fn run(args: Args) -> Result<()> {
let (target_path, base_path, project_config, unit_options) =
let (target_path, base_path, project_config, unit_options, symbol_mappings) =
match (&args.target, &args.base, &args.project, &args.unit) {
(Some(_), Some(_), None, None)
| (Some(_), None, None, None)
| (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None, None),
| (None, Some(_), None, None) => {
(args.target.clone(), args.base.clone(), None, None, BTreeMap::new())
}
(None, None, p, u) => {
let project = match p {
Some(project) => project.clone(),
@@ -94,7 +99,7 @@ pub fn run(args: Args) -> Result<()> {
let Some((project_config, project_config_info)) =
objdiff_core::config::try_project_config(project.as_ref())
else {
bail!("Project config not found in {}", &project)
bail!("Project config not found in {}", project)
};
let project_config = project_config.with_context(|| {
format!("Reading project config {}", project_config_info.path.display())
@@ -162,18 +167,65 @@ pub fn run(args: Args) -> Result<()> {
let unit_options = units.get(unit_idx).and_then(|u| u.options().cloned());
let target_path = object.target_path.clone();
let base_path = object.base_path.clone();
(target_path, base_path, Some(project_config), unit_options)
let symbol_mappings = object.symbol_mappings.clone();
(target_path, base_path, Some(project_config), unit_options, symbol_mappings)
}
_ => bail!("Either target and base or project and unit must be specified"),
};
run_interactive(args, target_path, base_path, project_config, unit_options)
if let Some(output) = &args.output {
run_oneshot(
&args,
output,
target_path.as_deref(),
base_path.as_deref(),
project_config.as_ref(),
unit_options,
&symbol_mappings,
)
} else {
run_interactive(args, target_path, base_path, project_config, unit_options, symbol_mappings)
}
}
fn run_oneshot(
args: &Args,
output: &Utf8PlatformPath,
target_path: Option<&Utf8PlatformPath>,
base_path: Option<&Utf8PlatformPath>,
project_config: Option<&ProjectConfig>,
unit_options: Option<ProjectOptions>,
symbol_mappings: &BTreeMap<String, String>,
) -> Result<()> {
let output_format = OutputFormat::from_option(args.format.as_deref())?;
let (diff_config, mapping_config) =
build_config_from_args(args, project_config, unit_options.as_ref(), symbol_mappings)?;
let target = target_path
.map(|p| {
obj::read::read(p.as_ref(), &diff_config, DiffSide::Target)
.with_context(|| format!("Loading {p}"))
})
.transpose()?;
let base = base_path
.map(|p| {
obj::read::read(p.as_ref(), &diff_config, DiffSide::Base)
.with_context(|| format!("Loading {p}"))
})
.transpose()?;
let result =
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
let left = target.as_ref().zip(result.left.as_ref());
let right = base.as_ref().zip(result.right.as_ref());
let diff_result = DiffResult::new(left, right, &diff_config)?;
write_output(&diff_result, Some(output), output_format)?;
Ok(())
}
fn build_config_from_args(
args: &Args,
project_config: Option<&ProjectConfig>,
unit_options: Option<&ProjectOptions>,
symbol_mappings: &BTreeMap<String, String>,
) -> Result<(DiffObjConfig, MappingConfig)> {
let mut diff_config = DiffObjConfig::default();
if let Some(options) = project_config.and_then(|config| config.options.as_ref()) {
@@ -183,17 +235,11 @@ fn build_config_from_args(
apply_project_options(&mut diff_config, options)?;
}
apply_config_args(&mut diff_config, &args.config)?;
let mut mapping_config = MappingConfig {
mappings: Default::default(),
selecting_left: args.selecting_left.clone(),
selecting_right: args.selecting_right.clone(),
};
for mapping in &args.mapping {
let (target, base) =
mapping.split_once('=').context("--mapping expects \"target=base\"")?;
mapping_config.mappings.insert(target.to_string(), base.to_string());
}
Ok((diff_config, mapping_config))
Ok((diff_config, MappingConfig {
mappings: symbol_mappings.clone(),
selecting_left: None,
selecting_right: None,
}))
}
pub struct AppState {
@@ -254,6 +300,7 @@ pub struct ObjectConfig {
pub base_path: Option<Utf8PlatformPathBuf>,
pub metadata: ProjectObjectMetadata,
pub complete: Option<bool>,
pub symbol_mappings: BTreeMap<String, String>,
}
impl ObjectConfig {
@@ -283,6 +330,7 @@ impl ObjectConfig {
base_path,
metadata: object.metadata.clone().unwrap_or_default(),
complete: object.complete(),
symbol_mappings: object.symbol_mappings.clone().unwrap_or_default(),
}
}
}
@@ -333,12 +381,17 @@ fn run_interactive(
base_path: Option<Utf8PlatformPathBuf>,
project_config: Option<ProjectConfig>,
unit_options: Option<ProjectOptions>,
symbol_mappings: BTreeMap<String, String>,
) -> Result<()> {
let Some(symbol_name) = &args.symbol else { bail!("Interactive mode requires a symbol name") };
let time_format = time::format_description::parse_borrowed::<2>("[hour]:[minute]:[second]")
.context("Failed to parse time format")?;
let (diff_obj_config, mapping_config) =
build_config_from_args(&args, project_config.as_ref(), unit_options.as_ref())?;
let (diff_obj_config, mapping_config) = build_config_from_args(
&args,
project_config.as_ref(),
unit_options.as_ref(),
&symbol_mappings,
)?;
let mut state = AppState {
jobs: Default::default(),
waker: Default::default(),
+5 -1
View File
@@ -210,7 +210,11 @@ fn report_object(
}
_ => {}
}
let mapping_config = diff::MappingConfig::default();
let mapping_config = diff::MappingConfig {
mappings: object.symbol_mappings.clone(),
selecting_left: None,
selecting_right: None,
};
let target = object
.target_path
.as_ref()
+2 -2
View File
@@ -1,3 +1,4 @@
#![allow(unused)] // TODO
use core::cmp::Ordering;
use anyhow::Result;
@@ -378,7 +379,6 @@ impl UiView for FunctionDiffUi {
}
// Reload
KeyCode::Char('r') => {
result.redraw = true;
return EventControlFlow::Reload;
}
// Scroll right
@@ -400,7 +400,6 @@ impl UiView for FunctionDiffUi {
FunctionRelocDiffs::DataValue => FunctionRelocDiffs::All,
FunctionRelocDiffs::All => FunctionRelocDiffs::None,
};
result.redraw = true;
return EventControlFlow::Reload;
}
// Toggle three-way diff
@@ -541,6 +540,7 @@ impl FunctionDiffUi {
DiffText::Opcode(mnemonic, _op) => format!("{mnemonic} "),
DiffText::Argument(arg) => arg.to_string(),
DiffText::BranchDest(addr) => format!("{addr:x}"),
DiffText::BranchArrow(_) => " ~> ".to_string(),
DiffText::Symbol(sym) => {
sym.demangled_name.as_ref().unwrap_or(&sym.name).clone()
}
+1
View File
@@ -1,3 +1,4 @@
#![allow(unused)] // TODO
use anyhow::Result;
use crossterm::event::Event;
use ratatui::Frame;
+19 -19
View File
@@ -128,17 +128,17 @@ features = ["all"]
anyhow = { version = "1.0", default-features = false }
filetime = { version = "0.2", optional = true }
flagset = { version = "0.4", default-features = false, optional = true }
itertools = { version = "0.14", default-features = false, features = ["use_alloc"] }
itertools = { version = "0.15", 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.37", default-features = false, features = ["read_core", "elf", "coff"] }
pbjson = { version = "0.8", default-features = false, optional = true }
object = { version = "0.39.1", default-features = false, features = ["read_core", "elf", "coff"] }
pbjson = { version = "0.9", 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 }
regex = { version = "1.12", default-features = false, features = [], optional = true }
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
similar = { git = "https://github.com/encounter/similar.git", branch = "no_std", default-features = false, features = ["hashbrown"], optional = true }
typed-path = { version = "0.11", default-features = false, optional = true }
similar = { version = "3.1", default-features = false, features = ["hashbrown"], optional = true }
typed-path = { version = "0.12", default-features = false, optional = true }
# config
globset = { version = "0.4", default-features = false, optional = true }
@@ -146,7 +146,7 @@ semver = { version = "1.0", default-features = false, optional = true }
serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true }
# dwarf
gimli = { git = "https://github.com/gimli-rs/gimli", rev = "7335f00e7c39fd501511584fefb0ba974117c950", default-features = false, features = ["read"], optional = true }
gimli = { version = "0.34.0", default-features = false, features = ["read"], optional = true }
typed-arena = { version = "2.0", default-features = false, optional = true }
# ppc
@@ -155,13 +155,13 @@ powerpc = { version = "0.4", optional = true }
rlwinmdec = { version = "1.1", optional = true }
# mips
rabbitizer = { version = "2.0.0-alpha.4", default-features = false, features = ["all_extensions"], optional = true }
rabbitizer = { version = "2.0.0-alpha.9", default-features = false, features = ["all_extensions"], optional = true }
# x86
iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true }
# arm
unarm = { version = "1.9", optional = true }
unarm = { version = "2.1", optional = true }
arm-attr = { version = "0.2", optional = true }
# arm64
@@ -169,17 +169,17 @@ yaxpeax-arch = { version = "0.3", default-features = false, optional = true }
yaxpeax-arm = { version = "0.3", default-features = false, optional = true }
# build
notify = { version = "8.1.0", optional = true }
notify-debouncer-full = { version = "0.5.0", optional = true }
notify = { version = "8.2.0", optional = true }
notify-debouncer-full = { version = "0.6.0", optional = true }
shell-escape = { version = "0.1", optional = true }
tempfile = { version = "3.20", optional = true }
tempfile = { version = "3.23", optional = true }
time = { version = "0.3", optional = true }
encoding_rs = { version = "0.8.35", optional = true }
# demangler
cpp_demangle = { version = "0.4", optional = true, default-features = false, features = ["alloc"] }
cpp_demangle = { version = "0.5", optional = true, default-features = false, features = ["alloc"] }
cwdemangle = { version = "1.0", optional = true }
gnuv2_demangle = { version = "0.1.0", optional = true }
gnuv2_demangle = { version = "0.4", optional = true }
msvc-demangler = { version = "0.11", optional = true }
[target.'cfg(windows)'.dependencies]
@@ -187,17 +187,17 @@ winapi = { version = "0.3", optional = true, features = ["winbase"] }
# For Linux static binaries, use rustls
[target.'cfg(target_os = "linux")'.dependencies]
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "rustls-tls"], optional = true }
self_update = { version = "0.42", default-features = false, features = ["rustls"], optional = true }
reqwest = { version = "0.13", default-features = false, features = ["blocking", "json", "multipart", "rustls"], optional = true }
self_update = { version = "1.0.0-rc.3", default-features = false, features = ["reqwest", "rustls", "github"], optional = true }
# For all other platforms, use native TLS
[target.'cfg(not(target_os = "linux"))'.dependencies]
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "default-tls"], optional = true }
self_update = { version = "0.42", optional = true }
reqwest = { version = "0.13", default-features = false, features = ["blocking", "json", "multipart", "default-tls"], optional = true }
self_update = { version = "1.0.0-rc.3", default-features = false, features = ["reqwest", "native-tls", "github"], optional = true }
[build-dependencies]
heck = { version = "0.5", optional = true }
pbjson-build = { version = "0.8", optional = true }
pbjson-build = { version = "0.9", optional = true }
prettyplease = { version = "0.2", optional = true }
proc-macro2 = { version = "1.0", optional = true }
prost-build = { version = "0.14", optional = true }
+100 -1
View File
@@ -82,6 +82,27 @@
"name": "Space between args",
"description": "Adds a space between arguments in the diff output."
},
{
"id": "showSymbolSizes",
"type": "choice",
"default": "off",
"name": "Show symbol sizes",
"description": "Shows symbol sizes in the symbol view.",
"items": [
{
"value": "off",
"name": "Off"
},
{
"value": "hex",
"name": "Hex"
},
{
"value": "decimal",
"name": "Decimal"
}
]
},
{
"id": "combineDataSections",
"type": "boolean",
@@ -96,6 +117,51 @@
"name": "Combine text sections",
"description": "Combines all text sections into one."
},
{
"id": "preferredStringEncoding",
"type": "choice",
"default": "auto",
"name": "Preferred string encoding",
"description": "Which encoding to use when diffing string literals used in functions.",
"items": [
{
"value": "auto",
"name": "Auto"
},
{
"value": "ascii",
"name": "ASCII"
},
{
"value": "utf_8",
"name": "UTF-8"
},
{
"value": "shift_jis",
"name": "Shift JIS"
},
{
"value": "windows_1252",
"name": "Windows-1252"
},
{
"value": "euc_jp",
"name": "EUC-JP"
},
{
"value": "big5",
"name": "Big5"
},
{
"value": "utf_16be",
"name": "UTF-16BE"
},
{
"value": "utf_16le",
"name": "UTF-16LE"
}
]
},
{
"id": "arm.archVersion",
"type": "choice",
@@ -107,20 +173,43 @@
"value": "auto",
"name": "Auto"
},
{
"value": "v4",
"name": "ARMv4"
},
{
"value": "v4t",
"name": "ARMv4T (GBA)"
},
{
"value": "v5t",
"name": "ARMv5T"
},
{
"value": "v5te",
"name": "ARMv5TE (DS)"
},
{
"value": "v5tej",
"name": "ARMv5TEJ"
},
{
"value": "v6",
"name": "ARMv6"
},
{
"value": "v6k",
"name": "ARMv6K (3DS)"
}
]
},
{
"id": "arm.vfpV2",
"type": "boolean",
"default": true,
"name": "VFPv2 instructions",
"description": "Adds floating-point instructions from VFPv2."
},
{
"id": "arm.unifiedSyntax",
"type": "boolean",
@@ -256,6 +345,13 @@
"name": "Register '$' prefix",
"description": "Display MIPS register names with a '$' prefix."
},
{
"id": "mips.prodgSnAsInvertedRegs",
"type": "boolean",
"default": false,
"name": "ProDG SN AS VU register order",
"description": "Changes the register order for specific R5900EE instructions to match the syntax used by the SN AS from ProDG (-snas flag)."
},
{
"id": "ppc.calculatePoolRelocations",
"type": "boolean",
@@ -294,8 +390,10 @@
"id": "general",
"name": "General",
"properties": [
"preferredStringEncoding",
"functionRelocDiffs",
"demangler",
"showSymbolSizes",
"spaceBetweenArgs",
"combineDataSections",
"combineTextSections"
@@ -306,6 +404,7 @@
"name": "ARM",
"properties": [
"arm.archVersion",
"arm.vfpV2",
"arm.unifiedSyntax",
"arm.avRegisters",
"arm.r9Usage",
@@ -317,7 +416,7 @@
{
"id": "mips",
"name": "MIPS",
"properties": ["mips.abi", "mips.instrCategory", "mips.registerPrefix"]
"properties": ["mips.abi", "mips.instrCategory", "mips.registerPrefix", "mips.prodgSnAsInvertedRegs"]
},
{
"id": "ppc",
+171
View File
@@ -0,0 +1,171 @@
syntax = "proto3";
package objdiff.diff;
// Top-level diff result containing left and right object diffs
message DiffResult {
optional DiffObject left = 1;
optional DiffObject right = 2;
}
// Diff information for a single object file
message DiffObject {
repeated DiffSection sections = 1;
repeated DiffSymbol symbols = 2;
}
// Diff information for a section
message DiffSection {
string name = 1;
DiffSectionKind kind = 2;
uint64 size = 3;
uint64 address = 4;
optional float match_percent = 5;
repeated DiffDataSegment data_diff = 6;
repeated DiffDataRelocation reloc_diff = 7;
}
enum DiffSectionKind {
SECTION_UNKNOWN = 0;
SECTION_CODE = 1;
SECTION_DATA = 2;
SECTION_BSS = 3;
SECTION_COMMON = 4;
}
// Symbol with diff information
message DiffSymbol {
string name = 1;
optional string demangled_name = 2;
uint64 address = 3;
uint64 size = 4;
DiffSymbolFlags flags = 5;
DiffSymbolKind kind = 10;
// The symbol index in the _other_ object that this symbol was diffed against
optional uint32 target_symbol = 6;
optional float match_percent = 7;
// Instruction diff rows (for code symbols)
repeated DiffInstructionRow instructions = 8;
// Data diff (for data symbols)
repeated DiffDataSegment data_diff = 9;
}
enum DiffSymbolKind {
SYMBOL_UNKNOWN = 0;
SYMBOL_FUNCTION = 1;
SYMBOL_OBJECT = 2;
SYMBOL_SECTION = 3;
}
// Symbol flags
message DiffSymbolFlags {
bool global = 1;
bool local = 2;
bool weak = 3;
bool common = 4;
bool hidden = 5;
bool ignored = 6;
bool size_inferred = 7;
}
// A single instruction diff row
message DiffInstructionRow {
DiffKind diff_kind = 1;
optional DiffInstruction instruction = 2;
repeated DiffInstructionArgDiff arg_diff = 3;
}
// Parsed instruction information
message DiffInstruction {
uint64 address = 1;
uint32 size = 2;
// Formatted instruction string
string formatted = 3;
// Formatted instruction parts
repeated DiffInstructionPart parts = 4;
// Relocation information (if present)
optional DiffRelocation relocation = 5;
// Branch destination address
optional uint64 branch_dest = 6;
// Source line number (if present)
optional uint32 line_number = 7;
}
// An instruction part
message DiffInstructionPart {
oneof part {
// Basic text (whitespace, punctuation, etc.)
string basic = 1;
// Opcode/mnemonic
DiffOpcode opcode = 2;
// Argument
DiffInstructionArg arg = 3;
// Separator between arguments (comma)
bool separator = 4;
}
}
message DiffOpcode {
// Mnemonic string
string mnemonic = 1;
// Base opcode ID
uint32 opcode = 2;
}
// An instruction argument
message DiffInstructionArg {
oneof arg {
// Signed immediate value
sint64 signed = 1;
// Unsigned immediate value
uint64 unsigned = 2;
// Opaque string value (register names, etc.)
string opaque = 3;
// Relocation
bool reloc = 4;
// Branch destination address
uint64 branch_dest = 5;
}
}
// Relocation information
message DiffRelocation {
uint32 type = 1;
string type_name = 2;
uint32 target_symbol = 3;
int64 addend = 4;
}
// Argument diff information
message DiffInstructionArgDiff {
// If set, this argument differs from the other side.
// The value is a rotating index for coloring.
optional uint32 diff_index = 1;
}
// Diff kind for instructions and data
enum DiffKind {
DIFF_NONE = 0;
DIFF_REPLACE = 1;
DIFF_DELETE = 2;
DIFF_INSERT = 3;
DIFF_OP_MISMATCH = 4;
DIFF_ARG_MISMATCH = 5;
}
// Data diff segment
message DiffDataSegment {
DiffKind kind = 1;
bytes data = 2;
// Size may be larger than data length for BSS
uint64 size = 3;
}
// Data relocation diff
message DiffDataRelocation {
DiffRelocation relocation = 1;
DiffKind kind = 2;
// Address range this relocation covers
uint64 start = 3;
uint64 end = 4;
}
Binary file not shown.
File diff suppressed because it is too large Load Diff
+13 -9
View File
@@ -77,7 +77,7 @@ impl ArchMips {
.and_then(|section| section.data().ok())
.and_then(|data| data.get(0x14..0x18))
.and_then(|s| s.try_into().ok())
.map(|bytes| endianness.read_i32_bytes(bytes))
.map(|bytes| endianness.read_i32(bytes))
.unwrap_or(0);
// Parse all relocations to pair R_MIPS_HI16 and R_MIPS_LO16. Since the instructions only
@@ -98,13 +98,13 @@ impl ArchMips {
match reloc.flags() {
object::RelocationFlags::Elf { r_type: elf::R_MIPS_HI16 } => {
let code = data[addr as usize..addr as usize + 4].try_into()?;
let addend = ((endianness.read_u32_bytes(code) & 0x0000FFFF) << 16) as i32;
let addend = ((endianness.read_u32(code) & 0x0000FFFF) << 16) as i32;
last_hi = Some(addr);
last_hi_addend = addend;
}
object::RelocationFlags::Elf { r_type: elf::R_MIPS_LO16 } => {
let code = data[addr as usize..addr as usize + 4].try_into()?;
let addend = (endianness.read_u32_bytes(code) & 0x0000FFFF) as i16 as i32;
let addend = (endianness.read_u32(code) & 0x0000FFFF) as i16 as i32;
let full_addend = (last_hi_addend + addend) as i64;
if let Some(hi_addr) = last_hi.take() {
addends.insert(hi_addr, full_addend);
@@ -188,6 +188,7 @@ impl ArchMips {
rabbitizer::InstructionDisplayFlags::default()
.with_unknown_instr_comment(false)
.with_use_dollar(diff_config.mips_register_prefix)
.with_r5900ee_prodg_sn_as_inverted_regs(diff_config.mips_prodg_sn_as_inverted_regs)
}
fn parse_ins_ref(
@@ -197,7 +198,7 @@ impl ArchMips {
diff_config: &DiffObjConfig,
) -> Result<rabbitizer::Instruction> {
Ok(rabbitizer::Instruction::new(
self.endianness.read_u32_bytes(code.try_into()?),
self.endianness.read_u32(code.try_into()?),
Vram::new(ins_ref.address as u32),
self.instruction_flags(diff_config),
))
@@ -216,8 +217,8 @@ impl Arch for ArchMips {
let instruction_flags = self.instruction_flags(diff_config);
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()?);
for chunk in code.as_chunks::<4>().0 {
let code = self.endianness.read_u32(*chunk);
let instruction =
rabbitizer::Instruction::new(code, Vram::new(cur_addr), instruction_flags);
let opcode = instruction.opcode() as u16;
@@ -237,7 +238,8 @@ impl Arch for ArchMips {
let instruction = self.parse_ins_ref(resolved.ins_ref, resolved.code, diff_config)?;
let display_flags = self.instruction_display_flags(diff_config);
let opcode = instruction.opcode();
cb(InstructionPart::opcode(opcode.name(), opcode as u16))?;
let mnemonic = instruction.mnemonic_display(&display_flags).to_string();
cb(InstructionPart::opcode(mnemonic, opcode as u16))?;
push_args(&instruction, resolved.relocation, &display_flags, cb)?;
Ok(())
}
@@ -269,7 +271,7 @@ impl Arch for ArchMips {
let data = section.data()?;
let code = self
.endianness
.read_u32_bytes(data[address as usize..address as usize + 4].try_into()?);
.read_u32(data[address as usize..address as usize + 4].try_into()?);
let addend = match r_type {
elf::R_MIPS_32 => code as i64,
elf::R_MIPS_26 => ((code & 0x03FFFFFF) << 2) as i64,
@@ -293,6 +295,7 @@ impl Arch for ArchMips {
}
elf::R_MIPS_PC16 => 0, // PC-relative relocation
R_MIPS15_S3 => ((code & 0x001FFFC0) >> 3) as i64,
elf::R_MIPS_GPREL32 => (code as i32 as i64) + self.ri_gp_value as i64,
flags => bail!("Unsupported MIPS implicit relocation {flags:?}"),
};
Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Keep, addend }))
@@ -318,6 +321,7 @@ impl Arch for ArchMips {
elf::R_MIPS_GOT16 => Some("R_MIPS_GOT16"),
elf::R_MIPS_PC16 => Some("R_MIPS_PC16"),
elf::R_MIPS_CALL16 => Some("R_MIPS_CALL16"),
elf::R_MIPS_GPREL32 => Some("R_MIPS_GPREL32"),
R_MIPS15_S3 => Some("R_MIPS15_S3"),
_ => None,
},
@@ -364,7 +368,7 @@ impl Arch for ArchMips {
&& new_address >= symbol.address + 4
&& let Some(data) = section.data_range(new_address - 4, 4)
&& let instruction = rabbitizer::Instruction::new(
self.endianness.read_u32_bytes(data.try_into().unwrap()),
self.endianness.read_u32(data.try_into().unwrap()),
Vram::new((new_address - 4) as u32),
self.default_instruction_flags(),
)
+140 -41
View File
@@ -7,17 +7,15 @@ use alloc::{
};
use core::{
any::Any,
ffi::CStr,
fmt::{self, Debug},
};
use anyhow::{Result, bail};
use encoding_rs::SHIFT_JIS;
use object::Endian as _;
use crate::{
diff::{
DiffObjConfig, DiffSide,
ConfigEnum, DiffObjConfig, DiffSide, PreferredStringEncoding,
display::{ContextItem, HoverItem, InstructionPart},
},
obj::{
@@ -44,6 +42,43 @@ pub mod x86;
pub const OPCODE_INVALID: u16 = u16::MAX;
pub const OPCODE_DATA: u16 = u16::MAX - 1;
const SUPPORTED_ENCODINGS_WITH_NULL_TERM: [(&encoding_rs::Encoding, &str); 5] = [
(encoding_rs::UTF_8, "UTF-8"),
(encoding_rs::SHIFT_JIS, "Shift JIS"),
(encoding_rs::WINDOWS_1252, "Windows-1252"),
(encoding_rs::EUC_JP, "EUC-JP"),
(encoding_rs::BIG5, "Big5"),
];
const SUPPORTED_ENCODINGS_NO_NULL_TERM: [(&encoding_rs::Encoding, &str); 2] =
[(encoding_rs::UTF_16BE, "UTF-16BE"), (encoding_rs::UTF_16LE, "UTF-16LE")];
#[derive(Debug, Clone, Default, PartialEq)]
pub struct LiteralInfo {
pub literal: String,
pub label_override: Option<String>,
pub copy_string: Option<String>,
pub hidden: bool, // Only used when the user hasn't set a preferred string encoding
pub is_string: bool,
}
impl LiteralInfo {
pub fn hidden(&self, diff_config: Option<&DiffObjConfig>) -> bool {
let Some(diff_config) = diff_config else {
return self.hidden;
};
if !self.is_string {
return self.hidden;
}
if diff_config.preferred_string_encoding == PreferredStringEncoding::Auto {
return self.hidden;
}
let Some(ref label) = self.label_override else {
return self.hidden;
};
*label != diff_config.preferred_string_encoding.name()
}
}
/// Represents the type of data associated with an instruction
#[derive(PartialEq)]
pub enum DataType {
@@ -75,18 +110,14 @@ 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, label_override) in self.display_literals(endian, bytes) {
let label = label_override.unwrap_or_else(|| self.to_string());
strs.push(format!("{label}: {literal}"))
for lit_info in self.display_literals(endian, bytes) {
let label = lit_info.label_override.unwrap_or_else(|| self.to_string());
strs.push(format!("{}: {:?}", label, lit_info.literal))
}
strs
}
pub fn display_literals(
&self,
endian: object::Endianness,
bytes: &[u8],
) -> Vec<(String, Option<String>)> {
pub fn display_literals(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<LiteralInfo> {
let mut strs = Vec::new();
if self.required_len().is_some_and(|l| bytes.len() < l) {
log::warn!(
@@ -110,73 +141,122 @@ impl DataType {
match self {
DataType::Int8 => {
let i = i8::from_ne_bytes(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None));
strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None));
strs.push(LiteralInfo {
literal: format!("{:#x}", ReallySigned(i)),
..Default::default()
});
}
}
DataType::Int16 => {
let i = endian.read_i16_bytes(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None));
let i = endian.read_i16(bytes.try_into().unwrap());
strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None));
strs.push(LiteralInfo {
literal: format!("{:#x}", ReallySigned(i)),
..Default::default()
});
}
}
DataType::Int32 => {
let i = endian.read_i32_bytes(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None));
let i = endian.read_i32(bytes.try_into().unwrap());
strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None));
strs.push(LiteralInfo {
literal: format!("{:#x}", ReallySigned(i)),
..Default::default()
});
}
}
DataType::Int64 => {
let i = endian.read_i64_bytes(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None));
let i = endian.read_i64(bytes.try_into().unwrap());
strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None));
strs.push(LiteralInfo {
literal: format!("{:#x}", ReallySigned(i)),
..Default::default()
});
}
}
DataType::Float => {
let bytes: [u8; 4] = bytes.try_into().unwrap();
strs.push((
format!("{:?}f", match endian {
strs.push(LiteralInfo {
literal: format!("{:?}f", match endian {
object::Endianness::Little => f32::from_le_bytes(bytes),
object::Endianness::Big => f32::from_be_bytes(bytes),
}),
None,
));
..Default::default()
});
}
DataType::Double => {
let bytes: [u8; 8] = bytes.try_into().unwrap();
strs.push((
format!("{:?}", match endian {
strs.push(LiteralInfo {
literal: format!("{:?}", match endian {
object::Endianness::Little => f64::from_le_bytes(bytes),
object::Endianness::Big => f64::from_be_bytes(bytes),
}),
None,
));
..Default::default()
});
}
DataType::Bytes => {
strs.push((format!("{bytes:#?}"), None));
strs.push(LiteralInfo { literal: format!("{bytes:#?}"), ..Default::default() });
}
DataType::String => {
if let Ok(cstr) = CStr::from_bytes_until_nul(bytes) {
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())));
let str_bytes = &bytes[..nul_idx];
// Special case to display (ASCII) as the label for ASCII-only strings.
let (cow, _, had_errors) = encoding_rs::UTF_8.decode(str_bytes);
if !had_errors && cow.is_ascii() {
let string = format!("{cow}");
let copy_string = escape_special_ascii_characters(&string);
strs.push(LiteralInfo {
literal: string,
label_override: Some("ASCII".into()),
copy_string: Some(copy_string),
hidden: false,
is_string: true,
});
}
for (encoding, encoding_name) in SUPPORTED_ENCODINGS_WITH_NULL_TERM {
let (cow, _, had_errors) = encoding.decode(str_bytes);
if !had_errors {
let string = format!("{cow}");
let copy_string = escape_special_ascii_characters(&string);
// Avoid showing ASCII-only strings more than once if the encoding is ASCII-compatible.
let hidden = encoding.is_ascii_compatible() && cow.is_ascii();
strs.push(LiteralInfo {
literal: string,
label_override: Some(encoding_name.into()),
copy_string: Some(copy_string),
hidden,
is_string: true,
});
}
}
}
for (encoding, encoding_name) in SUPPORTED_ENCODINGS_NO_NULL_TERM {
let (cow, _, had_errors) = encoding.decode(bytes);
if had_errors {
continue;
}
let trimmed = cow.trim_end_matches('\0');
if !trimmed.is_empty() {
let copy_string = escape_special_ascii_characters(trimmed);
strs.push(LiteralInfo {
literal: trimmed.to_string(),
label_override: Some(encoding_name.into()),
copy_string: Some(copy_string),
hidden: false,
is_string: true,
});
}
}
}
}
@@ -311,7 +391,8 @@ impl dyn Arch {
pub trait Arch: Any + Debug + Send + Sync {
/// Finishes arch-specific initialization that must be done after sections have been combined.
fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol]) {}
fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol], _symbol_indices: &[usize]) {
}
/// Generate a list of instructions references (offset, size, opcode) from the given code.
///
@@ -489,3 +570,21 @@ pub struct RelocationOverride {
pub target: RelocationOverrideTarget,
pub addend: i64,
}
/// Escape ASCII characters such as \n or \t, but not Unicode characters such as \u{3000}.
/// Suitable for copying to clipboard.
fn escape_special_ascii_characters(value: &str) -> String {
let mut escaped = String::new();
escaped.push('"');
for c in value.chars() {
if c.is_ascii() {
for e in c.escape_default() {
escaped.push(e);
}
} else {
escaped.push(c);
}
}
escaped.push('"');
escaped
}
@@ -513,6 +513,8 @@ pub fn ppc_data_flow_analysis(
)
}
// Note: This function only supports MWCC ASCII strings.
// Other encodings and other compilers are currently not supported.
fn get_string_data(obj: &Object, symbol_index: usize, offset: Simm) -> Option<&str> {
if let Some(sym) = obj.symbols.get(symbol_index)
&& sym.name.starts_with("@stringBase")
+34 -7
View File
@@ -238,9 +238,9 @@ impl Arch for ArchPpc {
addend: 0,
})),
|(_, reloc)| match reloc.target() {
object::RelocationTarget::Symbol(index) => Ok(Some(RelocationOverride {
object::RelocationTarget::Symbol(_) => Ok(Some(RelocationOverride {
target: RelocationOverrideTarget::Keep,
addend: index.0 as u16 as i16 as i64,
addend: 0,
})),
target => Err(anyhow!("Unsupported IMAGE_REL_PPC_PAIR target {target:?}")),
},
@@ -342,7 +342,10 @@ impl Arch for ArchPpc {
elf::R_PPC_UADDR32 => 4,
_ => 1,
},
_ => 1,
RelocationFlags::Coff(r_type) => match r_type {
pe::IMAGE_REL_PPC_ADDR32 => 4,
_ => 1,
},
}
}
@@ -355,8 +358,13 @@ impl Arch for ArchPpc {
}
fn guess_data_type(&self, resolved: ResolvedInstructionRef, bytes: &[u8]) -> Option<DataType> {
if resolved.relocation.is_some_and(|r| r.symbol.name.starts_with("@stringBase")) {
// Pooled string.
if resolved.relocation.is_some_and(|r| {
r.symbol.name.starts_with("@stringBase")
|| r.symbol.name.starts_with("@wstringBase")
|| r.symbol.name.starts_with("$SG")
|| r.symbol.demangled_name == Some("`string'".to_string())
}) {
// Compiler-generated symbol name for a string or a pool of strings.
return Some(DataType::String);
}
let opcode = powerpc::Opcode::from(resolved.ins_ref.opcode);
@@ -440,9 +448,13 @@ impl Arch for ArchPpc {
let simplified = ins.simplified().to_string();
let show_orig = orig != simplified;
let mut out = Vec::with_capacity(2);
out.push(ContextItem::Copy { value: simplified, label: None });
out.push(ContextItem::Copy { value: simplified, label: None, copy_string: None });
if show_orig {
out.push(ContextItem::Copy { value: orig, label: Some("original".to_string()) });
out.push(ContextItem::Copy {
value: orig,
label: Some("original".to_string()),
copy_string: None,
});
}
out
}
@@ -463,12 +475,27 @@ impl Arch for ArchPpc {
}
Ok(next_address.saturating_sub(symbol.address))
}
fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol], symbol_indices: &[usize]) {
// Change the indices used as keys from the original symbol indices to the new symbol array indices
self.extab = Self::convert_extab_map_indices(self, symbol_indices);
}
}
impl ArchPpc {
pub fn extab_for_symbol(&self, symbol_index: usize) -> Option<&ExceptionInfo> {
self.extab.as_ref()?.get(&symbol_index)
}
pub fn convert_extab_map_indices(
&self,
symbol_indices: &[usize],
) -> Option<BTreeMap<usize, ExceptionInfo>> {
let new_map: BTreeMap<usize, ExceptionInfo> =
self.extab.as_ref()?.iter().map(|e| (symbol_indices[*e.0 + 1], e.1.clone())).collect();
Some(new_map)
}
}
fn zero_reloc(code: u32, reloc: &Relocation) -> u32 {
+4 -4
View File
@@ -35,8 +35,8 @@ impl Arch for ArchSuperH {
let mut ops = Vec::<InstructionRef>::with_capacity(code.len() / 2);
let mut offset = address;
for chunk in code.chunks_exact(2) {
let opcode = u16::from_be_bytes(chunk.try_into().unwrap());
for chunk in code.as_chunks::<2>().0 {
let opcode = u16::from_be_bytes(*chunk);
let mut parts: Vec<InstructionPart> = vec![];
let resolved: ResolvedInstructionRef = Default::default();
let mut branch_dest: Option<u64> = None;
@@ -80,8 +80,8 @@ impl Arch for ArchSuperH {
let mut data_offsets = BTreeMap::<u64, DataInfo>::new();
let mut pos: u64 = 0;
for chunk in symbol_data.chunks_exact(2) {
let opcode = u16::from_be_bytes(chunk.try_into().unwrap());
for chunk in symbol_data.as_chunks::<2>().0 {
let opcode = u16::from_be_bytes(*chunk);
// mov.w
if (opcode & 0xf000) == 0x9000 {
let target = (opcode as u64 & 0xff) * 2 + 4 + pos;
+59 -10
View File
@@ -68,14 +68,26 @@ impl ArchX86 {
_ => None,
},
RelocationFlags::Elf(typ) => match typ {
elf::R_386_32 | elf::R_386_PC32 => Some(4),
elf::R_386_32
| elf::R_386_PC32
| elf::R_386_GOT32
| elf::R_386_PLT32
| elf::R_386_GOTOFF
| elf::R_386_GOTPC => Some(4),
elf::R_386_16 => Some(2),
_ => None,
},
},
Architecture::X86_64 => match flags {
RelocationFlags::Coff(typ) => match typ {
pe::IMAGE_REL_AMD64_ADDR32NB | pe::IMAGE_REL_AMD64_REL32 => Some(4),
pe::IMAGE_REL_AMD64_ADDR32NB
| pe::IMAGE_REL_AMD64_REL32
| pe::IMAGE_REL_AMD64_REL32_1
| pe::IMAGE_REL_AMD64_REL32_2
| pe::IMAGE_REL_AMD64_REL32_3
| pe::IMAGE_REL_AMD64_REL32_4
| pe::IMAGE_REL_AMD64_REL32_5
| pe::IMAGE_REL_AMD64_SECREL => Some(4),
pe::IMAGE_REL_AMD64_ADDR64 => Some(8),
_ => None,
},
@@ -176,6 +188,12 @@ impl Arch for ArchX86 {
branch_dest,
});
}
// Strip trailing int3 instructions (alignment padding) for x64
if matches!(self.arch, Architecture::X86_64) {
while out.last().is_some_and(|i| i.opcode == iced_x86::Mnemonic::Int3 as u16) {
out.pop();
}
}
Ok(out)
}
@@ -188,8 +206,8 @@ impl Arch for ArchX86 {
if resolved.ins_ref.opcode == OPCODE_DATA {
let (mnemonic, imm) = match resolved.ins_ref.size {
1 => (".byte", resolved.code[0] as u64),
2 => (".word", self.endianness.read_u16_bytes(resolved.code.try_into()?) as u64),
4 => (".dword", self.endianness.read_u32_bytes(resolved.code.try_into()?) as u64),
2 => (".word", self.endianness.read_u16(resolved.code.try_into()?) as u64),
4 => (".dword", self.endianness.read_u32(resolved.code.try_into()?) as u64),
_ => bail!("Unsupported x86 inline data size {}", resolved.ins_ref.size),
};
cb(InstructionPart::opcode(mnemonic, OPCODE_DATA))?;
@@ -272,27 +290,43 @@ impl Arch for ArchX86 {
object::RelocationFlags::Coff {
typ: pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32,
}
| object::RelocationFlags::Elf { r_type: elf::R_386_32 | elf::R_386_PC32 } => {
| object::RelocationFlags::Elf {
r_type:
elf::R_386_32
| elf::R_386_PC32
| elf::R_386_GOT32
| elf::R_386_PLT32
| elf::R_386_GOTOFF
| elf::R_386_GOTPC,
} => {
let data =
section.data()?[address as usize..address as usize + 4].try_into()?;
self.endianness.read_i32_bytes(data) as i64
self.endianness.read_i32(data) as i64
}
flags => bail!("Unsupported x86 implicit relocation {flags:?}"),
},
Architecture::X86_64 => match relocation.flags() {
object::RelocationFlags::Coff {
typ: pe::IMAGE_REL_AMD64_ADDR32NB | pe::IMAGE_REL_AMD64_REL32,
typ:
pe::IMAGE_REL_AMD64_ADDR32NB
| pe::IMAGE_REL_AMD64_REL32
| pe::IMAGE_REL_AMD64_REL32_1
| pe::IMAGE_REL_AMD64_REL32_2
| pe::IMAGE_REL_AMD64_REL32_3
| pe::IMAGE_REL_AMD64_REL32_4
| pe::IMAGE_REL_AMD64_REL32_5
| pe::IMAGE_REL_AMD64_SECREL,
}
| object::RelocationFlags::Elf { r_type: elf::R_X86_64_32 | elf::R_X86_64_PC32 } => {
let data =
section.data()?[address as usize..address as usize + 4].try_into()?;
self.endianness.read_i32_bytes(data) as i64
self.endianness.read_i32(data) as i64
}
object::RelocationFlags::Coff { typ: pe::IMAGE_REL_AMD64_ADDR64 }
| object::RelocationFlags::Elf { r_type: elf::R_X86_64_64 } => {
let data =
section.data()?[address as usize..address as usize + 8].try_into()?;
self.endianness.read_i64_bytes(data)
self.endianness.read_i64(data)
}
flags => bail!("Unsupported x86-64 implicit relocation {flags:?}"),
},
@@ -308,13 +342,28 @@ impl Arch for ArchX86 {
pe::IMAGE_REL_I386_REL32 => Some("IMAGE_REL_I386_REL32"),
_ => None,
},
_ => None,
RelocationFlags::Elf(typ) => match typ {
elf::R_386_32 => Some("R_386_32"),
elf::R_386_PC32 => Some("R_386_PC32"),
elf::R_386_GOT32 => Some("R_386_GOT32"),
elf::R_386_PLT32 => Some("R_386_PLT32"),
elf::R_386_GOTOFF => Some("R_386_GOTOFF"),
elf::R_386_GOTPC => Some("R_386_GOTPC"),
elf::R_386_16 => Some("R_386_16"),
_ => None,
},
},
Architecture::X86_64 => match flags {
RelocationFlags::Coff(typ) => match typ {
pe::IMAGE_REL_AMD64_ADDR64 => Some("IMAGE_REL_AMD64_ADDR64"),
pe::IMAGE_REL_AMD64_ADDR32NB => Some("IMAGE_REL_AMD64_ADDR32NB"),
pe::IMAGE_REL_AMD64_REL32 => Some("IMAGE_REL_AMD64_REL32"),
pe::IMAGE_REL_AMD64_REL32_1 => Some("IMAGE_REL_AMD64_REL32_1"),
pe::IMAGE_REL_AMD64_REL32_2 => Some("IMAGE_REL_AMD64_REL32_2"),
pe::IMAGE_REL_AMD64_REL32_3 => Some("IMAGE_REL_AMD64_REL32_3"),
pe::IMAGE_REL_AMD64_REL32_4 => Some("IMAGE_REL_AMD64_REL32_4"),
pe::IMAGE_REL_AMD64_REL32_5 => Some("IMAGE_REL_AMD64_REL32_5"),
pe::IMAGE_REL_AMD64_SECREL => Some("IMAGE_REL_AMD64_SECREL"),
_ => None,
},
_ => None,

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