mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6768df9d80 | |||
| eaba2391e0 | |||
| bd8f5ede23 | |||
| acf46c6b54 | |||
| 9358d8ec60 | |||
| 809e2ffddc | |||
| 87fa29e8b0 | |||
| 42d4c38079 | |||
| fa26200ed7 | |||
| a0e7f9bc37 | |||
| 5898d7aebf | |||
| ffb38d1bb0 | |||
| d56dda72f0 | |||
| c6971f3f2d | |||
| 3965a035fa | |||
| f1fc29f77e | |||
| 7c4f1c5d13 |
@@ -0,0 +1,2 @@
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -4,8 +4,8 @@ on:
|
||||
pull_request:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
- 'LICENSE*'
|
||||
- "*.md"
|
||||
- "LICENSE*"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
@@ -32,9 +32,9 @@ jobs:
|
||||
- name: Cache Rust workspace
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Cargo check
|
||||
run: cargo check --features all
|
||||
run: cargo check --all-targets --all-features
|
||||
- name: Cargo clippy
|
||||
run: cargo clippy --features all
|
||||
run: cargo clippy --all-targets --all-features
|
||||
|
||||
fmt:
|
||||
name: Format
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
name: Test
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||
platform: [ubuntu-latest, windows-latest, macos-latest]
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
@@ -159,7 +159,7 @@ jobs:
|
||||
key: ${{ matrix.target }}
|
||||
- name: Cargo build
|
||||
run: >
|
||||
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 }}
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -226,7 +226,7 @@ jobs:
|
||||
key: ${{ matrix.target }}
|
||||
- name: Cargo build
|
||||
run: >
|
||||
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 }}
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -258,7 +258,7 @@ jobs:
|
||||
name: Release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ build-cli, build-gui ]
|
||||
needs: [build-cli, build-gui]
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v5.0.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
args: [--markdown-linebreak-ext=md]
|
||||
- id: end-of-file-fixer
|
||||
- id: fix-byte-order-marker
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: cargo-fmt
|
||||
name: cargo fmt
|
||||
description: Run cargo fmt on all project files.
|
||||
language: system
|
||||
entry: cargo
|
||||
args: ["+nightly", "fmt", "--all"]
|
||||
pass_filenames: false
|
||||
- id: cargo clippy
|
||||
name: cargo clippy
|
||||
description: Run cargo clippy on all project files.
|
||||
language: system
|
||||
entry: cargo
|
||||
args: ["+nightly", "clippy", "--all-targets", "--all-features"]
|
||||
pass_filenames: false
|
||||
- id: cargo-deny
|
||||
name: cargo deny
|
||||
description: Run cargo deny on all project files.
|
||||
language: system
|
||||
entry: cargo
|
||||
args: ["deny", "check"]
|
||||
pass_filenames: false
|
||||
always_run: true
|
||||
Generated
+52
-29
@@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ab_glyph"
|
||||
@@ -450,6 +450,18 @@ version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "auditable-serde"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c7bf8143dfc3c0258df908843e169b5cc5fcf76c7718bd66135ef4a9cd558c5"
|
||||
dependencies = [
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"topological-sort",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
@@ -3268,7 +3280,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-cli"
|
||||
version = "3.0.0-beta.1"
|
||||
version = "3.0.0-beta.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argp",
|
||||
@@ -3291,7 +3303,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-core"
|
||||
version = "3.0.0-beta.1"
|
||||
version = "3.0.0-beta.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arm-attr",
|
||||
@@ -3344,7 +3356,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-gui"
|
||||
version = "3.0.0-beta.1"
|
||||
version = "3.0.0-beta.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if",
|
||||
@@ -3380,7 +3392,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-wasm"
|
||||
version = "3.0.0-beta.1"
|
||||
version = "3.0.0-beta.4"
|
||||
dependencies = [
|
||||
"log",
|
||||
"objdiff-core",
|
||||
@@ -3894,7 +3906,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rabbitizer"
|
||||
version = "2.0.0-dev0"
|
||||
source = "git+https://github.com/Decompollaborate/rabbitizer.git?branch=🦀#06dc8b6c826c3d60e112d4c2cd70aa54e308be12"
|
||||
source = "git+https://github.com/Decompollaborate/rabbitizer.git?branch=%F0%9F%A6%80#06dc8b6c826c3d60e112d4c2cd70aa54e308be12"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
]
|
||||
@@ -4205,9 +4217,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.11"
|
||||
version = "0.17.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da5349ae27d3887ca812fb375b45a4fbb36d8d12d2df394968cd86e35683fe73"
|
||||
checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
@@ -4474,6 +4486,9 @@ name = "semver"
|
||||
version = "1.0.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
@@ -5165,6 +5180,12 @@ dependencies = [
|
||||
"winnow 0.7.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "topological-sort"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d"
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.2"
|
||||
@@ -5537,9 +5558,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.225.0"
|
||||
version = "0.227.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f7eac0445cac73bcf09e6a97f83248d64356dccf9f2b100199769b6b42464e5"
|
||||
checksum = "80bb72f02e7fbf07183443b27b0f3d4144abf8c114189f2e088ed95b696a7822"
|
||||
dependencies = [
|
||||
"leb128fmt",
|
||||
"wasmparser",
|
||||
@@ -5547,11 +5568,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.225.0"
|
||||
version = "0.227.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1d20d0bf2c73c32a5114cf35a5c10ccf9f9aa37a3a2c0114b3e11cbf6faac12"
|
||||
checksum = "ce1ef0faabbbba6674e97a56bee857ccddf942785a336c8b47b42373c922a91d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"auditable-serde",
|
||||
"flate2",
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
@@ -5577,9 +5600,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.225.0"
|
||||
version = "0.227.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36e5456165f81e64cb9908a0fe9b9d852c2c74582aa3fe2be3c2da57f937d3ae"
|
||||
checksum = "0f51cad774fb3c9461ab9bccc9c62dfb7388397b5deda31bf40e8108ccd678b2"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"hashbrown",
|
||||
@@ -6300,19 +6323,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.39.0"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4dd9a372b25d6f35456b0a730d2adabeb0c4878066ba8f8089800349be6ecb5"
|
||||
checksum = "8e7091ed6c9abfa4e0a2ef3b39d0539da992d841fcf32c255f64fb98764ffee5"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt 0.39.0",
|
||||
"wit-bindgen-rt 0.40.0",
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.39.0"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f108fa9b77a346372858b30c11ea903680e7e2b9d820b1a5883e9d530bf51c7e"
|
||||
checksum = "398c650cec1278cfb72e214ba26ef3440ab726e66401bd39c04f465ee3979e6b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
@@ -6330,18 +6353,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
checksum = "68faed92ae696b93ea9a7b67ba6c37bf09d72c6d9a70fa824a743c3020212f11"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.39.0"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5ba5b852e976d35dbf6cb745746bf1bd4fc26782bab1e0c615fc71a7d8aac05"
|
||||
checksum = "83903c8dcd8084a8a67ae08190122cf0e25dc37bdc239070a00f47e22d3f0aae"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
@@ -6355,9 +6378,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.39.0"
|
||||
version = "0.40.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "401529c9af9304a20ed99fa01799e467b7d37727126f0c9a958895471268ad7a"
|
||||
checksum = "a7bf7f20495bcc7dc9f24c5fbcac9e919ca5ebdb7a1b1841d74447d3c8dd0c60"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"prettyplease",
|
||||
@@ -6370,9 +6393,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.225.0"
|
||||
version = "0.227.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2505c917564c1d74774563bbcd3e4f8c216a6508050862fd5f449ee56e3c5125"
|
||||
checksum = "635c3adc595422cbf2341a17fb73a319669cc8d33deed3a48368a841df86b676"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.0",
|
||||
@@ -6414,9 +6437,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.225.0"
|
||||
version = "0.227.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebefaa234e47224f10ce60480c5bfdece7497d0f3b87a12b41ff39e5c8377a78"
|
||||
checksum = "ddf445ed5157046e4baf56f9138c124a0824d4d1657e7204d71886ad8ce2fc11"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ strip = "debuginfo"
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.package]
|
||||
version = "3.0.0-beta.1"
|
||||
version = "3.0.0-beta.4"
|
||||
authors = ["Luke Street <luke@street.dev>"]
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@@ -143,15 +143,15 @@ If not specified, objdiff will use the default patterns listed above.
|
||||
`units` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
|
||||
|
||||
> `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.
|
||||
>
|
||||
>
|
||||
> `target_path`: Path to the "target" or "expected" object from the project root.
|
||||
> This object is the **intended result** of the match.
|
||||
>
|
||||
>
|
||||
> `base_path`: Path to the "base" or "actual" object from the project root.
|
||||
> This object is built from the **current source code**.
|
||||
>
|
||||
>
|
||||
> `metadata.auto_generated` _(optional)_: Hides the object from the object list, but still includes it in reports.
|
||||
>
|
||||
>
|
||||
> `metadata.complete` _(optional)_: Marks the object as "complete" (or "linked") in the object list.
|
||||
> This is useful for marking objects that are fully decompiled. A value of `false` will mark the object as "incomplete".
|
||||
|
||||
@@ -173,6 +173,21 @@ $ cargo install --locked --git https://github.com/encounter/objdiff.git objdiff-
|
||||
|
||||
The binaries will be installed to `~/.cargo/bin` as `objdiff` and `objdiff-cli`.
|
||||
|
||||
## Installing `pre-commit`
|
||||
|
||||
When contributing, it's recommended to install `pre-commit` to automatically run the linter and formatter before a commit.
|
||||
|
||||
[`uv`](https://github.com/astral-sh/uv#installation) is recommended to manage Python version and tools.
|
||||
|
||||
Rust nightly is required for `cargo +nightly fmt` and `cargo +nightly clippy`.
|
||||
|
||||
```shell
|
||||
$ cargo install --locked cargo-deny
|
||||
$ rustup toolchain install nightly
|
||||
$ uv tool install pre-commit
|
||||
$ pre-commit install
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Licensed under either of
|
||||
|
||||
@@ -73,6 +73,7 @@ ignore = [
|
||||
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
||||
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
||||
#{ 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" },
|
||||
]
|
||||
# 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.
|
||||
|
||||
@@ -22,7 +22,7 @@ use crossterm::{
|
||||
use objdiff_core::{
|
||||
bindings::diff::DiffResult,
|
||||
build::{
|
||||
BuildConfig,
|
||||
BuildConfig, BuildStatus,
|
||||
watcher::{Watcher, create_watcher},
|
||||
},
|
||||
config::{
|
||||
@@ -251,6 +251,8 @@ pub struct AppState {
|
||||
pub project_config: Option<ProjectConfig>,
|
||||
pub target_path: Option<Utf8PlatformPathBuf>,
|
||||
pub base_path: Option<Utf8PlatformPathBuf>,
|
||||
pub left_status: Option<BuildStatus>,
|
||||
pub right_status: Option<BuildStatus>,
|
||||
pub left_obj: Option<(Object, ObjectDiff)>,
|
||||
pub right_obj: Option<(Object, ObjectDiff)>,
|
||||
pub prev_obj: Option<(Object, ObjectDiff)>,
|
||||
@@ -348,6 +350,8 @@ impl AppState {
|
||||
JobResult::None => unreachable!("Unexpected JobResult::None"),
|
||||
JobResult::ObjDiff(result) => {
|
||||
let result = result.unwrap();
|
||||
self.left_status = Some(result.first_status);
|
||||
self.right_status = Some(result.second_status);
|
||||
self.left_obj = result.first_obj;
|
||||
self.right_obj = result.second_obj;
|
||||
self.reload_time = Some(result.time);
|
||||
@@ -388,6 +392,8 @@ fn run_interactive(
|
||||
project_config,
|
||||
target_path,
|
||||
base_path,
|
||||
left_status: None,
|
||||
right_status: None,
|
||||
left_obj: None,
|
||||
right_obj: None,
|
||||
prev_obj: None,
|
||||
|
||||
@@ -232,6 +232,7 @@ fn report_object(
|
||||
if symbol.section != Some(section_idx)
|
||||
|| symbol.size == 0
|
||||
|| symbol.flags.contains(SymbolFlag::Hidden)
|
||||
|| symbol.flags.contains(SymbolFlag::Ignored)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use core::cmp::Ordering;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use anyhow::Result;
|
||||
use crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers, MouseButton, MouseEventKind};
|
||||
use objdiff_core::{
|
||||
build::BuildStatus,
|
||||
diff::{
|
||||
DiffObjConfig, FunctionRelocDiffs, InstructionDiffKind, ObjectDiff, SymbolDiff,
|
||||
display::{DiffText, DiffTextColor, HighlightKind, display_row},
|
||||
@@ -126,6 +127,11 @@ impl UiView for FunctionDiffUi {
|
||||
);
|
||||
max_width = max_width.max(text.width());
|
||||
left_text = Some(text);
|
||||
} else if let Some(status) = &state.left_status {
|
||||
let mut text = Text::default();
|
||||
self.print_build_status(&mut text, status);
|
||||
max_width = max_width.max(text.width());
|
||||
left_text = Some(text);
|
||||
}
|
||||
|
||||
let mut right_text = None;
|
||||
@@ -155,6 +161,11 @@ impl UiView for FunctionDiffUi {
|
||||
let rect = content_chunks[1].inner(Margin::new(1, 1));
|
||||
self.print_margin(&mut text, symbol_diff, rect);
|
||||
margin_text = Some(text);
|
||||
} else if let Some(status) = &state.right_status {
|
||||
let mut text = Text::default();
|
||||
self.print_build_status(&mut text, status);
|
||||
max_width = max_width.max(text.width());
|
||||
right_text = Some(text);
|
||||
}
|
||||
|
||||
let mut prev_text = None;
|
||||
@@ -453,7 +464,7 @@ impl UiView for FunctionDiffUi {
|
||||
}
|
||||
(Some((_l, _ls, ld)), None) => ld.instruction_rows.len(),
|
||||
(None, Some((_r, _rs, rd))) => rd.instruction_rows.len(),
|
||||
(None, None) => bail!("Symbol not found: {}", self.symbol_name),
|
||||
(None, None) => 0,
|
||||
};
|
||||
self.left_sym = left_sym;
|
||||
self.right_sym = right_sym;
|
||||
@@ -596,6 +607,18 @@ impl FunctionDiffUi {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_build_status<'a>(&self, out: &mut Text<'a>, status: &'a BuildStatus) {
|
||||
if !status.cmdline.is_empty() {
|
||||
out.lines.push(Line::styled(status.cmdline.clone(), Style::new().fg(Color::LightBlue)));
|
||||
}
|
||||
for line in status.stdout.lines() {
|
||||
out.lines.push(Line::styled(line, Style::new().fg(Color::White)));
|
||||
}
|
||||
for line in status.stderr.lines() {
|
||||
out.lines.push(Line::styled(line, Style::new().fg(Color::Red)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const COLOR_ROTATION: [Color; 7] = [
|
||||
|
||||
@@ -81,6 +81,8 @@ std = [
|
||||
]
|
||||
mips = [
|
||||
"any-arch",
|
||||
"dep:cpp_demangle",
|
||||
"dep:cwdemangle",
|
||||
"dep:rabbitizer",
|
||||
]
|
||||
ppc = [
|
||||
|
||||
@@ -19,7 +19,15 @@ fn compile_protos() {
|
||||
.map(|m| m.modified().unwrap())
|
||||
.unwrap_or(std::time::SystemTime::UNIX_EPOCH);
|
||||
let mut run_protoc = false;
|
||||
let proto_files = vec![root.join("report.proto")];
|
||||
let proto_files = root
|
||||
.read_dir()
|
||||
.unwrap()
|
||||
.filter_map(|e| {
|
||||
let e = e.unwrap();
|
||||
let path = e.path();
|
||||
(path.extension() == Some(std::ffi::OsStr::new("proto"))).then_some(path)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
for proto_file in &proto_files {
|
||||
println!("cargo:rerun-if-changed={}", proto_file.display());
|
||||
let mtime = match std::fs::metadata(proto_file) {
|
||||
|
||||
@@ -275,4 +275,4 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
syntax = "proto3";
|
||||
|
||||
import "report.proto";
|
||||
|
||||
package objdiff.report;
|
||||
|
||||
// A pair of reports to compare and generate changes
|
||||
message ChangesInput {
|
||||
// The previous report
|
||||
Report from = 1;
|
||||
// The current report
|
||||
Report to = 2;
|
||||
}
|
||||
|
||||
// Changes between two reports
|
||||
message Changes {
|
||||
// The progress info for the previous report
|
||||
Measures from = 1;
|
||||
// The progress info for the current report
|
||||
Measures to = 2;
|
||||
// Units that changed
|
||||
repeated ChangeUnit units = 3;
|
||||
}
|
||||
|
||||
// A changed unit
|
||||
message ChangeUnit {
|
||||
// The name of the unit
|
||||
string name = 1;
|
||||
// The previous progress info (omitted if new)
|
||||
optional Measures from = 2;
|
||||
// The current progress info (omitted if removed)
|
||||
optional Measures to = 3;
|
||||
// Sections that changed
|
||||
repeated ChangeItem sections = 4;
|
||||
// Functions that changed
|
||||
repeated ChangeItem functions = 5;
|
||||
// Extra metadata for this unit
|
||||
optional ReportUnitMetadata metadata = 6;
|
||||
}
|
||||
|
||||
// A changed section or function
|
||||
message ChangeItem {
|
||||
// The name of the item
|
||||
string name = 1;
|
||||
// The previous progress info (omitted if new)
|
||||
optional ChangeItemInfo from = 2;
|
||||
// The current progress info (omitted if removed)
|
||||
optional ChangeItemInfo to = 3;
|
||||
// Extra metadata for this item
|
||||
optional ReportItemMetadata metadata = 4;
|
||||
}
|
||||
|
||||
// Progress info for a section or function
|
||||
message ChangeItemInfo {
|
||||
// The overall match percent for this item
|
||||
float fuzzy_match_percent = 1;
|
||||
// The size of the item in bytes
|
||||
uint64 size = 2;
|
||||
}
|
||||
Binary file not shown.
@@ -2,6 +2,18 @@ syntax = "proto3";
|
||||
|
||||
package objdiff.report;
|
||||
|
||||
// Project progress report
|
||||
message Report {
|
||||
// Overall progress info
|
||||
Measures measures = 1;
|
||||
// Units within this report
|
||||
repeated ReportUnit units = 2;
|
||||
// Report version
|
||||
uint32 version = 3;
|
||||
// Progress categories
|
||||
repeated ReportCategory categories = 4;
|
||||
}
|
||||
|
||||
// Progress info for a report or unit
|
||||
message Measures {
|
||||
// Overall match percent, including partially matched functions and data
|
||||
@@ -38,18 +50,6 @@ message Measures {
|
||||
uint32 complete_units = 16;
|
||||
}
|
||||
|
||||
// Project progress report
|
||||
message Report {
|
||||
// Overall progress info
|
||||
Measures measures = 1;
|
||||
// Units within this report
|
||||
repeated ReportUnit units = 2;
|
||||
// Report version
|
||||
uint32 version = 3;
|
||||
// Progress categories
|
||||
repeated ReportCategory categories = 4;
|
||||
}
|
||||
|
||||
message ReportCategory {
|
||||
// The ID of the category
|
||||
string id = 1;
|
||||
@@ -108,57 +108,3 @@ message ReportItemMetadata {
|
||||
// The virtual address of the function or section
|
||||
optional uint64 virtual_address = 2;
|
||||
}
|
||||
|
||||
// A pair of reports to compare and generate changes
|
||||
message ChangesInput {
|
||||
// The previous report
|
||||
Report from = 1;
|
||||
// The current report
|
||||
Report to = 2;
|
||||
}
|
||||
|
||||
// Changes between two reports
|
||||
message Changes {
|
||||
// The progress info for the previous report
|
||||
Measures from = 1;
|
||||
// The progress info for the current report
|
||||
Measures to = 2;
|
||||
// Units that changed
|
||||
repeated ChangeUnit units = 3;
|
||||
}
|
||||
|
||||
// A changed unit
|
||||
message ChangeUnit {
|
||||
// The name of the unit
|
||||
string name = 1;
|
||||
// The previous progress info (omitted if new)
|
||||
optional Measures from = 2;
|
||||
// The current progress info (omitted if removed)
|
||||
optional Measures to = 3;
|
||||
// Sections that changed
|
||||
repeated ChangeItem sections = 4;
|
||||
// Functions that changed
|
||||
repeated ChangeItem functions = 5;
|
||||
// Extra metadata for this unit
|
||||
optional ReportUnitMetadata metadata = 6;
|
||||
}
|
||||
|
||||
// A changed section or function
|
||||
message ChangeItem {
|
||||
// The name of the item
|
||||
string name = 1;
|
||||
// The previous progress info (omitted if new)
|
||||
optional ChangeItemInfo from = 2;
|
||||
// The current progress info (omitted if removed)
|
||||
optional ChangeItemInfo to = 3;
|
||||
// Extra metadata for this item
|
||||
optional ReportItemMetadata metadata = 4;
|
||||
}
|
||||
|
||||
// Progress info for a section or function
|
||||
message ChangeItemInfo {
|
||||
// The overall match percent for this item
|
||||
float fuzzy_match_percent = 1;
|
||||
// The size of the item in bytes
|
||||
uint64 size = 2;
|
||||
}
|
||||
|
||||
+127
-46
@@ -84,18 +84,11 @@ impl ArchArm {
|
||||
.filter_map(|s| DisasmMode::from_symbol(&s))
|
||||
.collect();
|
||||
mapping_symbols.sort_unstable_by_key(|x| x.address);
|
||||
(s.index().0, mapping_symbols)
|
||||
(s.index().0 - 1, mapping_symbols)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn endian(&self) -> unarm::Endian {
|
||||
match self.endianness {
|
||||
object::Endianness::Little => unarm::Endian::Little,
|
||||
object::Endianness::Big => unarm::Endian::Big,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_flags(&self, diff_config: &DiffObjConfig) -> unarm::ParseFlags {
|
||||
unarm::ParseFlags {
|
||||
ual: diff_config.arm_unified_syntax,
|
||||
@@ -130,6 +123,31 @@ impl ArchArm {
|
||||
code: &[u8],
|
||||
diff_config: &DiffObjConfig,
|
||||
) -> Result<(unarm::Ins, unarm::ParsedIns)> {
|
||||
if ins_ref.opcode == thumb::Opcode::BlH as u16 && ins_ref.size == 4 {
|
||||
// Special case: combined thumb BL instruction
|
||||
let parse_flags = self.parse_flags(diff_config);
|
||||
let first_ins = thumb::Ins {
|
||||
code: match self.endianness {
|
||||
object::Endianness::Little => u16::from_le_bytes([code[0], code[1]]),
|
||||
object::Endianness::Big => u16::from_be_bytes([code[0], code[1]]),
|
||||
} as u32,
|
||||
op: thumb::Opcode::BlH,
|
||||
};
|
||||
let second_ins = thumb::Ins::new(
|
||||
match self.endianness {
|
||||
object::Endianness::Little => u16::from_le_bytes([code[2], code[3]]),
|
||||
object::Endianness::Big => u16::from_be_bytes([code[2], code[3]]),
|
||||
} as u32,
|
||||
&parse_flags,
|
||||
);
|
||||
let first_parsed = first_ins.parse(&parse_flags);
|
||||
let second_parsed = second_ins.parse(&parse_flags);
|
||||
return Ok((
|
||||
unarm::Ins::Thumb(first_ins),
|
||||
first_parsed.combine_thumb_bl(&second_parsed),
|
||||
));
|
||||
}
|
||||
|
||||
let code = match (self.endianness, ins_ref.size) {
|
||||
(object::Endianness::Little, 2) => u16::from_le_bytes([code[0], code[1]]) as u32,
|
||||
(object::Endianness::Little, 4) => {
|
||||
@@ -153,11 +171,7 @@ impl ArchArm {
|
||||
} else {
|
||||
let ins = thumb::Ins { code, op: thumb::Opcode::from(ins_ref.opcode as u8) };
|
||||
let parsed = ins.parse(&self.parse_flags(diff_config));
|
||||
if ins.is_half_bl() {
|
||||
todo!("Combine thumb BL instructions");
|
||||
} else {
|
||||
(unarm::Ins::Thumb(ins), parsed)
|
||||
}
|
||||
(unarm::Ins::Thumb(ins), parsed)
|
||||
};
|
||||
Ok((ins, parsed_ins))
|
||||
}
|
||||
@@ -185,60 +199,127 @@ impl Arch for ArchArm {
|
||||
let first_mapping_idx = mapping_symbols
|
||||
.binary_search_by_key(&start_addr, |x| x.address)
|
||||
.unwrap_or_else(|idx| idx - 1);
|
||||
let first_mapping = mapping_symbols[first_mapping_idx].mapping;
|
||||
let mut mode = mapping_symbols[first_mapping_idx].mapping;
|
||||
|
||||
let mut mappings_iter =
|
||||
mapping_symbols.iter().skip(first_mapping_idx + 1).take_while(|x| x.address < end_addr);
|
||||
let mut mappings_iter = mapping_symbols
|
||||
.iter()
|
||||
.copied()
|
||||
.skip(first_mapping_idx + 1)
|
||||
.take_while(|x| x.address < end_addr);
|
||||
let mut next_mapping = mappings_iter.next();
|
||||
|
||||
let ins_count = code.len() / first_mapping.instruction_size(start_addr);
|
||||
let ins_count = code.len() / mode.instruction_size(start_addr);
|
||||
let mut ops = Vec::<ScannedInstruction>::with_capacity(ins_count);
|
||||
|
||||
let endian = self.endian();
|
||||
let parse_flags = self.parse_flags(diff_config);
|
||||
let mut parser = unarm::Parser::new(first_mapping, start_addr, endian, parse_flags, code);
|
||||
|
||||
while let Some((address, ins, _parsed_ins)) = parser.next() {
|
||||
let size = parser.mode.instruction_size(address);
|
||||
if let Some(next) = next_mapping {
|
||||
let next_address = parser.address;
|
||||
if next_address >= next.address {
|
||||
// Change mapping
|
||||
parser.mode = next.mapping;
|
||||
next_mapping = mappings_iter.next();
|
||||
}
|
||||
let mut address = start_addr;
|
||||
while address < end_addr {
|
||||
while let Some(next) = next_mapping.take_if(|x| address >= x.address) {
|
||||
// Change mapping
|
||||
mode = next.mapping;
|
||||
next_mapping = mappings_iter.next();
|
||||
}
|
||||
let (opcode, branch_dest) = match ins {
|
||||
unarm::Ins::Arm(x) => {
|
||||
let opcode = x.op as u16 | (1 << 15);
|
||||
let branch_dest = match x.op {
|
||||
|
||||
let mut ins_size = mode.instruction_size(address);
|
||||
let data = &code[(address - start_addr) as usize..];
|
||||
if data.len() < ins_size {
|
||||
// Push the remainder as data
|
||||
ops.push(ScannedInstruction {
|
||||
ins_ref: InstructionRef {
|
||||
address: address as u64,
|
||||
size: data.len() as u8,
|
||||
opcode: u16::MAX,
|
||||
},
|
||||
branch_dest: None,
|
||||
});
|
||||
break;
|
||||
}
|
||||
let code = match (self.endianness, ins_size) {
|
||||
(object::Endianness::Little, 2) => u16::from_le_bytes([data[0], data[1]]) as u32,
|
||||
(object::Endianness::Little, 4) => {
|
||||
u32::from_le_bytes([data[0], data[1], data[2], data[3]])
|
||||
}
|
||||
(object::Endianness::Big, 2) => u16::from_be_bytes([data[0], data[1]]) as u32,
|
||||
(object::Endianness::Big, 4) => {
|
||||
u32::from_be_bytes([data[0], data[1], data[2], data[3]])
|
||||
}
|
||||
_ => {
|
||||
// Invalid instruction size
|
||||
ops.push(ScannedInstruction {
|
||||
ins_ref: InstructionRef {
|
||||
address: address as u64,
|
||||
size: ins_size as u8,
|
||||
opcode: u16::MAX,
|
||||
},
|
||||
branch_dest: None,
|
||||
});
|
||||
address += ins_size as u32;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let (opcode, branch_dest) = match mode {
|
||||
unarm::ParseMode::Arm => {
|
||||
let ins = arm::Ins::new(code, &parse_flags);
|
||||
let opcode = ins.op as u16 | (1 << 15);
|
||||
let branch_dest = match ins.op {
|
||||
arm::Opcode::B | arm::Opcode::Bl => {
|
||||
address.checked_add_signed(x.field_branch_offset())
|
||||
address.checked_add_signed(ins.field_branch_offset())
|
||||
}
|
||||
arm::Opcode::BlxI => address.checked_add_signed(x.field_blx_offset()),
|
||||
arm::Opcode::BlxI => address.checked_add_signed(ins.field_blx_offset()),
|
||||
_ => None,
|
||||
};
|
||||
(opcode, branch_dest)
|
||||
}
|
||||
unarm::Ins::Thumb(x) => {
|
||||
let opcode = x.op as u16;
|
||||
let branch_dest = match x.op {
|
||||
unarm::ParseMode::Thumb => {
|
||||
let ins = thumb::Ins::new(code, &parse_flags);
|
||||
let opcode = ins.op as u16;
|
||||
let branch_dest = match ins.op {
|
||||
thumb::Opcode::B | thumb::Opcode::Bl => {
|
||||
address.checked_add_signed(x.field_branch_offset_8())
|
||||
address.checked_add_signed(ins.field_branch_offset_8())
|
||||
}
|
||||
thumb::Opcode::BlH if data.len() >= 4 => {
|
||||
// Combine BL instructions
|
||||
let second_ins = thumb::Ins::new(
|
||||
match self.endianness {
|
||||
object::Endianness::Little => {
|
||||
u16::from_le_bytes([data[2], data[3]]) as u32
|
||||
}
|
||||
object::Endianness::Big => {
|
||||
u16::from_be_bytes([data[2], data[3]]) as u32
|
||||
}
|
||||
},
|
||||
&parse_flags,
|
||||
);
|
||||
if let Some(low) = match second_ins.op {
|
||||
thumb::Opcode::Bl => Some(second_ins.field_low_branch_offset_11()),
|
||||
thumb::Opcode::BlxI => Some(second_ins.field_low_blx_offset_11()),
|
||||
_ => None,
|
||||
} {
|
||||
ins_size = 4;
|
||||
address.checked_add_signed(
|
||||
(ins.field_high_branch_offset_11() + (low as i32)) << 9 >> 9,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
thumb::Opcode::BLong => {
|
||||
address.checked_add_signed(x.field_branch_offset_11())
|
||||
address.checked_add_signed(ins.field_branch_offset_11())
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
(opcode, branch_dest)
|
||||
}
|
||||
unarm::Ins::Data => (u16::MAX, None),
|
||||
unarm::ParseMode::Data => (u16::MAX, None),
|
||||
};
|
||||
|
||||
ops.push(ScannedInstruction {
|
||||
ins_ref: InstructionRef { address: address as u64, size: size as u8, opcode },
|
||||
ins_ref: InstructionRef { address: address as u64, size: ins_size as u8, opcode },
|
||||
branch_dest: branch_dest.map(|x| x as u64),
|
||||
});
|
||||
address += ins_size as u32;
|
||||
}
|
||||
|
||||
Ok(ops)
|
||||
@@ -391,7 +472,7 @@ fn push_args(
|
||||
let reloc_arg = find_reloc_arg(parsed_ins, relocation);
|
||||
let mut writeback = false;
|
||||
let mut deref = false;
|
||||
for (i, arg) in parsed_ins.args_iter().enumerate() {
|
||||
for (i, &arg) in parsed_ins.args_iter().enumerate() {
|
||||
// Emit punctuation before separator
|
||||
if deref {
|
||||
match arg {
|
||||
@@ -463,19 +544,19 @@ fn push_args(
|
||||
| args::Argument::CoOpcode(value)
|
||||
| args::Argument::SatImm(value) => {
|
||||
arg_cb(InstructionPart::basic("#"))?;
|
||||
arg_cb(InstructionPart::unsigned(*value))?;
|
||||
arg_cb(InstructionPart::unsigned(value))?;
|
||||
}
|
||||
args::Argument::SImm(value)
|
||||
| args::Argument::OffsetImm(args::OffsetImm { post_indexed: _, value }) => {
|
||||
arg_cb(InstructionPart::basic("#"))?;
|
||||
arg_cb(InstructionPart::signed(*value))?;
|
||||
arg_cb(InstructionPart::signed(value))?;
|
||||
}
|
||||
args::Argument::BranchDest(value) => {
|
||||
arg_cb(InstructionPart::branch_dest(cur_addr.wrapping_add_signed(*value)))?;
|
||||
arg_cb(InstructionPart::branch_dest(cur_addr.wrapping_add_signed(value)))?;
|
||||
}
|
||||
args::Argument::CoOption(value) => {
|
||||
arg_cb(InstructionPart::basic("{"))?;
|
||||
arg_cb(InstructionPart::unsigned(*value))?;
|
||||
arg_cb(InstructionPart::unsigned(value))?;
|
||||
arg_cb(InstructionPart::basic("}"))?;
|
||||
}
|
||||
args::Argument::CoprocNum(value) => {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
use alloc::{collections::BTreeMap, string::ToString, vec::Vec};
|
||||
use alloc::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use core::ops::Range;
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
@@ -15,7 +19,7 @@ use crate::{
|
||||
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory, display::InstructionPart},
|
||||
obj::{
|
||||
InstructionArg, InstructionArgValue, InstructionRef, Relocation, RelocationFlags,
|
||||
ResolvedInstructionRef, ResolvedRelocation, ScannedInstruction,
|
||||
ResolvedInstructionRef, ResolvedRelocation, ScannedInstruction, SymbolFlag, SymbolFlagSet,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -26,6 +30,7 @@ pub struct ArchMips {
|
||||
pub isa_extension: Option<IsaExtension>,
|
||||
pub ri_gp_value: i32,
|
||||
pub paired_relocations: Vec<BTreeMap<u64, i64>>,
|
||||
pub ignored_symbols: BTreeSet<usize>,
|
||||
}
|
||||
|
||||
const EF_MIPS_ABI: u32 = 0x0000F000;
|
||||
@@ -118,7 +123,25 @@ impl ArchMips {
|
||||
paired_relocations[section_index] = addends;
|
||||
}
|
||||
|
||||
Ok(Self { endianness, abi, isa_extension, ri_gp_value, paired_relocations })
|
||||
let mut ignored_symbols = BTreeSet::new();
|
||||
for obj_symbol in object.symbols() {
|
||||
let Ok(name) = obj_symbol.name() else { continue };
|
||||
if let Some(prefix) = name.strip_suffix(".NON_MATCHING") {
|
||||
ignored_symbols.insert(obj_symbol.index().0);
|
||||
if let Some(target_symbol) = object.symbol_by_name(prefix) {
|
||||
ignored_symbols.insert(target_symbol.index().0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
endianness,
|
||||
abi,
|
||||
isa_extension,
|
||||
ri_gp_value,
|
||||
paired_relocations,
|
||||
ignored_symbols,
|
||||
})
|
||||
}
|
||||
|
||||
fn instruction_flags(&self, diff_config: &DiffObjConfig) -> rabbitizer::InstructionFlags {
|
||||
@@ -263,6 +286,13 @@ impl Arch for ArchMips {
|
||||
})
|
||||
}
|
||||
|
||||
fn demangle(&self, name: &str) -> Option<String> {
|
||||
cpp_demangle::Symbol::new(name)
|
||||
.ok()
|
||||
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
|
||||
.or_else(|| cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default()))
|
||||
}
|
||||
|
||||
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
||||
match flags {
|
||||
RelocationFlags::Elf(r_type) => match r_type {
|
||||
@@ -294,6 +324,14 @@ impl Arch for ArchMips {
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn extra_symbol_flags(&self, symbol: &object::Symbol) -> SymbolFlagSet {
|
||||
let mut flags = SymbolFlagSet::default();
|
||||
if self.ignored_symbols.contains(&symbol.index().0) {
|
||||
flags |= SymbolFlag::Ignored;
|
||||
}
|
||||
flags
|
||||
}
|
||||
}
|
||||
|
||||
fn push_args(
|
||||
|
||||
+141
-44
@@ -15,17 +15,36 @@ use crate::{
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ArchX86 {
|
||||
bits: u32,
|
||||
arch: Architecture,
|
||||
endianness: object::Endianness,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Architecture {
|
||||
X86,
|
||||
X86_64,
|
||||
}
|
||||
|
||||
impl ArchX86 {
|
||||
pub fn new(object: &object::File) -> Result<Self> {
|
||||
Ok(Self { bits: if object.is_64() { 64 } else { 32 }, endianness: object.endianness() })
|
||||
let arch = match object.architecture() {
|
||||
object::Architecture::I386 => Architecture::X86,
|
||||
object::Architecture::X86_64 => Architecture::X86_64,
|
||||
_ => bail!("Unsupported architecture for ArchX86: {:?}", object.architecture()),
|
||||
};
|
||||
Ok(Self { arch, endianness: object.endianness() })
|
||||
}
|
||||
|
||||
fn decoder<'a>(&self, code: &'a [u8], address: u64) -> Decoder<'a> {
|
||||
Decoder::with_ip(self.bits, code, address, DecoderOptions::NONE)
|
||||
Decoder::with_ip(
|
||||
match self.arch {
|
||||
Architecture::X86 => 32,
|
||||
Architecture::X86_64 => 64,
|
||||
},
|
||||
code,
|
||||
address,
|
||||
DecoderOptions::NONE,
|
||||
)
|
||||
}
|
||||
|
||||
fn formatter(&self, diff_config: &DiffObjConfig) -> Box<dyn iced_x86::Formatter> {
|
||||
@@ -38,6 +57,27 @@ impl ArchX86 {
|
||||
formatter.options_mut().set_space_after_operand_separator(diff_config.space_between_args);
|
||||
formatter
|
||||
}
|
||||
|
||||
fn reloc_size(&self, flags: RelocationFlags) -> Option<usize> {
|
||||
match self.arch {
|
||||
Architecture::X86 => match flags {
|
||||
RelocationFlags::Coff(typ) => match typ {
|
||||
pe::IMAGE_REL_I386_DIR16 | pe::IMAGE_REL_I386_REL16 => Some(2),
|
||||
pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32 => Some(4),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
Architecture::X86_64 => match flags {
|
||||
RelocationFlags::Coff(typ) => match typ {
|
||||
pe::IMAGE_REL_AMD64_ADDR32NB | pe::IMAGE_REL_AMD64_REL32 => Some(4),
|
||||
pe::IMAGE_REL_AMD64_ADDR64 => Some(8),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Arch for ArchX86 {
|
||||
@@ -88,17 +128,16 @@ impl Arch for ArchX86 {
|
||||
// memory operand.
|
||||
let mut reloc_replace = None;
|
||||
if let Some(reloc) = resolved.relocation {
|
||||
const PLACEHOLDER: u64 = 0x7BDE3E7D; // chosen by fair dice roll.
|
||||
// guaranteed to be random.
|
||||
const PLACEHOLDER: u64 = 0x7BDE3E7D; // chosen by fair dice roll. guaranteed to be random.
|
||||
let reloc_offset = reloc.relocation.address - resolved.ins_ref.address;
|
||||
let reloc_size = reloc_size(reloc.relocation.flags).unwrap_or(usize::MAX);
|
||||
let reloc_size = self.reloc_size(reloc.relocation.flags).unwrap_or(usize::MAX);
|
||||
let offsets = decoder.get_constant_offsets(&instruction);
|
||||
if reloc_offset == offsets.displacement_offset() as u64
|
||||
&& reloc_size == offsets.displacement_size()
|
||||
{
|
||||
instruction.set_memory_displacement64(PLACEHOLDER);
|
||||
// Formatter always writes the displacement as Int32
|
||||
reloc_replace = Some((OpKind::Memory, NumberKind::Int32, PLACEHOLDER));
|
||||
reloc_replace = Some((OpKind::Memory, 4, PLACEHOLDER));
|
||||
} else if reloc_offset == offsets.immediate_offset() as u64
|
||||
&& reloc_size == offsets.immediate_size()
|
||||
{
|
||||
@@ -116,18 +155,12 @@ impl Arch for ArchX86 {
|
||||
_ => OpKind::default(),
|
||||
}
|
||||
};
|
||||
let number_kind = match reloc_size {
|
||||
2 => NumberKind::UInt16,
|
||||
4 => NumberKind::UInt32,
|
||||
8 => NumberKind::UInt64,
|
||||
_ => NumberKind::default(),
|
||||
};
|
||||
if is_branch {
|
||||
instruction.set_near_branch64(PLACEHOLDER);
|
||||
} else {
|
||||
instruction.set_immediate32(PLACEHOLDER as u32);
|
||||
}
|
||||
reloc_replace = Some((op_kind, number_kind, PLACEHOLDER));
|
||||
reloc_replace = Some((op_kind, reloc_size, PLACEHOLDER));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,12 +181,28 @@ impl Arch for ArchX86 {
|
||||
_relocation: &object::Relocation,
|
||||
flags: RelocationFlags,
|
||||
) -> Result<i64> {
|
||||
match flags {
|
||||
RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32) => {
|
||||
let data = section.data()?[address as usize..address as usize + 4].try_into()?;
|
||||
Ok(self.endianness.read_i32_bytes(data) as i64)
|
||||
}
|
||||
flags => bail!("Unsupported x86 implicit relocation {flags:?}"),
|
||||
match self.arch {
|
||||
Architecture::X86 => match flags {
|
||||
RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32) => {
|
||||
let data =
|
||||
section.data()?[address as usize..address as usize + 4].try_into()?;
|
||||
Ok(self.endianness.read_i32_bytes(data) as i64)
|
||||
}
|
||||
flags => bail!("Unsupported x86 implicit relocation {flags:?}"),
|
||||
},
|
||||
Architecture::X86_64 => match flags {
|
||||
RelocationFlags::Coff(pe::IMAGE_REL_AMD64_ADDR32NB | pe::IMAGE_REL_AMD64_REL32) => {
|
||||
let data =
|
||||
section.data()?[address as usize..address as usize + 4].try_into()?;
|
||||
Ok(self.endianness.read_i32_bytes(data) as i64)
|
||||
}
|
||||
RelocationFlags::Coff(pe::IMAGE_REL_AMD64_ADDR64) => {
|
||||
let data =
|
||||
section.data()?[address as usize..address as usize + 8].try_into()?;
|
||||
Ok(self.endianness.read_i64_bytes(data))
|
||||
}
|
||||
flags => bail!("Unsupported x86-64 implicit relocation {flags:?}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,33 +217,35 @@ impl Arch for ArchX86 {
|
||||
}
|
||||
|
||||
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
||||
match flags {
|
||||
RelocationFlags::Coff(typ) => match typ {
|
||||
pe::IMAGE_REL_I386_DIR32 => Some("IMAGE_REL_I386_DIR32"),
|
||||
pe::IMAGE_REL_I386_REL32 => Some("IMAGE_REL_I386_REL32"),
|
||||
match self.arch {
|
||||
Architecture::X86 => match flags {
|
||||
RelocationFlags::Coff(typ) => match typ {
|
||||
pe::IMAGE_REL_I386_DIR32 => Some("IMAGE_REL_I386_DIR32"),
|
||||
pe::IMAGE_REL_I386_REL32 => Some("IMAGE_REL_I386_REL32"),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
Architecture::X86_64 => match flags {
|
||||
RelocationFlags::Coff(typ) => match typ {
|
||||
pe::IMAGE_REL_AMD64_ADDR64 => Some("IMAGE_REL_AMD64_ADDR64"),
|
||||
pe::IMAGE_REL_AMD64_ADDR32NB => Some("IMAGE_REL_AMD64_ADDR32NB"),
|
||||
pe::IMAGE_REL_AMD64_REL32 => Some("IMAGE_REL_AMD64_REL32"),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn data_reloc_size(&self, flags: RelocationFlags) -> usize { reloc_size(flags).unwrap_or(1) }
|
||||
}
|
||||
|
||||
fn reloc_size(flags: RelocationFlags) -> Option<usize> {
|
||||
match flags {
|
||||
RelocationFlags::Coff(typ) => match typ {
|
||||
pe::IMAGE_REL_I386_DIR16 | pe::IMAGE_REL_I386_REL16 => Some(2),
|
||||
pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32 => Some(4),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
|
||||
self.reloc_size(flags).unwrap_or(1)
|
||||
}
|
||||
}
|
||||
|
||||
struct InstructionFormatterOutput<'a> {
|
||||
cb: &'a mut dyn FnMut(InstructionPart<'_>) -> Result<()>,
|
||||
reloc_replace: Option<(OpKind, NumberKind, u64)>,
|
||||
reloc_replace: Option<(OpKind, usize, u64)>,
|
||||
error: Option<anyhow::Error>,
|
||||
skip_next: bool,
|
||||
}
|
||||
@@ -269,11 +320,18 @@ impl FormatterOutput for InstructionFormatterOutput<'_> {
|
||||
return;
|
||||
}
|
||||
|
||||
if let (Some(operand), Some((target_op_kind, target_number_kind, target_value))) =
|
||||
if let (Some(operand), Some((target_op_kind, reloc_size, target_value))) =
|
||||
(instruction_operand, self.reloc_replace)
|
||||
{
|
||||
#[allow(clippy::match_like_matches_macro)]
|
||||
if instruction.op_kind(operand) == target_op_kind
|
||||
&& number_kind == target_number_kind
|
||||
&& match (number_kind, reloc_size) {
|
||||
(NumberKind::Int8 | NumberKind::UInt8, 1)
|
||||
| (NumberKind::Int16 | NumberKind::UInt16, 2)
|
||||
| (NumberKind::Int32 | NumberKind::UInt32, 4)
|
||||
| (NumberKind::Int64 | NumberKind::UInt64, 8) => true,
|
||||
_ => false,
|
||||
}
|
||||
&& value == target_value
|
||||
{
|
||||
if let Err(e) = (self.cb)(InstructionPart::reloc()) {
|
||||
@@ -343,7 +401,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_scan_instructions() {
|
||||
let arch = ArchX86 { bits: 32, endianness: object::Endianness::Little };
|
||||
let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
|
||||
let code = [
|
||||
0xc7, 0x85, 0x68, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x04, 0x85, 0x00,
|
||||
0x00, 0x00, 0x00,
|
||||
@@ -362,7 +420,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_process_instruction() {
|
||||
let arch = ArchX86 { bits: 32, endianness: object::Endianness::Little };
|
||||
let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
|
||||
let code = [0xc7, 0x85, 0x68, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00];
|
||||
let opcode = iced_x86::Mnemonic::Mov as u16;
|
||||
let mut parts = Vec::new();
|
||||
@@ -398,7 +456,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_process_instruction_with_reloc_1() {
|
||||
let arch = ArchX86 { bits: 32, endianness: object::Endianness::Little };
|
||||
let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
|
||||
let code = [0xc7, 0x85, 0x68, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00];
|
||||
let opcode = iced_x86::Mnemonic::Mov as u16;
|
||||
let mut parts = Vec::new();
|
||||
@@ -443,7 +501,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_process_instruction_with_reloc_2() {
|
||||
let arch = ArchX86 { bits: 32, endianness: object::Endianness::Little };
|
||||
let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
|
||||
let code = [0x8b, 0x04, 0x85, 0x00, 0x00, 0x00, 0x00];
|
||||
let opcode = iced_x86::Mnemonic::Mov as u16;
|
||||
let mut parts = Vec::new();
|
||||
@@ -486,7 +544,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_process_instruction_with_reloc_3() {
|
||||
let arch = ArchX86 { bits: 32, endianness: object::Endianness::Little };
|
||||
let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
|
||||
let code = [0xe8, 0x00, 0x00, 0x00, 0x00];
|
||||
let opcode = iced_x86::Mnemonic::Call as u16;
|
||||
let mut parts = Vec::new();
|
||||
@@ -514,4 +572,43 @@ mod test {
|
||||
.unwrap();
|
||||
assert_eq!(parts, &[InstructionPart::opcode("call", opcode), InstructionPart::reloc()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_instruction_with_reloc_4() {
|
||||
let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
|
||||
let code = [0x8b, 0x15, 0xa4, 0x21, 0x7e, 0x00];
|
||||
let opcode = iced_x86::Mnemonic::Mov as u16;
|
||||
let mut parts = Vec::new();
|
||||
arch.display_instruction(
|
||||
ResolvedInstructionRef {
|
||||
ins_ref: InstructionRef { address: 0x1234, size: 6, opcode },
|
||||
code: &code,
|
||||
relocation: Some(ResolvedRelocation {
|
||||
relocation: &Relocation {
|
||||
flags: RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32),
|
||||
address: 0x1234 + 2,
|
||||
target_symbol: 0,
|
||||
addend: 0,
|
||||
},
|
||||
symbol: &Default::default(),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
&DiffObjConfig::default(),
|
||||
&mut |part| {
|
||||
parts.push(part.into_static());
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(parts, &[
|
||||
InstructionPart::opcode("mov", opcode),
|
||||
InstructionPart::opaque("edx"),
|
||||
InstructionPart::basic(","),
|
||||
InstructionPart::basic(" "),
|
||||
InstructionPart::basic("["),
|
||||
InstructionPart::reloc(),
|
||||
InstructionPart::basic("]"),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ pub fn diff_code(
|
||||
left_section_idx,
|
||||
diff_config,
|
||||
)?;
|
||||
let right_ops = left_obj.arch.scan_instructions(
|
||||
let right_ops = right_obj.arch.scan_instructions(
|
||||
right_symbol.address,
|
||||
right_data,
|
||||
right_section_idx,
|
||||
@@ -437,7 +437,7 @@ fn diff_instruction(
|
||||
{
|
||||
// If either the raw code bytes or relocations don't match, process instructions and compare args
|
||||
let left_ins = left_obj.arch.process_instruction(left_resolved, diff_config)?;
|
||||
let right_ins = left_obj.arch.process_instruction(right_resolved, diff_config)?;
|
||||
let right_ins = right_obj.arch.process_instruction(right_resolved, diff_config)?;
|
||||
if left_ins.args.len() != right_ins.args.len() {
|
||||
state.diff_score += PENALTY_REPLACE;
|
||||
return Ok(InstructionDiffResult::new(InstructionDiffKind::Replace));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user