Compare commits

..

21 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
53 changed files with 2617 additions and 1238 deletions
+17 -18
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
@@ -67,7 +67,7 @@ jobs:
# 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
+1734 -1002
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.7.1"
version = "3.7.3"
authors = ["Luke Street <luke@street.dev>"]
edition = "2024"
license = "MIT OR Apache-2.0"
+6 -4
View File
@@ -75,9 +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-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-2026-0002", reason = "Unsound advisory in lru 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.
@@ -245,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 = []
+1 -1
View File
@@ -20,7 +20,7 @@ enable-ansi-support = "0.3"
memmap2 = "0.9"
objdiff-core = { path = "../objdiff-core", features = ["all"] }
prost = "0.14"
ratatui = "0.29"
ratatui = "0.30"
rayon = "1.11"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
+40 -12
View File
@@ -1,4 +1,6 @@
#![allow(unused)] // TODO
use std::{
collections::BTreeMap,
io::stdout,
mem,
sync::{
@@ -79,11 +81,13 @@ pub struct Args {
}
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(),
@@ -95,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())
@@ -163,15 +167,24 @@ 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"),
};
if let Some(output) = &args.output {
run_oneshot(&args, output, target_path.as_deref(), base_path.as_deref(), unit_options)
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)
run_interactive(args, target_path, base_path, project_config, unit_options, symbol_mappings)
}
}
@@ -180,10 +193,13 @@ fn run_oneshot(
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, None, unit_options.as_ref())?;
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)
@@ -198,8 +214,8 @@ fn run_oneshot(
.transpose()?;
let result =
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
let left = target.as_ref().and_then(|o| result.left.as_ref().map(|d| (o, d)));
let right = base.as_ref().and_then(|o| result.right.as_ref().map(|d| (o, d)));
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(())
@@ -209,6 +225,7 @@ 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()) {
@@ -218,7 +235,11 @@ fn build_config_from_args(
apply_project_options(&mut diff_config, options)?;
}
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 {
@@ -279,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 {
@@ -308,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(),
}
}
}
@@ -358,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()
+1
View File
@@ -1,3 +1,4 @@
#![allow(unused)] // TODO
use core::cmp::Ordering;
use anyhow::Result;
+1
View File
@@ -1,3 +1,4 @@
#![allow(unused)] // TODO
use anyhow::Result;
use crossterm::event::Event;
use ratatui::Frame;
+11 -11
View File
@@ -128,16 +128,16 @@ 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.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 }
similar = { version = "3.1", default-features = false, features = ["hashbrown"], optional = true }
typed-path = { version = "0.12", default-features = false, optional = true }
# 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 }
# 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,7 +155,7 @@ powerpc = { version = "0.4", optional = true }
rlwinmdec = { version = "1.1", optional = true }
# 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
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
[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 }
+54 -1
View File
@@ -117,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",
@@ -300,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",
@@ -338,6 +390,7 @@
"id": "general",
"name": "General",
"properties": [
"preferredStringEncoding",
"functionRelocDiffs",
"demangler",
"showSymbolSizes",
@@ -363,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",
+22 -8
View File
@@ -183,7 +183,7 @@ impl Arch for ArchArm {
let mapping_symbols = self
.disasm_modes
.get(&section_index)
.map(|x| x.as_slice())
.map(|x| if x.is_empty() { &fallback_mappings } else { x.as_slice() })
.unwrap_or(&fallback_mappings);
let first_mapping_idx = mapping_symbols
.binary_search_by_key(&start_addr, |x| x.address)
@@ -343,6 +343,15 @@ impl Arch for ArchArm {
// Handle ELF implicit relocations
object::RelocationFlags::Elf { r_type } => {
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 address = address as usize;
let addend = match r_type {
@@ -352,7 +361,7 @@ impl Arch for ArchArm {
| elf::R_ARM_CALL
| elf::R_ARM_JUMP24 => {
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;
(imm24 << 2) << 8 >> 8
}
@@ -360,9 +369,9 @@ impl Arch for ArchArm {
// Thumb calls
elf::R_ARM_THM_PC22 | elf::R_ARM_THM_XPC22 => {
let data = section_data[address..address + 2].try_into()?;
let high = self.endianness.read_i16_bytes(data) as i32;
let high = self.endianness.read_i16(data) as i32;
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);
(imm22 << 1) << 9 >> 9
@@ -371,7 +380,7 @@ impl Arch for ArchArm {
// 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_bytes(data) as i32;
let insn = self.endianness.read_u16(data) as i32;
let imm11 = insn & 0x7ff;
(imm11 << 1) << 20 >> 20
}
@@ -379,7 +388,7 @@ impl Arch for ArchArm {
// 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_bytes(data) as i32;
let insn = self.endianness.read_u16(data) as i32;
let imm8 = insn & 0xff;
(imm8 << 1) << 23 >> 23
}
@@ -387,7 +396,7 @@ impl Arch for ArchArm {
// Data
elf::R_ARM_ABS32 => {
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:?}"),
@@ -419,6 +428,7 @@ impl Arch for ArchArm {
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,
@@ -471,9 +481,13 @@ impl Arch for ArchArm {
while next_address >= symbol.address + 2
&& let Some(data) = section.data_range(next_address - 2, 2)
&& data == [0u8; 2]
&& section.relocation_at(next_address - 2, 2).is_none()
{
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))
}
+11 -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,
@@ -366,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(),
)
+105 -54
View File
@@ -15,7 +15,7 @@ use object::Endian as _;
use crate::{
diff::{
DiffObjConfig, DiffSide,
ConfigEnum, DiffObjConfig, DiffSide, PreferredStringEncoding,
display::{ContextItem, HoverItem, InstructionPart},
},
obj::{
@@ -42,15 +42,42 @@ pub mod x86;
pub const OPCODE_INVALID: u16 = u16::MAX;
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::SHIFT_JIS, "Shift JIS"),
(encoding_rs::UTF_16BE, "UTF-16BE"),
(encoding_rs::UTF_16LE, "UTF-16LE"),
(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)]
@@ -83,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>, 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!(
@@ -118,80 +141,106 @@ impl DataType {
match self {
DataType::Int8 => {
let i = i8::from_ne_bytes(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None, None));
strs.push(LiteralInfo { literal: format!("{i:#x}"), ..Default::default() });
if i < 0 {
strs.push((format!("{:#x}", ReallySigned(i)), None, 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, 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, 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, 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, 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, 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, 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,
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,
None,
));
..Default::default()
});
}
DataType::Bytes => {
strs.push((format!("{bytes:#?}"), None, None));
strs.push(LiteralInfo { literal: format!("{bytes:#?}"), ..Default::default() });
}
DataType::String => {
// Special case to display (ASCII) as the label for ASCII-only strings.
let mut is_ascii = false;
if bytes.is_ascii()
&& let Ok(str) = str::from_utf8(bytes)
{
let trimmed = str.trim_end_matches('\0');
if !trimmed.is_empty() {
let copy_string = escape_special_ascii_characters(trimmed);
strs.push((trimmed.to_string(), Some("ASCII".into()), Some(copy_string)));
is_ascii = true;
if let Some(nul_idx) = bytes.iter().position(|&c| c == b'\0') {
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 {
// Avoid showing ASCII-only strings more than once if the encoding is ASCII-compatible.
if is_ascii && encoding.is_ascii_compatible() {
continue;
}
for (encoding, encoding_name) in SUPPORTED_ENCODINGS_NO_NULL_TERM {
let (cow, _, had_errors) = encoding.decode(bytes);
if had_errors {
continue;
@@ -199,11 +248,13 @@ impl DataType {
let trimmed = cow.trim_end_matches('\0');
if !trimmed.is_empty() {
let copy_string = escape_special_ascii_characters(trimmed);
strs.push((
trimmed.to_string(),
Some(encoding_name.into()),
Some(copy_string),
));
strs.push(LiteralInfo {
literal: trimmed.to_string(),
label_override: Some(encoding_name.into()),
copy_string: Some(copy_string),
hidden: false,
is_string: true,
});
}
}
}
@@ -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")
+5 -2
View File
@@ -359,9 +359,12 @@ 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") || r.symbol.name.starts_with("$SG")
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())
}) {
// Pooled 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);
+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;
+34 -7
View File
@@ -80,7 +80,14 @@ impl ArchX86 {
},
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,
},
@@ -181,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)
}
@@ -193,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))?;
@@ -288,24 +301,32 @@ impl Arch for ArchX86 {
} => {
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:?}"),
},
@@ -337,6 +358,12 @@ impl Arch for ArchX86 {
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,
+1 -1
View File
@@ -1,4 +1,4 @@
#![allow(clippy::needless_lifetimes)] // Generated serde code
#![allow(clippy::useless_borrows_in_formatting)] // Generated serde code
use alloc::{
string::{String, ToString},
+1 -1
View File
@@ -1,4 +1,4 @@
#![allow(clippy::needless_lifetimes)] // Generated serde code
#![allow(clippy::useless_borrows_in_formatting)] // Generated serde code
use alloc::{
string::{String, ToString},

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