Compare commits

...

47 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
87 changed files with 10773 additions and 7868 deletions
+18 -19
View File
@@ -28,7 +28,7 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get -y install libgtk-3-dev sudo apt-get -y install libgtk-3-dev
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup Rust toolchain - name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
with: with:
@@ -47,7 +47,7 @@ jobs:
RUSTFLAGS: -D warnings RUSTFLAGS: -D warnings
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup Rust toolchain - name: Setup Rust toolchain
# We use nightly options in rustfmt.toml # We use nightly options in rustfmt.toml
uses: dtolnay/rust-toolchain@nightly uses: dtolnay/rust-toolchain@nightly
@@ -62,12 +62,12 @@ jobs:
strategy: strategy:
matrix: matrix:
checks: checks:
- advisories # - advisories
- bans licenses sources - bans licenses sources
# Prevent new advisories from failing CI # Prevent new advisories from failing CI
continue-on-error: ${{ matrix.checks == 'advisories' }} continue-on-error: ${{ matrix.checks == 'advisories' }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: EmbarkStudios/cargo-deny-action@v2 - uses: EmbarkStudios/cargo-deny-action@v2
with: with:
command: check ${{ matrix.checks }} command: check ${{ matrix.checks }}
@@ -86,7 +86,7 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get -y install libgtk-3-dev sudo apt-get -y install libgtk-3-dev
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup Rust toolchain - name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: Cache Rust workspace - name: Cache Rust workspace
@@ -145,10 +145,10 @@ jobs:
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Install uv - name: Install uv
if: matrix.build == 'zigbuild' if: matrix.build == 'zigbuild'
uses: astral-sh/setup-uv@v6 uses: astral-sh/setup-uv@v7
- name: Install cargo-zigbuild - name: Install cargo-zigbuild
if: matrix.build == 'zigbuild' if: matrix.build == 'zigbuild'
run: | run: |
@@ -167,7 +167,7 @@ jobs:
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }} cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }} --bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }} name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
path: | path: |
@@ -213,10 +213,10 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get -y install ${{ matrix.packages }} sudo apt-get -y install ${{ matrix.packages }}
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Install uv - name: Install uv
if: matrix.build == 'zigbuild' if: matrix.build == 'zigbuild'
uses: astral-sh/setup-uv@v6 uses: astral-sh/setup-uv@v7
- name: Install cargo-zigbuild - name: Install cargo-zigbuild
if: matrix.build == 'zigbuild' if: matrix.build == 'zigbuild'
run: | run: |
@@ -235,7 +235,7 @@ jobs:
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }} cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }} --bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }} name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
path: | path: |
@@ -248,7 +248,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup Rust toolchain - name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@nightly uses: dtolnay/rust-toolchain@nightly
with: with:
@@ -256,7 +256,7 @@ jobs:
- name: Cache Rust workspace - name: Cache Rust workspace
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v6.4.0
with: with:
node-version: lts/* node-version: lts/*
- name: Install dependencies - name: Install dependencies
@@ -264,7 +264,7 @@ jobs:
- name: Build - name: Build
run: npm -C objdiff-wasm run build run: npm -C objdiff-wasm run build
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: wasm name: wasm
path: objdiff-wasm/dist/ path: objdiff-wasm/dist/
@@ -277,7 +277,7 @@ jobs:
needs: [ build-cli, build-gui, build-wasm ] needs: [ build-cli, build-gui, build-wasm ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Check git tag against package versions - name: Check git tag against package versions
shell: bash shell: bash
run: | run: |
@@ -340,12 +340,11 @@ jobs:
release-cargo: release-cargo:
name: Release (Cargo) name: Release (Cargo)
if: 'false' # TODO re-enable when all dependencies are published
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [ check-version ] needs: [ check-version ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup Rust toolchain - name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: Publish - name: Publish
@@ -360,9 +359,9 @@ jobs:
needs: [ check-version ] needs: [ check-version ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v6.4.0
with: with:
node-version: lts/* node-version: lts/*
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
Generated
+1735 -1003
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -14,7 +14,7 @@ default-members = [
resolver = "3" resolver = "3"
[workspace.package] [workspace.package]
version = "3.5.0" version = "3.7.3"
authors = ["Luke Street <luke@street.dev>"] authors = ["Luke Street <luke@street.dev>"]
edition = "2024" edition = "2024"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
+6 -3
View File
@@ -75,8 +75,13 @@ ignore = [
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, #{ 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-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-0052", reason = "Unmaintained async-std crate is an indirect dependency" },
{ id = "RUSTSEC-2025-0119", reason = "Unmaintained number_prefix crate is an indirect dependency" },
{ id = "RUSTSEC-2025-0134", reason = "Unmaintained rustls-pemfile 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 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. # If this is false, then it uses a built-in git library.
@@ -244,8 +249,6 @@ allow-git = []
[sources.allow-org] [sources.allow-org]
# github.com organizations to allow git sources for # github.com organizations to allow git sources for
github = [ github = [
"encounter",
"gimli-rs", # gimli
] ]
# gitlab.com organizations to allow git sources for # gitlab.com organizations to allow git sources for
gitlab = [] gitlab = []
+1 -1
View File
@@ -20,7 +20,7 @@ enable-ansi-support = "0.3"
memmap2 = "0.9" memmap2 = "0.9"
objdiff-core = { path = "../objdiff-core", features = ["all"] } objdiff-core = { path = "../objdiff-core", features = ["all"] }
prost = "0.14" prost = "0.14"
ratatui = "0.29" ratatui = "0.30"
rayon = "1.11" rayon = "1.11"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
+82 -10
View File
@@ -1,4 +1,6 @@
#![allow(unused)] // TODO
use std::{ use std::{
collections::BTreeMap,
io::stdout, io::stdout,
mem, mem,
sync::{ sync::{
@@ -19,6 +21,7 @@ use crossterm::{
}, },
}; };
use objdiff_core::{ use objdiff_core::{
bindings::diff::DiffResult,
build::{ build::{
BuildConfig, BuildStatus, BuildConfig, BuildStatus,
watcher::{Watcher, create_watcher}, watcher::{Watcher, create_watcher},
@@ -28,7 +31,7 @@ use objdiff_core::{
build_globset, build_globset,
path::{check_path_buf, platform_path, platform_path_serde_option}, path::{check_path_buf, platform_path, platform_path_serde_option},
}, },
diff::{DiffObjConfig, MappingConfig, ObjectDiff}, diff::{self, DiffObjConfig, DiffSide, MappingConfig, ObjectDiff},
jobs::{ jobs::{
Job, JobQueue, JobResult, Job, JobQueue, JobResult,
objdiff::{ObjDiffConfig, start_build}, objdiff::{ObjDiffConfig, start_build},
@@ -40,7 +43,10 @@ use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
use crate::{ use crate::{
cmd::apply_config_args, 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}, views::{EventControlFlow, EventResult, UiView, function_diff::FunctionDiffUi},
}; };
@@ -60,6 +66,12 @@ pub struct Args {
#[argp(option, short = 'u')] #[argp(option, short = 'u')]
/// Unit name within project /// Unit name within project
unit: Option<String>, 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)] #[argp(positional)]
/// Function symbol to diff /// Function symbol to diff
symbol: Option<String>, symbol: Option<String>,
@@ -69,11 +81,13 @@ pub struct Args {
} }
pub fn run(args: Args) -> Result<()> { 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) { match (&args.target, &args.base, &args.project, &args.unit) {
(Some(_), Some(_), None, None) (Some(_), Some(_), None, None)
| (Some(_), None, None, None) | (Some(_), None, None, None)
| (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None, None), | (None, Some(_), None, None) => {
(args.target.clone(), args.base.clone(), None, None, BTreeMap::new())
}
(None, None, p, u) => { (None, None, p, u) => {
let project = match p { let project = match p {
Some(project) => project.clone(), Some(project) => project.clone(),
@@ -85,7 +99,7 @@ pub fn run(args: Args) -> Result<()> {
let Some((project_config, project_config_info)) = let Some((project_config, project_config_info)) =
objdiff_core::config::try_project_config(project.as_ref()) objdiff_core::config::try_project_config(project.as_ref())
else { else {
bail!("Project config not found in {}", &project) bail!("Project config not found in {}", project)
}; };
let project_config = project_config.with_context(|| { let project_config = project_config.with_context(|| {
format!("Reading project config {}", project_config_info.path.display()) format!("Reading project config {}", project_config_info.path.display())
@@ -153,18 +167,65 @@ pub fn run(args: Args) -> Result<()> {
let unit_options = units.get(unit_idx).and_then(|u| u.options().cloned()); let unit_options = units.get(unit_idx).and_then(|u| u.options().cloned());
let target_path = object.target_path.clone(); let target_path = object.target_path.clone();
let base_path = object.base_path.clone(); let base_path = object.base_path.clone();
(target_path, base_path, Some(project_config), 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"), _ => 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( fn build_config_from_args(
args: &Args, args: &Args,
project_config: Option<&ProjectConfig>, project_config: Option<&ProjectConfig>,
unit_options: Option<&ProjectOptions>, unit_options: Option<&ProjectOptions>,
symbol_mappings: &BTreeMap<String, String>,
) -> Result<(DiffObjConfig, MappingConfig)> { ) -> Result<(DiffObjConfig, MappingConfig)> {
let mut diff_config = DiffObjConfig::default(); let mut diff_config = DiffObjConfig::default();
if let Some(options) = project_config.and_then(|config| config.options.as_ref()) { if let Some(options) = project_config.and_then(|config| config.options.as_ref()) {
@@ -174,7 +235,11 @@ fn build_config_from_args(
apply_project_options(&mut diff_config, options)?; apply_project_options(&mut diff_config, options)?;
} }
apply_config_args(&mut diff_config, &args.config)?; apply_config_args(&mut diff_config, &args.config)?;
Ok((diff_config, MappingConfig::default())) Ok((diff_config, MappingConfig {
mappings: symbol_mappings.clone(),
selecting_left: None,
selecting_right: None,
}))
} }
pub struct AppState { pub struct AppState {
@@ -235,6 +300,7 @@ pub struct ObjectConfig {
pub base_path: Option<Utf8PlatformPathBuf>, pub base_path: Option<Utf8PlatformPathBuf>,
pub metadata: ProjectObjectMetadata, pub metadata: ProjectObjectMetadata,
pub complete: Option<bool>, pub complete: Option<bool>,
pub symbol_mappings: BTreeMap<String, String>,
} }
impl ObjectConfig { impl ObjectConfig {
@@ -264,6 +330,7 @@ impl ObjectConfig {
base_path, base_path,
metadata: object.metadata.clone().unwrap_or_default(), metadata: object.metadata.clone().unwrap_or_default(),
complete: object.complete(), complete: object.complete(),
symbol_mappings: object.symbol_mappings.clone().unwrap_or_default(),
} }
} }
} }
@@ -314,12 +381,17 @@ fn run_interactive(
base_path: Option<Utf8PlatformPathBuf>, base_path: Option<Utf8PlatformPathBuf>,
project_config: Option<ProjectConfig>, project_config: Option<ProjectConfig>,
unit_options: Option<ProjectOptions>, unit_options: Option<ProjectOptions>,
symbol_mappings: BTreeMap<String, String>,
) -> Result<()> { ) -> Result<()> {
let Some(symbol_name) = &args.symbol else { bail!("Interactive mode requires a symbol name") }; let Some(symbol_name) = &args.symbol else { bail!("Interactive mode requires a symbol name") };
let time_format = time::format_description::parse_borrowed::<2>("[hour]:[minute]:[second]") let time_format = time::format_description::parse_borrowed::<2>("[hour]:[minute]:[second]")
.context("Failed to parse time format")?; .context("Failed to parse time format")?;
let (diff_obj_config, mapping_config) = let (diff_obj_config, mapping_config) = build_config_from_args(
build_config_from_args(&args, project_config.as_ref(), unit_options.as_ref())?; &args,
project_config.as_ref(),
unit_options.as_ref(),
&symbol_mappings,
)?;
let mut state = AppState { let mut state = AppState {
jobs: Default::default(), jobs: Default::default(),
waker: 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 let target = object
.target_path .target_path
.as_ref() .as_ref()
+1
View File
@@ -1,3 +1,4 @@
#![allow(unused)] // TODO
use core::cmp::Ordering; use core::cmp::Ordering;
use anyhow::Result; use anyhow::Result;
+1
View File
@@ -1,3 +1,4 @@
#![allow(unused)] // TODO
use anyhow::Result; use anyhow::Result;
use crossterm::event::Event; use crossterm::event::Event;
use ratatui::Frame; use ratatui::Frame;
+11 -11
View File
@@ -128,16 +128,16 @@ features = ["all"]
anyhow = { version = "1.0", default-features = false } anyhow = { version = "1.0", default-features = false }
filetime = { version = "0.2", optional = true } filetime = { version = "0.2", optional = true }
flagset = { version = "0.4", default-features = false, 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 } log = { version = "0.4", default-features = false, optional = true }
memmap2 = { version = "0.9", optional = true } memmap2 = { version = "0.9", optional = true }
num-traits = { version = "0.2", default-features = false, optional = true } num-traits = { version = "0.2", default-features = false, optional = true }
object = { version = "0.37", default-features = false, features = ["read_core", "elf", "coff"] } object = { version = "0.39.1", default-features = false, features = ["read_core", "elf", "coff"] }
pbjson = { version = "0.8", default-features = false, optional = true } pbjson = { version = "0.9", default-features = false, optional = true }
prost = { version = "0.14", default-features = false, features = ["derive"], optional = true } prost = { version = "0.14", default-features = false, features = ["derive"], optional = true }
regex = { version = "1.12", 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 } 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 } similar = { version = "3.1", default-features = false, features = ["hashbrown"], optional = true }
typed-path = { version = "0.12", default-features = false, optional = true } typed-path = { version = "0.12", default-features = false, optional = true }
# config # config
@@ -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 } serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true }
# dwarf # 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 } typed-arena = { version = "2.0", default-features = false, optional = true }
# ppc # ppc
@@ -155,7 +155,7 @@ powerpc = { version = "0.4", optional = true }
rlwinmdec = { version = "1.1", optional = true } rlwinmdec = { version = "1.1", optional = true }
# mips # mips
rabbitizer = { version = "2.0.0-alpha.7", default-features = false, features = ["all_extensions"], optional = true } rabbitizer = { version = "2.0.0-alpha.9", default-features = false, features = ["all_extensions"], optional = true }
# x86 # x86
iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true } iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true }
@@ -187,17 +187,17 @@ winapi = { version = "0.3", optional = true, features = ["winbase"] }
# For Linux static binaries, use rustls # For Linux static binaries, use rustls
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "rustls-tls"], optional = true } reqwest = { version = "0.13", default-features = false, features = ["blocking", "json", "multipart", "rustls"], optional = true }
self_update = { version = "0.42", default-features = false, features = ["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 # For all other platforms, use native TLS
[target.'cfg(not(target_os = "linux"))'.dependencies] [target.'cfg(not(target_os = "linux"))'.dependencies]
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "default-tls"], optional = true } reqwest = { version = "0.13", default-features = false, features = ["blocking", "json", "multipart", "default-tls"], optional = true }
self_update = { version = "0.42", optional = true } self_update = { version = "1.0.0-rc.3", default-features = false, features = ["reqwest", "native-tls", "github"], optional = true }
[build-dependencies] [build-dependencies]
heck = { version = "0.5", optional = true } 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 } prettyplease = { version = "0.2", optional = true }
proc-macro2 = { version = "1.0", optional = true } proc-macro2 = { version = "1.0", optional = true }
prost-build = { version = "0.14", optional = true } prost-build = { version = "0.14", optional = true }
+54 -1
View File
@@ -117,6 +117,51 @@
"name": "Combine text sections", "name": "Combine text sections",
"description": "Combines all text sections into one." "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", "id": "arm.archVersion",
"type": "choice", "type": "choice",
@@ -300,6 +345,13 @@
"name": "Register '$' prefix", "name": "Register '$' prefix",
"description": "Display MIPS register names with a '$' 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", "id": "ppc.calculatePoolRelocations",
"type": "boolean", "type": "boolean",
@@ -338,6 +390,7 @@
"id": "general", "id": "general",
"name": "General", "name": "General",
"properties": [ "properties": [
"preferredStringEncoding",
"functionRelocDiffs", "functionRelocDiffs",
"demangler", "demangler",
"showSymbolSizes", "showSymbolSizes",
@@ -363,7 +416,7 @@
{ {
"id": "mips", "id": "mips",
"name": "MIPS", "name": "MIPS",
"properties": ["mips.abi", "mips.instrCategory", "mips.registerPrefix"] "properties": ["mips.abi", "mips.instrCategory", "mips.registerPrefix", "mips.prodgSnAsInvertedRegs"]
}, },
{ {
"id": "ppc", "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.
+47 -9
View File
@@ -162,7 +162,7 @@ impl ArchArm {
} }
impl Arch for ArchArm { impl Arch for ArchArm {
fn post_init(&mut self, sections: &[Section], symbols: &[Symbol]) { fn post_init(&mut self, sections: &[Section], symbols: &[Symbol], _symbol_indices: &[usize]) {
self.disasm_modes = Self::get_mapping_symbols(sections, symbols); self.disasm_modes = Self::get_mapping_symbols(sections, symbols);
} }
@@ -183,7 +183,7 @@ impl Arch for ArchArm {
let mapping_symbols = self let mapping_symbols = self
.disasm_modes .disasm_modes
.get(&section_index) .get(&section_index)
.map(|x| x.as_slice()) .map(|x| if x.is_empty() { &fallback_mappings } else { x.as_slice() })
.unwrap_or(&fallback_mappings); .unwrap_or(&fallback_mappings);
let first_mapping_idx = mapping_symbols let first_mapping_idx = mapping_symbols
.binary_search_by_key(&start_addr, |x| x.address) .binary_search_by_key(&start_addr, |x| x.address)
@@ -224,7 +224,9 @@ impl Arch for ArchArm {
} }
// Check how many bytes we can/should read // Check how many bytes we can/should read
let num_code_bytes = if data.len() >= 4 { let bytes_until_next_mapping =
next_mapping.map(|m| (m.address - address) as usize).unwrap_or(usize::MAX);
let num_code_bytes = if data.len() >= 4 && bytes_until_next_mapping >= 4 {
if mode == unarm::ParseMode::Data && address & 3 != 0 { if mode == unarm::ParseMode::Data && address & 3 != 0 {
// 32-bit .word value should be aligned on a 4-byte boundary, otherwise use .hword // 32-bit .word value should be aligned on a 4-byte boundary, otherwise use .hword
2 2
@@ -341,6 +343,15 @@ impl Arch for ArchArm {
// Handle ELF implicit relocations // Handle ELF implicit relocations
object::RelocationFlags::Elf { r_type } => { object::RelocationFlags::Elf { r_type } => {
if relocation.has_implicit_addend() { if relocation.has_implicit_addend() {
// R_ARM_V4BX marks a `bx` instruction so the linker can optionally
// rewrite it for ARMv4 interworking. Drops the addend since it's meaningless.
if r_type == elf::R_ARM_V4BX {
return Ok(Some(RelocationOverride {
target: RelocationOverrideTarget::Skip,
addend: 0,
}));
}
let section_data = section.data()?; let section_data = section.data()?;
let address = address as usize; let address = address as usize;
let addend = match r_type { let addend = match r_type {
@@ -350,7 +361,7 @@ impl Arch for ArchArm {
| elf::R_ARM_CALL | elf::R_ARM_CALL
| elf::R_ARM_JUMP24 => { | elf::R_ARM_JUMP24 => {
let data = section_data[address..address + 4].try_into()?; let data = section_data[address..address + 4].try_into()?;
let addend = self.endianness.read_i32_bytes(data); let addend = self.endianness.read_i32(data);
let imm24 = addend & 0xffffff; let imm24 = addend & 0xffffff;
(imm24 << 2) << 8 >> 8 (imm24 << 2) << 8 >> 8
} }
@@ -358,18 +369,34 @@ impl Arch for ArchArm {
// Thumb calls // Thumb calls
elf::R_ARM_THM_PC22 | elf::R_ARM_THM_XPC22 => { elf::R_ARM_THM_PC22 | elf::R_ARM_THM_XPC22 => {
let data = section_data[address..address + 2].try_into()?; let data = section_data[address..address + 2].try_into()?;
let high = self.endianness.read_i16_bytes(data) as i32; let high = self.endianness.read_i16(data) as i32;
let data = section_data[address + 2..address + 4].try_into()?; let data = section_data[address + 2..address + 4].try_into()?;
let low = self.endianness.read_i16_bytes(data) as i32; let low = self.endianness.read_i16(data) as i32;
let imm22 = ((high & 0x7ff) << 11) | (low & 0x7ff); let imm22 = ((high & 0x7ff) << 11) | (low & 0x7ff);
(imm22 << 1) << 9 >> 9 (imm22 << 1) << 9 >> 9
} }
// Thumb unconditional branch (B, 11-bit offset)
elf::R_ARM_THM_PC11 => {
let data = section_data[address..address + 2].try_into()?;
let insn = self.endianness.read_u16(data) as i32;
let imm11 = insn & 0x7ff;
(imm11 << 1) << 20 >> 20
}
// Thumb conditional branch (B<cond>, 8-bit offset)
elf::R_ARM_THM_PC9 => {
let data = section_data[address..address + 2].try_into()?;
let insn = self.endianness.read_u16(data) as i32;
let imm8 = insn & 0xff;
(imm8 << 1) << 23 >> 23
}
// Data // Data
elf::R_ARM_ABS32 => { elf::R_ARM_ABS32 => {
let data = section_data[address..address + 4].try_into()?; let data = section_data[address..address + 4].try_into()?;
self.endianness.read_i32_bytes(data) self.endianness.read_i32(data)
} }
flags => bail!("Unsupported ARM implicit relocation {flags:?}"), flags => bail!("Unsupported ARM implicit relocation {flags:?}"),
@@ -399,6 +426,9 @@ impl Arch for ArchArm {
elf::R_ARM_PC24 => Some("R_ARM_PC24"), elf::R_ARM_PC24 => Some("R_ARM_PC24"),
elf::R_ARM_XPC25 => Some("R_ARM_XPC25"), elf::R_ARM_XPC25 => Some("R_ARM_XPC25"),
elf::R_ARM_CALL => Some("R_ARM_CALL"), elf::R_ARM_CALL => Some("R_ARM_CALL"),
elf::R_ARM_THM_PC11 => Some("R_ARM_THM_PC11"),
elf::R_ARM_THM_PC9 => Some("R_ARM_THM_PC9"),
elf::R_ARM_V4BX => Some("R_ARM_V4BX"),
_ => None, _ => None,
}, },
_ => None, _ => None,
@@ -418,6 +448,8 @@ impl Arch for ArchArm {
elf::R_ARM_PC24 => 4, elf::R_ARM_PC24 => 4,
elf::R_ARM_XPC25 => 4, elf::R_ARM_XPC25 => 4,
elf::R_ARM_CALL => 4, elf::R_ARM_CALL => 4,
elf::R_ARM_THM_PC11 => 2,
elf::R_ARM_THM_PC9 => 2,
_ => 1, _ => 1,
}, },
_ => 1, _ => 1,
@@ -449,9 +481,13 @@ impl Arch for ArchArm {
while next_address >= symbol.address + 2 while next_address >= symbol.address + 2
&& let Some(data) = section.data_range(next_address - 2, 2) && let Some(data) = section.data_range(next_address - 2, 2)
&& data == [0u8; 2] && data == [0u8; 2]
&& section.relocation_at(next_address - 2, 2).is_none()
{ {
next_address -= 2; next_address -= 2;
if let Some(relocation) = section.relocation_at(next_address, 2) {
// Avoid cutting trailing relocations in half.
next_address += self.data_reloc_size(relocation.flags) as u64;
break;
}
} }
Ok(next_address.saturating_sub(symbol.address)) Ok(next_address.saturating_sub(symbol.address))
} }
@@ -544,7 +580,9 @@ impl unarm::FormatIns for ArgsFormatter<'_> {
| RelocationFlags::Elf(elf::R_ARM_THM_PC22) | RelocationFlags::Elf(elf::R_ARM_THM_PC22)
| RelocationFlags::Elf(elf::R_ARM_PC24) | RelocationFlags::Elf(elf::R_ARM_PC24)
| RelocationFlags::Elf(elf::R_ARM_XPC25) | RelocationFlags::Elf(elf::R_ARM_XPC25)
| RelocationFlags::Elf(elf::R_ARM_CALL) => { | RelocationFlags::Elf(elf::R_ARM_CALL)
| RelocationFlags::Elf(elf::R_ARM_THM_PC11)
| RelocationFlags::Elf(elf::R_ARM_THM_PC9) => {
return self.write(InstructionPart::reloc()); return self.write(InstructionPart::reloc());
} }
_ => {} _ => {}
+11 -9
View File
@@ -77,7 +77,7 @@ impl ArchMips {
.and_then(|section| section.data().ok()) .and_then(|section| section.data().ok())
.and_then(|data| data.get(0x14..0x18)) .and_then(|data| data.get(0x14..0x18))
.and_then(|s| s.try_into().ok()) .and_then(|s| s.try_into().ok())
.map(|bytes| endianness.read_i32_bytes(bytes)) .map(|bytes| endianness.read_i32(bytes))
.unwrap_or(0); .unwrap_or(0);
// Parse all relocations to pair R_MIPS_HI16 and R_MIPS_LO16. Since the instructions only // 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() { match reloc.flags() {
object::RelocationFlags::Elf { r_type: elf::R_MIPS_HI16 } => { object::RelocationFlags::Elf { r_type: elf::R_MIPS_HI16 } => {
let code = data[addr as usize..addr as usize + 4].try_into()?; 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 = Some(addr);
last_hi_addend = addend; last_hi_addend = addend;
} }
object::RelocationFlags::Elf { r_type: elf::R_MIPS_LO16 } => { object::RelocationFlags::Elf { r_type: elf::R_MIPS_LO16 } => {
let code = data[addr as usize..addr as usize + 4].try_into()?; 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; let full_addend = (last_hi_addend + addend) as i64;
if let Some(hi_addr) = last_hi.take() { if let Some(hi_addr) = last_hi.take() {
addends.insert(hi_addr, full_addend); addends.insert(hi_addr, full_addend);
@@ -188,6 +188,7 @@ impl ArchMips {
rabbitizer::InstructionDisplayFlags::default() rabbitizer::InstructionDisplayFlags::default()
.with_unknown_instr_comment(false) .with_unknown_instr_comment(false)
.with_use_dollar(diff_config.mips_register_prefix) .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( fn parse_ins_ref(
@@ -197,7 +198,7 @@ impl ArchMips {
diff_config: &DiffObjConfig, diff_config: &DiffObjConfig,
) -> Result<rabbitizer::Instruction> { ) -> Result<rabbitizer::Instruction> {
Ok(rabbitizer::Instruction::new( 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), Vram::new(ins_ref.address as u32),
self.instruction_flags(diff_config), self.instruction_flags(diff_config),
)) ))
@@ -216,8 +217,8 @@ impl Arch for ArchMips {
let instruction_flags = self.instruction_flags(diff_config); let instruction_flags = self.instruction_flags(diff_config);
let mut ops = Vec::<InstructionRef>::with_capacity(code.len() / 4); let mut ops = Vec::<InstructionRef>::with_capacity(code.len() / 4);
let mut cur_addr = address as u32; let mut cur_addr = address as u32;
for chunk in code.chunks_exact(4) { for chunk in code.as_chunks::<4>().0 {
let code = self.endianness.read_u32_bytes(chunk.try_into()?); let code = self.endianness.read_u32(*chunk);
let instruction = let instruction =
rabbitizer::Instruction::new(code, Vram::new(cur_addr), instruction_flags); rabbitizer::Instruction::new(code, Vram::new(cur_addr), instruction_flags);
let opcode = instruction.opcode() as u16; 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 instruction = self.parse_ins_ref(resolved.ins_ref, resolved.code, diff_config)?;
let display_flags = self.instruction_display_flags(diff_config); let display_flags = self.instruction_display_flags(diff_config);
let opcode = instruction.opcode(); 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)?; push_args(&instruction, resolved.relocation, &display_flags, cb)?;
Ok(()) Ok(())
} }
@@ -269,7 +271,7 @@ impl Arch for ArchMips {
let data = section.data()?; let data = section.data()?;
let code = self let code = self
.endianness .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 { let addend = match r_type {
elf::R_MIPS_32 => code as i64, elf::R_MIPS_32 => code as i64,
elf::R_MIPS_26 => ((code & 0x03FFFFFF) << 2) as i64, elf::R_MIPS_26 => ((code & 0x03FFFFFF) << 2) as i64,
@@ -366,7 +368,7 @@ impl Arch for ArchMips {
&& new_address >= symbol.address + 4 && new_address >= symbol.address + 4
&& let Some(data) = section.data_range(new_address - 4, 4) && let Some(data) = section.data_range(new_address - 4, 4)
&& let instruction = rabbitizer::Instruction::new( && 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), Vram::new((new_address - 4) as u32),
self.default_instruction_flags(), self.default_instruction_flags(),
) )
+127 -38
View File
@@ -15,7 +15,7 @@ use object::Endian as _;
use crate::{ use crate::{
diff::{ diff::{
DiffObjConfig, DiffSide, ConfigEnum, DiffObjConfig, DiffSide, PreferredStringEncoding,
display::{ContextItem, HoverItem, InstructionPart}, display::{ContextItem, HoverItem, InstructionPart},
}, },
obj::{ obj::{
@@ -42,15 +42,42 @@ pub mod x86;
pub const OPCODE_INVALID: u16 = u16::MAX; pub const OPCODE_INVALID: u16 = u16::MAX;
pub const OPCODE_DATA: u16 = u16::MAX - 1; pub const OPCODE_DATA: u16 = u16::MAX - 1;
const SUPPORTED_ENCODINGS: [(&encoding_rs::Encoding, &str); 7] = [ const SUPPORTED_ENCODINGS_WITH_NULL_TERM: [(&encoding_rs::Encoding, &str); 5] = [
(encoding_rs::UTF_8, "UTF-8"), (encoding_rs::UTF_8, "UTF-8"),
(encoding_rs::SHIFT_JIS, "Shift JIS"), (encoding_rs::SHIFT_JIS, "Shift JIS"),
(encoding_rs::UTF_16BE, "UTF-16BE"),
(encoding_rs::UTF_16LE, "UTF-16LE"),
(encoding_rs::WINDOWS_1252, "Windows-1252"), (encoding_rs::WINDOWS_1252, "Windows-1252"),
(encoding_rs::EUC_JP, "EUC-JP"), (encoding_rs::EUC_JP, "EUC-JP"),
(encoding_rs::BIG5, "Big5"), (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 /// Represents the type of data associated with an instruction
#[derive(PartialEq)] #[derive(PartialEq)]
@@ -83,18 +110,14 @@ impl fmt::Display for DataType {
impl DataType { impl DataType {
pub fn display_labels(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<String> { pub fn display_labels(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<String> {
let mut strs = Vec::new(); let mut strs = Vec::new();
for (literal, label_override) in self.display_literals(endian, bytes) { for lit_info in self.display_literals(endian, bytes) {
let label = label_override.unwrap_or_else(|| self.to_string()); let label = lit_info.label_override.unwrap_or_else(|| self.to_string());
strs.push(format!("{label}: {literal:?}")) strs.push(format!("{}: {:?}", label, lit_info.literal))
} }
strs strs
} }
pub fn display_literals( pub fn display_literals(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<LiteralInfo> {
&self,
endian: object::Endianness,
bytes: &[u8],
) -> Vec<(String, Option<String>)> {
let mut strs = Vec::new(); let mut strs = Vec::new();
if self.required_len().is_some_and(|l| bytes.len() < l) { if self.required_len().is_some_and(|l| bytes.len() < l) {
log::warn!( log::warn!(
@@ -118,58 +141,70 @@ impl DataType {
match self { match self {
DataType::Int8 => { DataType::Int8 => {
let i = i8::from_ne_bytes(bytes.try_into().unwrap()); 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 { if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None)); strs.push(LiteralInfo {
literal: format!("{:#x}", ReallySigned(i)),
..Default::default()
});
} }
} }
DataType::Int16 => { DataType::Int16 => {
let i = endian.read_i16_bytes(bytes.try_into().unwrap()); let i = endian.read_i16(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None)); strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
if i < 0 { if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None)); strs.push(LiteralInfo {
literal: format!("{:#x}", ReallySigned(i)),
..Default::default()
});
} }
} }
DataType::Int32 => { DataType::Int32 => {
let i = endian.read_i32_bytes(bytes.try_into().unwrap()); let i = endian.read_i32(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None)); strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
if i < 0 { if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None)); strs.push(LiteralInfo {
literal: format!("{:#x}", ReallySigned(i)),
..Default::default()
});
} }
} }
DataType::Int64 => { DataType::Int64 => {
let i = endian.read_i64_bytes(bytes.try_into().unwrap()); let i = endian.read_i64(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None)); strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
if i < 0 { if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None)); strs.push(LiteralInfo {
literal: format!("{:#x}", ReallySigned(i)),
..Default::default()
});
} }
} }
DataType::Float => { DataType::Float => {
let bytes: [u8; 4] = bytes.try_into().unwrap(); let bytes: [u8; 4] = bytes.try_into().unwrap();
strs.push(( strs.push(LiteralInfo {
format!("{:?}f", match endian { literal: format!("{:?}f", match endian {
object::Endianness::Little => f32::from_le_bytes(bytes), object::Endianness::Little => f32::from_le_bytes(bytes),
object::Endianness::Big => f32::from_be_bytes(bytes), object::Endianness::Big => f32::from_be_bytes(bytes),
}), }),
None, ..Default::default()
)); });
} }
DataType::Double => { DataType::Double => {
let bytes: [u8; 8] = bytes.try_into().unwrap(); let bytes: [u8; 8] = bytes.try_into().unwrap();
strs.push(( strs.push(LiteralInfo {
format!("{:?}", match endian { literal: format!("{:?}", match endian {
object::Endianness::Little => f64::from_le_bytes(bytes), object::Endianness::Little => f64::from_le_bytes(bytes),
object::Endianness::Big => f64::from_be_bytes(bytes), object::Endianness::Big => f64::from_be_bytes(bytes),
}), }),
None, ..Default::default()
)); });
} }
DataType::Bytes => { DataType::Bytes => {
strs.push((format!("{bytes:#?}"), None)); strs.push(LiteralInfo { literal: format!("{bytes:#?}"), ..Default::default() });
} }
DataType::String => { DataType::String => {
if let Some(nul_idx) = bytes.iter().position(|&c| c == b'\0') { if let Some(nul_idx) = bytes.iter().position(|&c| c == b'\0') {
@@ -177,16 +212,51 @@ impl DataType {
// Special case to display (ASCII) as the label for ASCII-only strings. // Special case to display (ASCII) as the label for ASCII-only strings.
let (cow, _, had_errors) = encoding_rs::UTF_8.decode(str_bytes); let (cow, _, had_errors) = encoding_rs::UTF_8.decode(str_bytes);
if !had_errors && cow.is_ascii() { if !had_errors && cow.is_ascii() {
strs.push((format!("{cow}"), Some("ASCII".into()))); 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 { for (encoding, encoding_name) in SUPPORTED_ENCODINGS_WITH_NULL_TERM {
let (cow, _, had_errors) = encoding.decode(str_bytes); let (cow, _, had_errors) = encoding.decode(str_bytes);
// Avoid showing ASCII-only strings more than once if the encoding is ASCII-compatible. if !had_errors {
if !had_errors && (!encoding.is_ascii_compatible() || !cow.is_ascii()) { let string = format!("{cow}");
strs.push((format!("{cow}"), Some(encoding_name.into()))); 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,
});
}
}
} }
} }
@@ -321,7 +391,8 @@ impl dyn Arch {
pub trait Arch: Any + Debug + Send + Sync { pub trait Arch: Any + Debug + Send + Sync {
/// Finishes arch-specific initialization that must be done after sections have been combined. /// 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. /// Generate a list of instructions references (offset, size, opcode) from the given code.
/// ///
@@ -499,3 +570,21 @@ pub struct RelocationOverride {
pub target: RelocationOverrideTarget, pub target: RelocationOverrideTarget,
pub addend: i64, 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> { fn get_string_data(obj: &Object, symbol_index: usize, offset: Simm) -> Option<&str> {
if let Some(sym) = obj.symbols.get(symbol_index) if let Some(sym) = obj.symbols.get(symbol_index)
&& sym.name.starts_with("@stringBase") && sym.name.starts_with("@stringBase")
+34 -7
View File
@@ -238,9 +238,9 @@ impl Arch for ArchPpc {
addend: 0, addend: 0,
})), })),
|(_, reloc)| match reloc.target() { |(_, reloc)| match reloc.target() {
object::RelocationTarget::Symbol(index) => Ok(Some(RelocationOverride { object::RelocationTarget::Symbol(_) => Ok(Some(RelocationOverride {
target: RelocationOverrideTarget::Keep, target: RelocationOverrideTarget::Keep,
addend: index.0 as u16 as i16 as i64, addend: 0,
})), })),
target => Err(anyhow!("Unsupported IMAGE_REL_PPC_PAIR target {target:?}")), target => Err(anyhow!("Unsupported IMAGE_REL_PPC_PAIR target {target:?}")),
}, },
@@ -342,7 +342,10 @@ impl Arch for ArchPpc {
elf::R_PPC_UADDR32 => 4, elf::R_PPC_UADDR32 => 4,
_ => 1, _ => 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> { fn guess_data_type(&self, resolved: ResolvedInstructionRef, bytes: &[u8]) -> Option<DataType> {
if resolved.relocation.is_some_and(|r| r.symbol.name.starts_with("@stringBase")) { if resolved.relocation.is_some_and(|r| {
// Pooled string. 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); return Some(DataType::String);
} }
let opcode = powerpc::Opcode::from(resolved.ins_ref.opcode); let opcode = powerpc::Opcode::from(resolved.ins_ref.opcode);
@@ -440,9 +448,13 @@ impl Arch for ArchPpc {
let simplified = ins.simplified().to_string(); let simplified = ins.simplified().to_string();
let show_orig = orig != simplified; let show_orig = orig != simplified;
let mut out = Vec::with_capacity(2); 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 { 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 out
} }
@@ -463,12 +475,27 @@ impl Arch for ArchPpc {
} }
Ok(next_address.saturating_sub(symbol.address)) 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 { impl ArchPpc {
pub fn extab_for_symbol(&self, symbol_index: usize) -> Option<&ExceptionInfo> { pub fn extab_for_symbol(&self, symbol_index: usize) -> Option<&ExceptionInfo> {
self.extab.as_ref()?.get(&symbol_index) 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 { 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 ops = Vec::<InstructionRef>::with_capacity(code.len() / 2);
let mut offset = address; let mut offset = address;
for chunk in code.chunks_exact(2) { for chunk in code.as_chunks::<2>().0 {
let opcode = u16::from_be_bytes(chunk.try_into().unwrap()); let opcode = u16::from_be_bytes(*chunk);
let mut parts: Vec<InstructionPart> = vec![]; let mut parts: Vec<InstructionPart> = vec![];
let resolved: ResolvedInstructionRef = Default::default(); let resolved: ResolvedInstructionRef = Default::default();
let mut branch_dest: Option<u64> = None; 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 data_offsets = BTreeMap::<u64, DataInfo>::new();
let mut pos: u64 = 0; let mut pos: u64 = 0;
for chunk in symbol_data.chunks_exact(2) { for chunk in symbol_data.as_chunks::<2>().0 {
let opcode = u16::from_be_bytes(chunk.try_into().unwrap()); let opcode = u16::from_be_bytes(*chunk);
// mov.w // mov.w
if (opcode & 0xf000) == 0x9000 { if (opcode & 0xf000) == 0x9000 {
let target = (opcode as u64 & 0xff) * 2 + 4 + pos; let target = (opcode as u64 & 0xff) * 2 + 4 + pos;
+59 -10
View File
@@ -68,14 +68,26 @@ impl ArchX86 {
_ => None, _ => None,
}, },
RelocationFlags::Elf(typ) => match typ { 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), elf::R_386_16 => Some(2),
_ => None, _ => None,
}, },
}, },
Architecture::X86_64 => match flags { Architecture::X86_64 => match flags {
RelocationFlags::Coff(typ) => match typ { 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), pe::IMAGE_REL_AMD64_ADDR64 => Some(8),
_ => None, _ => None,
}, },
@@ -176,6 +188,12 @@ impl Arch for ArchX86 {
branch_dest, 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) Ok(out)
} }
@@ -188,8 +206,8 @@ impl Arch for ArchX86 {
if resolved.ins_ref.opcode == OPCODE_DATA { if resolved.ins_ref.opcode == OPCODE_DATA {
let (mnemonic, imm) = match resolved.ins_ref.size { let (mnemonic, imm) = match resolved.ins_ref.size {
1 => (".byte", resolved.code[0] as u64), 1 => (".byte", resolved.code[0] as u64),
2 => (".word", self.endianness.read_u16_bytes(resolved.code.try_into()?) as u64), 2 => (".word", self.endianness.read_u16(resolved.code.try_into()?) as u64),
4 => (".dword", self.endianness.read_u32_bytes(resolved.code.try_into()?) as u64), 4 => (".dword", self.endianness.read_u32(resolved.code.try_into()?) as u64),
_ => bail!("Unsupported x86 inline data size {}", resolved.ins_ref.size), _ => bail!("Unsupported x86 inline data size {}", resolved.ins_ref.size),
}; };
cb(InstructionPart::opcode(mnemonic, OPCODE_DATA))?; cb(InstructionPart::opcode(mnemonic, OPCODE_DATA))?;
@@ -272,27 +290,43 @@ impl Arch for ArchX86 {
object::RelocationFlags::Coff { object::RelocationFlags::Coff {
typ: pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32, 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 = let data =
section.data()?[address as usize..address as usize + 4].try_into()?; 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:?}"), flags => bail!("Unsupported x86 implicit relocation {flags:?}"),
}, },
Architecture::X86_64 => match relocation.flags() { Architecture::X86_64 => match relocation.flags() {
object::RelocationFlags::Coff { 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 } => { | object::RelocationFlags::Elf { r_type: elf::R_X86_64_32 | elf::R_X86_64_PC32 } => {
let data = let data =
section.data()?[address as usize..address as usize + 4].try_into()?; 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::Coff { typ: pe::IMAGE_REL_AMD64_ADDR64 }
| object::RelocationFlags::Elf { r_type: elf::R_X86_64_64 } => { | object::RelocationFlags::Elf { r_type: elf::R_X86_64_64 } => {
let data = let data =
section.data()?[address as usize..address as usize + 8].try_into()?; 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:?}"), 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"), pe::IMAGE_REL_I386_REL32 => Some("IMAGE_REL_I386_REL32"),
_ => None, _ => 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 { Architecture::X86_64 => match flags {
RelocationFlags::Coff(typ) => match typ { RelocationFlags::Coff(typ) => match typ {
pe::IMAGE_REL_AMD64_ADDR64 => Some("IMAGE_REL_AMD64_ADDR64"), pe::IMAGE_REL_AMD64_ADDR64 => Some("IMAGE_REL_AMD64_ADDR64"),
pe::IMAGE_REL_AMD64_ADDR32NB => Some("IMAGE_REL_AMD64_ADDR32NB"), 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 => 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,
}, },
_ => None, _ => None,

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