mirror of
https://github.com/encounter/objdiff.git
synced 2025-12-15 16:16:15 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb1d434bbc | ||
|
|
23009bf9a3 | ||
|
|
6fb4bb8855 | ||
| a138dfa907 | |||
|
|
0b95613768 | ||
| 5d4b33a500 | |||
|
|
f2a591356e | ||
| 8d24ec6373 | |||
| 58430d947b | |||
| 1533125f9d | |||
| 5654060dc8 | |||
| 84079c3934 | |||
|
|
48804dc2e3 | ||
|
|
8cfa8b7dab | ||
|
|
93a4d7e55d | ||
|
|
7c424a7966 |
24
.github/workflows/build.yaml
vendored
24
.github/workflows/build.yaml
vendored
@@ -36,9 +36,9 @@ jobs:
|
||||
- name: Cache Rust workspace
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Cargo check
|
||||
run: cargo check --all-targets --all-features
|
||||
run: cargo check --all-targets --all-features --workspace
|
||||
- name: Cargo clippy
|
||||
run: cargo clippy --all-targets --all-features
|
||||
run: cargo clippy --all-targets --all-features --workspace
|
||||
|
||||
fmt:
|
||||
name: Format
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
- name: Cache Rust workspace
|
||||
uses: Swatinem/rust-cache@v2
|
||||
- name: Cargo test
|
||||
run: cargo test --release --features all
|
||||
run: cargo test --release --all-features --workspace
|
||||
|
||||
build-cli:
|
||||
name: Build objdiff-cli
|
||||
@@ -146,13 +146,14 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install uv
|
||||
if: matrix.build == 'zigbuild'
|
||||
uses: astral-sh/setup-uv@v6
|
||||
- name: Install cargo-zigbuild
|
||||
if: matrix.build == 'zigbuild'
|
||||
run: |
|
||||
python3 -m venv .venv
|
||||
. .venv/bin/activate
|
||||
echo PATH=$PATH >> $GITHUB_ENV
|
||||
pip install ziglang==0.13.0.post1 cargo-zigbuild==0.19.8
|
||||
uv tool install cargo-zigbuild==0.20.1 --with-executables-from ziglang==0.15.1
|
||||
echo "CARGO_ZIGBUILD_ZIG_PATH=$(uv tool dir)/cargo-zigbuild/bin/python-zig" >> $GITHUB_ENV
|
||||
- name: Setup Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
@@ -213,13 +214,14 @@ jobs:
|
||||
sudo apt-get -y install ${{ matrix.packages }}
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install uv
|
||||
if: matrix.build == 'zigbuild'
|
||||
uses: astral-sh/setup-uv@v6
|
||||
- name: Install cargo-zigbuild
|
||||
if: matrix.build == 'zigbuild'
|
||||
run: |
|
||||
python3 -m venv .venv
|
||||
. .venv/bin/activate
|
||||
echo PATH=$PATH >> $GITHUB_ENV
|
||||
pip install ziglang==0.13.0.post1 cargo-zigbuild==0.19.8
|
||||
uv tool install cargo-zigbuild==0.20.1 --with-executables-from ziglang==0.15.1
|
||||
echo "CARGO_ZIGBUILD_ZIG_PATH=$(uv tool dir)/cargo-zigbuild/bin/python-zig" >> $GITHUB_ENV
|
||||
- name: Setup Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
|
||||
@@ -24,7 +24,7 @@ repos:
|
||||
description: Run cargo clippy on all project files.
|
||||
language: system
|
||||
entry: cargo
|
||||
args: ["+nightly", "clippy", "--all-targets", "--all-features"]
|
||||
args: ["+nightly", "clippy", "--all-targets", "--all-features", "--workspace"]
|
||||
pass_filenames: false
|
||||
- id: cargo-deny
|
||||
name: cargo deny
|
||||
|
||||
100
Cargo.lock
generated
100
Cargo.lock
generated
@@ -1898,8 +1898,8 @@ dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"log",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-syntax 0.8.5",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -2832,11 +2832,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||
dependencies = [
|
||||
"regex-automata 0.1.10",
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3092,12 +3092,11 @@ checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
version = "0.50.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3436,7 +3435,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-cli"
|
||||
version = "3.0.0"
|
||||
version = "3.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argp",
|
||||
@@ -3459,7 +3458,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-core"
|
||||
version = "3.0.0"
|
||||
version = "3.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arm-attr",
|
||||
@@ -3514,7 +3513,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-gui"
|
||||
version = "3.0.0"
|
||||
version = "3.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argp",
|
||||
@@ -3551,7 +3550,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "objdiff-wasm"
|
||||
version = "3.0.0"
|
||||
version = "3.1.0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"objdiff-core",
|
||||
@@ -3684,12 +3683,6 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "owned_ttf_parser"
|
||||
version = "0.25.1"
|
||||
@@ -4312,17 +4305,8 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.4.9",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
dependencies = [
|
||||
"regex-syntax 0.6.29",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4333,15 +4317,9 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.8.5",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
@@ -5563,14 +5541,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
|
||||
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"regex-automata",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
@@ -5863,9 +5841,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.235.0"
|
||||
version = "0.236.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3bc393c395cb621367ff02d854179882b9a351b4e0c93d1397e6090b53a5c2a"
|
||||
checksum = "724fccfd4f3c24b7e589d333fc0429c68042897a7e8a5f8694f31792471841e7"
|
||||
dependencies = [
|
||||
"leb128fmt",
|
||||
"wasmparser",
|
||||
@@ -5873,9 +5851,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.235.0"
|
||||
version = "0.236.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b055604ba04189d54b8c0ab2c2fc98848f208e103882d5c0b984f045d5ea4d20"
|
||||
checksum = "c909f94a49a8de3365f3c0344f064818f1e369ff1740c5b04f455f85d454768e"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
@@ -5898,9 +5876,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.235.0"
|
||||
version = "0.236.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "161296c618fa2d63f6ed5fffd1112937e803cb9ec71b32b01a76321555660917"
|
||||
checksum = "a9b1e81f3eb254cf7404a82cee6926a4a3ccc5aad80cc3d43608a070c67aa1d7"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"hashbrown",
|
||||
@@ -6726,19 +6704,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.43.0"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a18712ff1ec5bd09da500fe1e91dec11256b310da0ff33f8b4ec92b927cf0c6"
|
||||
checksum = "04bd9ed271234163b18c92783b0d406f08ca32c232e972f207a68c7b324c44bf"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt 0.43.0",
|
||||
"wit-bindgen-rt 0.44.0",
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.43.0"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c53468e077362201de11999c85c07c36e12048a990a3e0d69da2bd61da355d0"
|
||||
checksum = "b4103c7a3e178b75cd8b0b574fa199ed015e8399c9859b003865cc28834b474b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
@@ -6756,18 +6734,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.43.0"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fd734226eac1fd7c450956964e3a9094c9cee65e9dafdf126feef8c0096db65"
|
||||
checksum = "653c85dd7aee6fe6f4bded0d242406deadae9819029ce6f7d258c920c384358a"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.43.0"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "531ebfcec48e56473805285febdb450e270fa75b2dacb92816861d0473b4c15f"
|
||||
checksum = "95d164b3b6fbd2b0dd8b639b1012110c0bc256519a0a6def410d4020fa8ae106"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
@@ -6781,9 +6759,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.43.0"
|
||||
version = "0.44.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7852bf8a9d1ea80884d26b864ddebd7b0c7636697c6ca10f4c6c93945e023966"
|
||||
checksum = "2c9100a5e1ac85e526dcd4ef49c3ff7689e026fa5e56e2a2047fd377fc682e02"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"prettyplease",
|
||||
@@ -6796,9 +6774,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.235.0"
|
||||
version = "0.236.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64a57a11109cc553396f89f3a38a158a97d0b1adaec113bd73e0f64d30fb601f"
|
||||
checksum = "3622959ed7ed6341c38e5aa35af243632534b0a36226852faa802939ce11e00f"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.1",
|
||||
@@ -6840,9 +6818,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.235.0"
|
||||
version = "0.236.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a1f95a87d03a33e259af286b857a95911eb46236a0f726cbaec1227b3dfc67a"
|
||||
checksum = "16e4833a20cd6e85d6abfea0e63a399472d6f88c6262957c17f546879a80ba15"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
|
||||
24
Cargo.toml
24
Cargo.toml
@@ -5,18 +5,28 @@ members = [
|
||||
"objdiff-gui",
|
||||
"objdiff-wasm",
|
||||
]
|
||||
default-members = [
|
||||
"objdiff-cli",
|
||||
"objdiff-core",
|
||||
"objdiff-gui",
|
||||
# Exclude objdiff-wasm by default
|
||||
]
|
||||
resolver = "3"
|
||||
|
||||
[workspace.package]
|
||||
version = "3.1.0"
|
||||
authors = ["Luke Street <luke@street.dev>"]
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/encounter/objdiff"
|
||||
rust-version = "1.88"
|
||||
|
||||
[profile.release-lto]
|
||||
inherits = "release"
|
||||
lto = "fat"
|
||||
strip = "debuginfo"
|
||||
codegen-units = 1
|
||||
|
||||
[workspace.package]
|
||||
version = "3.0.0"
|
||||
authors = ["Luke Street <luke@street.dev>"]
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/encounter/objdiff"
|
||||
rust-version = "1.88"
|
||||
[profile.release-min]
|
||||
inherits = "release-lto"
|
||||
opt-level = "z"
|
||||
|
||||
140
README.md
140
README.md
@@ -7,12 +7,14 @@ A local diffing tool for decompilation projects. Inspired by [decomp.me](https:/
|
||||
|
||||
Features:
|
||||
|
||||
- Compare entire object files: functions and data.
|
||||
- Built-in symbol demangling for C++. (CodeWarrior, Itanium & MSVC)
|
||||
- Automatic rebuild on source file changes.
|
||||
- Project integration via [configuration file](#configuration).
|
||||
- Search and filter all of a project's objects and quickly switch.
|
||||
- Click to highlight all instances of values and registers.
|
||||
- Compare entire object files: functions and data
|
||||
- Built-in C++ symbol demangling (GCC, MSVC, CodeWarrior, Itanium)
|
||||
- Automatic rebuild on source file changes
|
||||
- Project integration via [configuration file](#configuration)
|
||||
- Search and filter objects with quick switching
|
||||
- Click-to-highlight values and registers
|
||||
- Detailed progress reporting (powers [decomp.dev](https://decomp.dev))
|
||||
- WebAssembly API, [web interface](https://github.com/encounter/objdiff-web) and [Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=decomp-dev.objdiff) (WIP)
|
||||
|
||||
Supports:
|
||||
|
||||
@@ -40,7 +42,7 @@ For Linux and macOS, run `chmod +x objdiff-*` to make the binary executable.
|
||||
|
||||
### CLI
|
||||
|
||||
CLI binaries can be found on the [releases page](https://github.com/encounter/objdiff/releases).
|
||||
CLI binaries are available on the [releases page](https://github.com/encounter/objdiff/releases).
|
||||
|
||||
## Screenshots
|
||||
|
||||
@@ -49,33 +51,30 @@ CLI binaries can be found on the [releases page](https://github.com/encounter/ob
|
||||
|
||||
## Usage
|
||||
|
||||
objdiff works by comparing two relocatable object files (`.o`). The objects are expected to have the same relative path
|
||||
from the "target" and "base" directories.
|
||||
objdiff compares two relocatable object files (`.o`). Here's how it works:
|
||||
|
||||
For example, if the target ("expected") object is located at `build/asm/MetroTRK/mslsupp.o` and the base ("actual")
|
||||
object is located at `build/src/MetroTRK/mslsupp.o`, the following configuration would be used:
|
||||
1. **Create an `objdiff.json` configuration file** in your project root (or generate it with your build script).
|
||||
This file lists **all objects in the project** with their target ("expected") and base ("current") paths.
|
||||
|
||||
- Target build directory: `build/asm`
|
||||
- Base build directory: `build/src`
|
||||
- Object: `MetroTRK/mslsupp.o`
|
||||
2. **Load the project** in objdiff.
|
||||
|
||||
objdiff will then execute the build system from the project directory to build both objects:
|
||||
3. **Select an object** from the sidebar to begin diffing.
|
||||
|
||||
```sh
|
||||
$ make build/asm/MetroTRK/mslsupp.o # Only if "Build target object" is enabled
|
||||
$ make build/src/MetroTRK/mslsupp.o
|
||||
```
|
||||
4. **objdiff automatically:**
|
||||
- Executes the build system to compile the base object (from current source code)
|
||||
- Compares the two objects and displays the differences
|
||||
- Watches for source file changes and rebuilds when detected
|
||||
|
||||
The objects will then be compared and the results will be displayed in the UI.
|
||||
The configuration file allows complete flexibility in project structure - your build directories can have any layout as long as the paths are specified correctly.
|
||||
|
||||
See [Configuration](#configuration) for more information.
|
||||
See [Configuration](#configuration) for setup details.
|
||||
|
||||
## Configuration
|
||||
|
||||
While **not required** (most settings can be specified in the UI), projects can add an `objdiff.json` file to configure the tool automatically. The configuration file must be located in
|
||||
Projects can add an `objdiff.json` file to configure the tool automatically. The configuration file must be located in
|
||||
the root project directory.
|
||||
|
||||
If your project has a generator script (e.g. `configure.py`), it's recommended to generate the objdiff configuration
|
||||
If your project has a generator script (e.g. `configure.py`), it's highly recommended to generate the objdiff configuration
|
||||
file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it from being committed.
|
||||
|
||||
```json
|
||||
@@ -90,17 +89,23 @@ file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it
|
||||
"build_base": true,
|
||||
"watch_patterns": [
|
||||
"*.c",
|
||||
"*.cc",
|
||||
"*.cp",
|
||||
"*.cpp",
|
||||
"*.cxx",
|
||||
"*.c++",
|
||||
"*.h",
|
||||
"*.hh",
|
||||
"*.hp",
|
||||
"*.hpp",
|
||||
"*.hxx",
|
||||
"*.h++",
|
||||
"*.pch",
|
||||
"*.pch++",
|
||||
"*.inc",
|
||||
"*.s",
|
||||
"*.S",
|
||||
"*.asm",
|
||||
"*.inc",
|
||||
"*.py",
|
||||
"*.yml",
|
||||
"*.txt",
|
||||
@@ -122,78 +127,69 @@ file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it
|
||||
|
||||
### Schema
|
||||
|
||||
View [config.schema.json](config.schema.json) for all available options. The below list is a summary of the most important options.
|
||||
> [!NOTE]
|
||||
> View [config.schema.json](config.schema.json) for all available options. Below is a summary of the most important options.
|
||||
|
||||
`custom_make` _(optional)_: By default, objdiff will use `make` to build the project.
|
||||
If the project uses a different build system (e.g. `ninja`), specify it here.
|
||||
The build command will be `[custom_make] [custom_args] path/to/object.o`.
|
||||
#### Build Configuration
|
||||
|
||||
`custom_args` _(optional)_: Additional arguments to pass to the build command prior to the object path.
|
||||
**`custom_make`** _(optional, default: `"make"`)_
|
||||
If the project uses a different build system (e.g. `ninja`), specify it here. The build command will be `[custom_make] [custom_args] path/to/object.o`.
|
||||
|
||||
`build_target`: If true, objdiff will tell the build system to build the target objects before diffing (e.g.
|
||||
`make path/to/target.o`).
|
||||
This is useful if the target objects are not built by default or can change based on project configuration or edits
|
||||
to assembly files.
|
||||
Requires the build system to be configured properly.
|
||||
**`custom_args`** _(optional)_
|
||||
Additional arguments to pass to the build command prior to the object path.
|
||||
|
||||
`build_base`: If true, objdiff will tell the build system to build the base objects before diffing (e.g. `make path/to/base.o`).
|
||||
It's unlikely you'll want to disable this, unless you're using an external tool to rebuild the base object on source file changes.
|
||||
**`build_target`** _(default: `false`)_
|
||||
If true, objdiff will build the target objects before diffing (e.g. `make path/to/target.o`). Useful if target objects are not built by default or can change based on project configuration. Requires proper build system configuration.
|
||||
|
||||
`watch_patterns` _(optional)_: A list of glob patterns to watch for changes.
|
||||
([Supported syntax](https://docs.rs/globset/latest/globset/#syntax))
|
||||
If any of these files change, objdiff will automatically rebuild the objects and re-compare them.
|
||||
If not specified, objdiff will use the default patterns listed above.
|
||||
**`build_base`** _(default: `true`)_
|
||||
If true, objdiff will build the base objects before diffing (e.g. `make path/to/base.o`). It's unlikely you'll want to disable this unless using an external tool to rebuild the base object.
|
||||
|
||||
`ignore_patterns` _(optional)_: A list of glob patterns to explicitly ignore when watching for changes.
|
||||
([Supported syntax](https://docs.rs/globset/latest/globset/#syntax))
|
||||
If not specified, objdiff will use the default patterns listed above.
|
||||
#### File Watching
|
||||
|
||||
`units` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
|
||||
**`watch_patterns`** _(optional, default: listed above)_
|
||||
A list of glob patterns to watch for changes ([supported syntax](https://docs.rs/globset/latest/globset/#syntax)). When these files change, objdiff automatically rebuilds and re-compares objects.
|
||||
|
||||
> `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".
|
||||
**`ignore_patterns`** _(optional, default: listed above)_
|
||||
A list of glob patterns to explicitly ignore when watching for changes ([supported syntax](https://docs.rs/globset/latest/globset/#syntax)).
|
||||
|
||||
#### Units (Objects)
|
||||
|
||||
**`units`** _(optional)_
|
||||
If specified, objdiff displays a list of objects in the sidebar for easy navigation. Each unit contains:
|
||||
|
||||
- **`name`** _(optional)_ - The display name in the UI. Defaults to the object's `path`.
|
||||
- **`target_path`** _(optional)_ - Path to the "target" or "expected" object (the **intended result**).
|
||||
- **`base_path`** _(optional)_ - Path to the "base" or "current" object (built from **current source code**). Omit if there is no source object yet.
|
||||
- **`metadata.auto_generated`** _(optional)_ - Hides the object from the sidebar but includes it in progress reports.
|
||||
- **`metadata.complete`** _(optional)_ - Marks the object as "complete" (linked) when `true` or "incomplete" when `false`.
|
||||
|
||||
## Building
|
||||
|
||||
Install Rust via [rustup](https://rustup.rs).
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/encounter/objdiff.git
|
||||
$ cd objdiff
|
||||
$ cargo run --release
|
||||
git clone https://github.com/encounter/objdiff.git
|
||||
cd objdiff
|
||||
cargo run --release
|
||||
```
|
||||
|
||||
Or using `cargo install`.
|
||||
Or install directly with cargo:
|
||||
|
||||
```shell
|
||||
$ cargo install --locked --git https://github.com/encounter/objdiff.git objdiff-gui objdiff-cli
|
||||
cargo install --locked --git https://github.com/encounter/objdiff.git objdiff-gui objdiff-cli
|
||||
```
|
||||
|
||||
The binaries will be installed to `~/.cargo/bin` as `objdiff` and `objdiff-cli`.
|
||||
Binaries will be installed to `~/.cargo/bin` as `objdiff` and `objdiff-cli`.
|
||||
|
||||
## Installing `pre-commit`
|
||||
## Contributing
|
||||
|
||||
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`.
|
||||
Install `pre-commit` to run linting and formatting automatically:
|
||||
|
||||
```shell
|
||||
$ cargo install --locked cargo-deny
|
||||
$ rustup toolchain install nightly
|
||||
$ uv tool install pre-commit
|
||||
$ pre-commit install
|
||||
rustup toolchain install nightly # Required for cargo fmt/clippy
|
||||
cargo install --locked cargo-deny # https://github.com/EmbarkStudios/cargo-deny
|
||||
uv tool install pre-commit # https://docs.astral.sh/uv, or use pipx or pip
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"custom_make": {
|
||||
"type": "string",
|
||||
"description": "By default, objdiff will use make to build the project.\nIf the project uses a different build system (e.g. ninja), specify it here.\nThe build command will be `[custom_make] [custom_args] path/to/object.o`.",
|
||||
"description": "If the project uses a different build system (e.g. ninja), specify it here.\nThe build command will be `[custom_make] [custom_args] path/to/object.o`.",
|
||||
"examples": [
|
||||
"make",
|
||||
"ninja"
|
||||
@@ -41,33 +41,39 @@
|
||||
},
|
||||
"build_target": {
|
||||
"type": "boolean",
|
||||
"description": "If true, objdiff will tell the build system to build the target objects before diffing (e.g. `make path/to/target.o`).\nThis is useful if the target objects are not built by default or can change based on project configuration or edits to assembly files.\nRequires the build system to be configured properly.",
|
||||
"description": "If true, objdiff will build the target objects before diffing (e.g. `make path/to/target.o`).\nUseful if target objects are not built by default or can change based on project configuration.\nRequires proper build system configuration.",
|
||||
"default": false
|
||||
},
|
||||
"build_base": {
|
||||
"type": "boolean",
|
||||
"description": "If true, objdiff will tell the build system to build the base objects before diffing (e.g. `make path/to/base.o`).\nIt's unlikely you'll want to disable this, unless you're using an external tool to rebuild the base object on source file changes.",
|
||||
"description": "If true, objdiff will build the base objects before diffing (e.g. `make path/to/base.o`).\nIt's unlikely you'll want to disable this unless using an external tool to rebuild the base object.",
|
||||
"default": true
|
||||
},
|
||||
"watch_patterns": {
|
||||
"type": "array",
|
||||
"description": "List of glob patterns to watch for changes in the project.\nIf any of these files change, objdiff will automatically rebuild the objects and re-compare them.\nSupported syntax: https://docs.rs/globset/latest/globset/#syntax",
|
||||
"description": "A list of glob patterns to watch for changes.\nWhen these files change, objdiff automatically rebuilds and re-compares objects.\nSupported syntax: https://docs.rs/globset/latest/globset/#syntax",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [
|
||||
"*.c",
|
||||
"*.cc",
|
||||
"*.cp",
|
||||
"*.cpp",
|
||||
"*.cxx",
|
||||
"*.c++",
|
||||
"*.h",
|
||||
"*.hh",
|
||||
"*.hp",
|
||||
"*.hpp",
|
||||
"*.hxx",
|
||||
"*.h++",
|
||||
"*.pch",
|
||||
"*.pch++",
|
||||
"*.inc",
|
||||
"*.s",
|
||||
"*.S",
|
||||
"*.asm",
|
||||
"*.inc",
|
||||
"*.py",
|
||||
"*.yml",
|
||||
"*.txt",
|
||||
@@ -76,7 +82,7 @@
|
||||
},
|
||||
"ignore_patterns": {
|
||||
"type": "array",
|
||||
"description": "List of glob patterns to explicitly ignore when watching for changes.\nFiles matching these patterns will not trigger a rebuild.\nSupported syntax: https://docs.rs/globset/latest/globset/#syntax",
|
||||
"description": "A list of glob patterns to explicitly ignore when watching for changes.\nSupported syntax: https://docs.rs/globset/latest/globset/#syntax",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -113,7 +119,7 @@
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the object in the UI. If not specified, the object's path will be used."
|
||||
"description": "The display name in the UI. Defaults to the object's path."
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
@@ -122,11 +128,11 @@
|
||||
},
|
||||
"target_path": {
|
||||
"type": "string",
|
||||
"description": "Path to the target object from the project root.\nRequired if path is not specified."
|
||||
"description": "Path to the \"target\" or \"expected\" object (the intended result)."
|
||||
},
|
||||
"base_path": {
|
||||
"type": "string",
|
||||
"description": "Path to the base object from the project root.\nRequired if path is not specified."
|
||||
"description": "Path to the \"base\" or \"current\" object (built from current source code).\nOmit if there is no source object yet."
|
||||
},
|
||||
"reverse_fn_order": {
|
||||
"type": "boolean",
|
||||
@@ -201,7 +207,7 @@
|
||||
"properties": {
|
||||
"complete": {
|
||||
"type": "boolean",
|
||||
"description": "Marks the object as \"complete\" (or \"linked\") in the object list.\nThis is useful for marking objects that are fully decompiled. A value of `false` will mark the object as \"incomplete\"."
|
||||
"description": "Marks the object as \"complete\" (linked) when `true` or \"incomplete\" when `false`."
|
||||
},
|
||||
"reverse_fn_order": {
|
||||
"type": "boolean",
|
||||
@@ -221,7 +227,7 @@
|
||||
},
|
||||
"auto_generated": {
|
||||
"type": "boolean",
|
||||
"description": "Hides the object from the object list by default, but still includes it in reports."
|
||||
"description": "Hides the object from the sidebar but includes it in progress reports."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -74,6 +74,7 @@ ignore = [
|
||||
#"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" },
|
||||
{ id = "RUSTSEC-2025-0052", reason = "Unmaintained async-std 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.
|
||||
@@ -241,8 +242,8 @@ allow-git = []
|
||||
[sources.allow-org]
|
||||
# github.com organizations to allow git sources for
|
||||
github = [
|
||||
"enarx", # flagset
|
||||
"encounter",
|
||||
"gimli-rs", # gimli
|
||||
]
|
||||
# gitlab.com organizations to allow git sources for
|
||||
gitlab = []
|
||||
|
||||
@@ -8,8 +8,8 @@ use objdiff_core::{
|
||||
Report, ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
|
||||
},
|
||||
config::path::platform_path,
|
||||
diff, obj,
|
||||
obj::{SectionKind, SymbolFlag},
|
||||
diff,
|
||||
obj::{self, SectionKind, SymbolFlag, SymbolKind},
|
||||
};
|
||||
use prost::Message;
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
@@ -177,14 +177,16 @@ fn report_object(
|
||||
.target_path
|
||||
.as_ref()
|
||||
.map(|p| {
|
||||
obj::read::read(p.as_ref(), diff_config).with_context(|| format!("Failed to open {p}"))
|
||||
obj::read::read(p.as_ref(), diff_config, diff::DiffSide::Target)
|
||||
.with_context(|| format!("Failed to open {p}"))
|
||||
})
|
||||
.transpose()?;
|
||||
let base = object
|
||||
.base_path
|
||||
.as_ref()
|
||||
.map(|p| {
|
||||
obj::read::read(p.as_ref(), diff_config).with_context(|| format!("Failed to open {p}"))
|
||||
obj::read::read(p.as_ref(), diff_config, diff::DiffSide::Base)
|
||||
.with_context(|| format!("Failed to open {p}"))
|
||||
})
|
||||
.transpose()?;
|
||||
let result =
|
||||
@@ -245,6 +247,7 @@ fn report_object(
|
||||
|| symbol.size == 0
|
||||
|| symbol.flags.contains(SymbolFlag::Hidden)
|
||||
|| symbol.flags.contains(SymbolFlag::Ignored)
|
||||
|| symbol.kind == SymbolKind::Section
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use rabbitizer::{
|
||||
|
||||
use crate::{
|
||||
arch::{Arch, RelocationOverride, RelocationOverrideTarget},
|
||||
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory, display::InstructionPart},
|
||||
diff::{DiffObjConfig, DiffSide, MipsAbi, MipsInstrCategory, display::InstructionPart},
|
||||
obj::{
|
||||
InstructionArg, InstructionArgValue, InstructionRef, Relocation, RelocationFlags,
|
||||
ResolvedInstructionRef, ResolvedRelocation, Section, Symbol, SymbolFlag, SymbolFlagSet,
|
||||
@@ -27,6 +27,7 @@ pub struct ArchMips {
|
||||
pub ri_gp_value: i32,
|
||||
pub paired_relocations: Vec<BTreeMap<u64, i64>>,
|
||||
pub ignored_symbols: BTreeSet<usize>,
|
||||
pub diff_side: DiffSide,
|
||||
}
|
||||
|
||||
const EF_MIPS_ABI: u32 = 0x0000F000;
|
||||
@@ -38,7 +39,7 @@ const EF_MIPS_MACH_5900: u32 = 0x00920000;
|
||||
const R_MIPS15_S3: u32 = 119;
|
||||
|
||||
impl ArchMips {
|
||||
pub fn new(object: &object::File) -> Result<Self> {
|
||||
pub fn new(object: &object::File, diff_side: DiffSide) -> Result<Self> {
|
||||
let mut abi = Abi::O32;
|
||||
let mut isa_extension = None;
|
||||
match object.flags() {
|
||||
@@ -124,7 +125,11 @@ impl ArchMips {
|
||||
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) {
|
||||
// Only remove the prefixless symbols if we are on the Base side of the diff,
|
||||
// to allow diffing against target objects that contain `.NON_MATCHING` markers.
|
||||
if diff_side == DiffSide::Base
|
||||
&& let Some(target_symbol) = object.symbol_by_name(prefix)
|
||||
{
|
||||
ignored_symbols.insert(target_symbol.index().0);
|
||||
}
|
||||
}
|
||||
@@ -137,6 +142,7 @@ impl ArchMips {
|
||||
ri_gp_value,
|
||||
paired_relocations,
|
||||
ignored_symbols,
|
||||
diff_side,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ use object::Endian as _;
|
||||
|
||||
use crate::{
|
||||
diff::{
|
||||
DiffObjConfig,
|
||||
DiffObjConfig, DiffSide,
|
||||
display::{ContextItem, HoverItem, InstructionPart},
|
||||
},
|
||||
obj::{
|
||||
@@ -418,15 +418,18 @@ pub trait Arch: Any + Debug + Send + Sync {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_arch(object: &object::File) -> Result<Box<dyn Arch>> {
|
||||
pub fn new_arch(object: &object::File, diff_side: DiffSide) -> Result<Box<dyn Arch>> {
|
||||
use object::Object as _;
|
||||
// Avoid unused warnings on non-mips builds
|
||||
let _ = diff_side;
|
||||
|
||||
Ok(match object.architecture() {
|
||||
#[cfg(feature = "ppc")]
|
||||
object::Architecture::PowerPc | object::Architecture::PowerPc64 => {
|
||||
Box::new(ppc::ArchPpc::new(object)?)
|
||||
}
|
||||
#[cfg(feature = "mips")]
|
||||
object::Architecture::Mips => Box::new(mips::ArchMips::new(object)?),
|
||||
object::Architecture::Mips => Box::new(mips::ArchMips::new(object, diff_side)?),
|
||||
#[cfg(feature = "x86")]
|
||||
object::Architecture::I386 | object::Architecture::X86_64 => {
|
||||
Box::new(x86::ArchX86::new(object)?)
|
||||
|
||||
@@ -21,6 +21,7 @@ use crate::{
|
||||
obj::{
|
||||
FlowAnalysisResult, InstructionRef, Object, Relocation, RelocationFlags,
|
||||
ResolvedInstructionRef, ResolvedRelocation, Section, Symbol, SymbolFlag, SymbolFlagSet,
|
||||
SymbolKind,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -675,7 +676,7 @@ fn make_symbol_ref(symbol: &object::Symbol) -> Result<ExtabSymbolRef> {
|
||||
#[derive(Debug)]
|
||||
struct PoolReference {
|
||||
addr_src_gpr: powerpc::GPR,
|
||||
addr_offset: i16,
|
||||
addr_offset: i64,
|
||||
addr_dst_gpr: Option<powerpc::GPR>,
|
||||
}
|
||||
|
||||
@@ -683,16 +684,20 @@ struct PoolReference {
|
||||
// If so, return information pertaining to where the instruction is getting that address from and
|
||||
// what it's doing with the address (e.g. copying it into another register, adding an offset, etc).
|
||||
fn get_pool_reference_for_inst(
|
||||
opcode: powerpc::Opcode,
|
||||
ins: powerpc::Ins,
|
||||
simplified: &powerpc::ParsedIns,
|
||||
) -> Option<PoolReference> {
|
||||
use powerpc::{Argument, Opcode};
|
||||
let args = &simplified.args;
|
||||
if flow_analysis::guess_data_type_from_load_store_inst_op(opcode).is_some() {
|
||||
if flow_analysis::guess_data_type_from_load_store_inst_op(ins.op).is_some() {
|
||||
match (args[1], args[2]) {
|
||||
(Argument::Offset(offset), Argument::GPR(addr_src_gpr)) => {
|
||||
// e.g. lwz. Immediate offset.
|
||||
Some(PoolReference { addr_src_gpr, addr_offset: offset.0, addr_dst_gpr: None })
|
||||
Some(PoolReference {
|
||||
addr_src_gpr,
|
||||
addr_offset: offset.0 as i64,
|
||||
addr_dst_gpr: None,
|
||||
})
|
||||
}
|
||||
(Argument::GPR(addr_src_gpr), Argument::GPR(_offset_gpr)) => {
|
||||
// e.g. lwzx. The offset is in a register and was likely calculated from an index.
|
||||
@@ -712,17 +717,51 @@ fn get_pool_reference_for_inst(
|
||||
// If either of these match, we also want to return the destination register that the
|
||||
// address is being copied into so that we can detect any future references to that new
|
||||
// register as well.
|
||||
match (opcode, args[0], args[1], args[2]) {
|
||||
match (ins.op, args[0], args[1], args[2]) {
|
||||
(
|
||||
// `addi` or `subi`
|
||||
Opcode::Addi,
|
||||
Argument::GPR(addr_dst_gpr),
|
||||
Argument::GPR(addr_src_gpr),
|
||||
Argument::Simm(simm),
|
||||
) => Some(PoolReference {
|
||||
addr_src_gpr,
|
||||
addr_offset: simm.0,
|
||||
addr_dst_gpr: Some(addr_dst_gpr),
|
||||
}),
|
||||
) => {
|
||||
let offset = if simplified.mnemonic == "addi" { simm.0 } else { -simm.0 };
|
||||
Some(PoolReference {
|
||||
addr_src_gpr,
|
||||
addr_offset: offset as i64,
|
||||
addr_dst_gpr: Some(addr_dst_gpr),
|
||||
})
|
||||
}
|
||||
(
|
||||
// `addis`
|
||||
Opcode::Addis,
|
||||
Argument::GPR(addr_dst_gpr),
|
||||
Argument::GPR(addr_src_gpr),
|
||||
Argument::Uimm(uimm), // Note: `addis` uses UIMM, unlike `addi`, `subi`, and `subis`
|
||||
) => {
|
||||
assert_eq!(simplified.mnemonic, "addis");
|
||||
let offset = (uimm.0 as i64) << 16;
|
||||
Some(PoolReference {
|
||||
addr_src_gpr,
|
||||
addr_offset: offset,
|
||||
addr_dst_gpr: Some(addr_dst_gpr),
|
||||
})
|
||||
}
|
||||
(
|
||||
// `subis`
|
||||
Opcode::Addis,
|
||||
Argument::GPR(addr_dst_gpr),
|
||||
Argument::GPR(addr_src_gpr),
|
||||
Argument::Simm(simm),
|
||||
) => {
|
||||
assert_eq!(simplified.mnemonic, "subis");
|
||||
let offset = (simm.0 as i64) << 16;
|
||||
Some(PoolReference {
|
||||
addr_src_gpr,
|
||||
addr_offset: offset,
|
||||
addr_dst_gpr: Some(addr_dst_gpr),
|
||||
})
|
||||
}
|
||||
(
|
||||
// `mr` or `mr.`
|
||||
Opcode::Or,
|
||||
@@ -777,13 +816,13 @@ fn clear_overwritten_gprs(ins: powerpc::Ins, gpr_pool_relocs: &mut BTreeMap<u8,
|
||||
// Also, if this instruction is accessing the middle of a symbol instead of the start, we add an
|
||||
// addend to indicate that.
|
||||
fn make_fake_pool_reloc(
|
||||
offset: i16,
|
||||
offset: i64,
|
||||
cur_addr: u32,
|
||||
pool_reloc: &Relocation,
|
||||
symbols: &[Symbol],
|
||||
) -> Option<Relocation> {
|
||||
let pool_reloc = resolve_relocation(symbols, pool_reloc);
|
||||
let offset_from_pool = pool_reloc.relocation.addend + offset as i64;
|
||||
let offset_from_pool = pool_reloc.relocation.addend + offset;
|
||||
let target_address = pool_reloc.symbol.address.checked_add_signed(offset_from_pool)?;
|
||||
let target_symbol;
|
||||
let addend;
|
||||
@@ -794,6 +833,7 @@ fn make_fake_pool_reloc(
|
||||
&& s.size > 0
|
||||
&& !s.flags.contains(SymbolFlag::Hidden)
|
||||
&& !s.flags.contains(SymbolFlag::Ignored)
|
||||
&& s.kind != SymbolKind::Section
|
||||
&& (s.address..s.address + s.size).contains(&target_address)
|
||||
})?;
|
||||
addend = target_address.checked_sub(symbols[target_symbol].address)? as i64;
|
||||
@@ -946,7 +986,7 @@ fn generate_fake_pool_relocations_for_function(
|
||||
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
|
||||
}
|
||||
}
|
||||
} else if let Some(pool_ref) = get_pool_reference_for_inst(ins.op, &simplified) {
|
||||
} else if let Some(pool_ref) = get_pool_reference_for_inst(ins, &simplified) {
|
||||
// This instruction doesn't have a real relocation, so it may be a reference to one of
|
||||
// the already-loaded pools.
|
||||
if let Some(pool_reloc) = gpr_pool_relocs.get(&pool_ref.addr_src_gpr.0) {
|
||||
@@ -965,7 +1005,7 @@ fn generate_fake_pool_relocations_for_function(
|
||||
// with the offset within the .data section of an array variable into r21.
|
||||
// Then the body of the loop will `lwzx` one of the array elements from r21.
|
||||
let mut new_reloc = pool_reloc.clone();
|
||||
new_reloc.addend += pool_ref.addr_offset as i64;
|
||||
new_reloc.addend += pool_ref.addr_offset;
|
||||
gpr_pool_relocs.insert(addr_dst_gpr.0, new_reloc);
|
||||
} else {
|
||||
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
|
||||
|
||||
@@ -204,8 +204,9 @@ pub struct ScratchConfig {
|
||||
pub const CONFIG_FILENAMES: [&str; 3] = ["objdiff.json", "objdiff.yml", "objdiff.yaml"];
|
||||
|
||||
pub const DEFAULT_WATCH_PATTERNS: &[&str] = &[
|
||||
"*.c", "*.cp", "*.cpp", "*.cxx", "*.h", "*.hp", "*.hpp", "*.hxx", "*.s", "*.S", "*.asm",
|
||||
"*.inc", "*.py", "*.yml", "*.txt", "*.json",
|
||||
"*.c", "*.cc", "*.cp", "*.cpp", "*.cxx", "*.c++", "*.h", "*.hh", "*.hp", "*.hpp", "*.hxx",
|
||||
"*.h++", "*.pch", "*.pch++", "*.inc", "*.s", "*.S", "*.asm", "*.py", "*.yml", "*.txt",
|
||||
"*.json",
|
||||
];
|
||||
|
||||
pub const DEFAULT_IGNORE_PATTERNS: &[&str] = &["build/**/*"];
|
||||
|
||||
@@ -41,7 +41,13 @@ pub fn no_diff_code(
|
||||
instruction_rows.push(InstructionDiffRow { ins_ref: Some(*i), ..Default::default() });
|
||||
}
|
||||
resolve_branches(&ops, &mut instruction_rows);
|
||||
Ok(SymbolDiff { target_symbol: None, match_percent: None, diff_score: None, instruction_rows })
|
||||
Ok(SymbolDiff {
|
||||
target_symbol: None,
|
||||
match_percent: None,
|
||||
diff_score: None,
|
||||
instruction_rows,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
const PENALTY_IMM_DIFF: u64 = 1;
|
||||
@@ -147,12 +153,14 @@ pub fn diff_code(
|
||||
match_percent: Some(match_percent),
|
||||
diff_score: Some((diff_score, max_score)),
|
||||
instruction_rows: left_rows,
|
||||
..Default::default()
|
||||
},
|
||||
SymbolDiff {
|
||||
target_symbol: Some(left_symbol_idx),
|
||||
match_percent: Some(match_percent),
|
||||
diff_score: Some((diff_score, max_score)),
|
||||
instruction_rows: right_rows,
|
||||
..Default::default()
|
||||
},
|
||||
))
|
||||
}
|
||||
@@ -496,6 +504,14 @@ fn diff_instruction(
|
||||
result.right_args_diff.push(InstructionArgDiffIndex::new(b_diff));
|
||||
}
|
||||
}
|
||||
if result.kind == InstructionDiffKind::None
|
||||
&& left_resolved.code.len() != right_resolved.code.len()
|
||||
{
|
||||
// If everything else matches but the raw code length differs (e.g. x86 instructions
|
||||
// with same disassembly but different encoding), mark as op mismatch
|
||||
result.kind = InstructionDiffKind::OpMismatch;
|
||||
state.diff_score += PENALTY_REG_DIFF;
|
||||
}
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,17 +24,31 @@ pub fn diff_bss_symbol(
|
||||
target_symbol: Some(right_symbol_ref),
|
||||
match_percent: Some(percent),
|
||||
diff_score: None,
|
||||
instruction_rows: vec![],
|
||||
..Default::default()
|
||||
},
|
||||
SymbolDiff {
|
||||
target_symbol: Some(left_symbol_ref),
|
||||
match_percent: Some(percent),
|
||||
diff_score: None,
|
||||
instruction_rows: vec![],
|
||||
..Default::default()
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub fn symbol_name_matches(left_name: &str, right_name: &str) -> bool {
|
||||
// Match Metrowerks symbol$1234 against symbol$2345
|
||||
if let Some((prefix, suffix)) = left_name.split_once('$') {
|
||||
if !suffix.chars().all(char::is_numeric) {
|
||||
return false;
|
||||
}
|
||||
right_name
|
||||
.split_once('$')
|
||||
.is_some_and(|(p, s)| p == prefix && s.chars().all(char::is_numeric))
|
||||
} else {
|
||||
left_name == right_name
|
||||
}
|
||||
}
|
||||
|
||||
fn reloc_eq(
|
||||
left_obj: &Object,
|
||||
right_obj: &Object,
|
||||
@@ -45,8 +59,8 @@ fn reloc_eq(
|
||||
return false;
|
||||
}
|
||||
|
||||
let symbol_name_addend_matches =
|
||||
left.symbol.name == right.symbol.name && left.relocation.addend == right.relocation.addend;
|
||||
let symbol_name_addend_matches = symbol_name_matches(&left.symbol.name, &right.symbol.name)
|
||||
&& left.relocation.addend == right.relocation.addend;
|
||||
match (left.symbol.section, right.symbol.section) {
|
||||
(Some(sl), Some(sr)) => {
|
||||
// Match if section and name+addend or address match
|
||||
@@ -70,7 +84,83 @@ pub fn resolve_relocation<'obj>(
|
||||
ResolvedRelocation { relocation: reloc, symbol }
|
||||
}
|
||||
|
||||
/// Compares relocations contained with a certain data range.
|
||||
/// Compares the bytes within a certain data range.
|
||||
fn diff_data_range(left_data: &[u8], right_data: &[u8]) -> (f32, Vec<DataDiff>, Vec<DataDiff>) {
|
||||
let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data);
|
||||
let bytes_match_ratio = get_diff_ratio(&ops, left_data.len(), right_data.len());
|
||||
|
||||
let mut left_data_diff = Vec::<DataDiff>::new();
|
||||
let mut right_data_diff = Vec::<DataDiff>::new();
|
||||
for op in ops {
|
||||
let (tag, left_range, right_range) = op.as_tag_tuple();
|
||||
let left_len = left_range.len();
|
||||
let right_len = right_range.len();
|
||||
let mut len = left_len.max(right_len);
|
||||
let kind = match tag {
|
||||
similar::DiffTag::Equal => DataDiffKind::None,
|
||||
similar::DiffTag::Delete => DataDiffKind::Delete,
|
||||
similar::DiffTag::Insert => DataDiffKind::Insert,
|
||||
similar::DiffTag::Replace => {
|
||||
// Ensure replacements are equal length
|
||||
len = left_len.min(right_len);
|
||||
DataDiffKind::Replace
|
||||
}
|
||||
};
|
||||
let left_data = &left_data[left_range];
|
||||
let right_data = &right_data[right_range];
|
||||
left_data_diff.push(DataDiff {
|
||||
data: left_data[..len.min(left_data.len())].to_vec(),
|
||||
kind,
|
||||
len,
|
||||
..Default::default()
|
||||
});
|
||||
right_data_diff.push(DataDiff {
|
||||
data: right_data[..len.min(right_data.len())].to_vec(),
|
||||
kind,
|
||||
len,
|
||||
..Default::default()
|
||||
});
|
||||
if kind == DataDiffKind::Replace {
|
||||
match left_len.cmp(&right_len) {
|
||||
Ordering::Less => {
|
||||
let len = right_len - left_len;
|
||||
left_data_diff.push(DataDiff {
|
||||
data: vec![],
|
||||
kind: DataDiffKind::Insert,
|
||||
len,
|
||||
..Default::default()
|
||||
});
|
||||
right_data_diff.push(DataDiff {
|
||||
data: right_data[left_len..right_len].to_vec(),
|
||||
kind: DataDiffKind::Insert,
|
||||
len,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
Ordering::Greater => {
|
||||
let len = left_len - right_len;
|
||||
left_data_diff.push(DataDiff {
|
||||
data: left_data[right_len..left_len].to_vec(),
|
||||
kind: DataDiffKind::Delete,
|
||||
len,
|
||||
..Default::default()
|
||||
});
|
||||
right_data_diff.push(DataDiff {
|
||||
data: vec![],
|
||||
kind: DataDiffKind::Delete,
|
||||
len,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
Ordering::Equal => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(bytes_match_ratio, left_data_diff, right_data_diff)
|
||||
}
|
||||
|
||||
/// Compares relocations contained within a certain data range.
|
||||
fn diff_data_relocs_for_range<'left, 'right>(
|
||||
left_obj: &'left Object,
|
||||
right_obj: &'right Object,
|
||||
@@ -172,76 +262,10 @@ pub fn diff_data_section(
|
||||
.min(right_section.size);
|
||||
let left_data = &left_section.data[..left_max as usize];
|
||||
let right_data = &right_section.data[..right_max as usize];
|
||||
let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data);
|
||||
let match_percent = get_diff_ratio(&ops, left_data.len(), right_data.len()) * 100.0;
|
||||
|
||||
let mut left_data_diff = Vec::<DataDiff>::new();
|
||||
let mut right_data_diff = Vec::<DataDiff>::new();
|
||||
for op in ops {
|
||||
let (tag, left_range, right_range) = op.as_tag_tuple();
|
||||
let left_len = left_range.len();
|
||||
let right_len = right_range.len();
|
||||
let mut len = left_len.max(right_len);
|
||||
let kind = match tag {
|
||||
similar::DiffTag::Equal => DataDiffKind::None,
|
||||
similar::DiffTag::Delete => DataDiffKind::Delete,
|
||||
similar::DiffTag::Insert => DataDiffKind::Insert,
|
||||
similar::DiffTag::Replace => {
|
||||
// Ensure replacements are equal length
|
||||
len = left_len.min(right_len);
|
||||
DataDiffKind::Replace
|
||||
}
|
||||
};
|
||||
let left_data = &left_section.data[left_range];
|
||||
let right_data = &right_section.data[right_range];
|
||||
left_data_diff.push(DataDiff {
|
||||
data: left_data[..len.min(left_data.len())].to_vec(),
|
||||
kind,
|
||||
len,
|
||||
..Default::default()
|
||||
});
|
||||
right_data_diff.push(DataDiff {
|
||||
data: right_data[..len.min(right_data.len())].to_vec(),
|
||||
kind,
|
||||
len,
|
||||
..Default::default()
|
||||
});
|
||||
if kind == DataDiffKind::Replace {
|
||||
match left_len.cmp(&right_len) {
|
||||
Ordering::Less => {
|
||||
let len = right_len - left_len;
|
||||
left_data_diff.push(DataDiff {
|
||||
data: vec![],
|
||||
kind: DataDiffKind::Insert,
|
||||
len,
|
||||
..Default::default()
|
||||
});
|
||||
right_data_diff.push(DataDiff {
|
||||
data: right_data[left_len..right_len].to_vec(),
|
||||
kind: DataDiffKind::Insert,
|
||||
len,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
Ordering::Greater => {
|
||||
let len = left_len - right_len;
|
||||
left_data_diff.push(DataDiff {
|
||||
data: left_data[right_len..left_len].to_vec(),
|
||||
kind: DataDiffKind::Delete,
|
||||
len,
|
||||
..Default::default()
|
||||
});
|
||||
right_data_diff.push(DataDiff {
|
||||
data: vec![],
|
||||
kind: DataDiffKind::Delete,
|
||||
len,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
Ordering::Equal => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
let (bytes_match_ratio, left_data_diff, right_data_diff) =
|
||||
diff_data_range(left_data, right_data);
|
||||
let match_percent = bytes_match_ratio * 100.0;
|
||||
|
||||
let mut left_reloc_diffs = Vec::new();
|
||||
let mut right_reloc_diffs = Vec::new();
|
||||
@@ -300,6 +324,55 @@ pub fn diff_data_section(
|
||||
Ok((left_section_diff, right_section_diff))
|
||||
}
|
||||
|
||||
pub fn no_diff_data_symbol(obj: &Object, symbol_index: usize) -> Result<SymbolDiff> {
|
||||
let symbol = &obj.symbols[symbol_index];
|
||||
let section_idx = symbol.section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
|
||||
let section = &obj.sections[section_idx];
|
||||
|
||||
let start = symbol
|
||||
.address
|
||||
.checked_sub(section.address)
|
||||
.ok_or_else(|| anyhow!("Symbol address out of section bounds"))?;
|
||||
let end = start + symbol.size;
|
||||
if end > section.size {
|
||||
return Err(anyhow!(
|
||||
"Symbol {} size out of section bounds ({} > {})",
|
||||
symbol.name,
|
||||
end,
|
||||
section.size
|
||||
));
|
||||
}
|
||||
let range = start as usize..end as usize;
|
||||
let data = §ion.data[range.clone()];
|
||||
|
||||
let len = symbol.size as usize;
|
||||
let data_diff =
|
||||
vec![DataDiff { data: data.to_vec(), kind: DataDiffKind::None, len, ..Default::default() }];
|
||||
|
||||
let mut reloc_diffs = Vec::new();
|
||||
for reloc in section.relocations.iter() {
|
||||
if !range.contains(&(reloc.address as usize)) {
|
||||
continue;
|
||||
}
|
||||
let reloc_len = obj.arch.data_reloc_size(reloc.flags);
|
||||
let range = reloc.address as usize..reloc.address as usize + reloc_len;
|
||||
reloc_diffs.push(DataRelocationDiff {
|
||||
reloc: reloc.clone(),
|
||||
kind: DataDiffKind::None,
|
||||
range,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(SymbolDiff {
|
||||
target_symbol: None,
|
||||
match_percent: None,
|
||||
diff_score: None,
|
||||
data_diff,
|
||||
data_reloc_diff: reloc_diffs,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn diff_data_symbol(
|
||||
left_obj: &Object,
|
||||
right_obj: &Object,
|
||||
@@ -348,6 +421,9 @@ pub fn diff_data_symbol(
|
||||
let left_data = &left_section.data[left_range.clone()];
|
||||
let right_data = &right_section.data[right_range.clone()];
|
||||
|
||||
let (bytes_match_ratio, left_data_diff, right_data_diff) =
|
||||
diff_data_range(left_data, right_data);
|
||||
|
||||
let reloc_diffs = diff_data_relocs_for_range(
|
||||
left_obj,
|
||||
right_obj,
|
||||
@@ -357,10 +433,9 @@ pub fn diff_data_symbol(
|
||||
right_range,
|
||||
);
|
||||
|
||||
let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data);
|
||||
let bytes_match_ratio = get_diff_ratio(&ops, left_data.len(), right_data.len());
|
||||
|
||||
let mut match_ratio = bytes_match_ratio;
|
||||
let mut left_reloc_diffs = Vec::new();
|
||||
let mut right_reloc_diffs = Vec::new();
|
||||
if !reloc_diffs.is_empty() {
|
||||
let mut total_reloc_bytes = 0;
|
||||
let mut matching_reloc_bytes = 0;
|
||||
@@ -376,6 +451,27 @@ pub fn diff_data_symbol(
|
||||
if diff_kind == DataDiffKind::None {
|
||||
matching_reloc_bytes += reloc_diff_len;
|
||||
}
|
||||
|
||||
if let Some(left_reloc) = left_reloc {
|
||||
let len = left_obj.arch.data_reloc_size(left_reloc.relocation.flags);
|
||||
let range = left_reloc.relocation.address as usize
|
||||
..left_reloc.relocation.address as usize + len;
|
||||
left_reloc_diffs.push(DataRelocationDiff {
|
||||
reloc: left_reloc.relocation.clone(),
|
||||
kind: diff_kind,
|
||||
range,
|
||||
});
|
||||
}
|
||||
if let Some(right_reloc) = right_reloc {
|
||||
let len = right_obj.arch.data_reloc_size(right_reloc.relocation.flags);
|
||||
let range = right_reloc.relocation.address as usize
|
||||
..right_reloc.relocation.address as usize + len;
|
||||
right_reloc_diffs.push(DataRelocationDiff {
|
||||
reloc: right_reloc.relocation.clone(),
|
||||
kind: diff_kind,
|
||||
range,
|
||||
});
|
||||
}
|
||||
}
|
||||
if total_reloc_bytes > 0 {
|
||||
let relocs_match_ratio = matching_reloc_bytes as f32 / total_reloc_bytes as f32;
|
||||
@@ -397,13 +493,17 @@ pub fn diff_data_symbol(
|
||||
target_symbol: Some(right_symbol_idx),
|
||||
match_percent: Some(match_percent),
|
||||
diff_score: None,
|
||||
instruction_rows: vec![],
|
||||
data_diff: left_data_diff,
|
||||
data_reloc_diff: left_reloc_diffs,
|
||||
..Default::default()
|
||||
},
|
||||
SymbolDiff {
|
||||
target_symbol: Some(left_symbol_idx),
|
||||
match_percent: Some(match_percent),
|
||||
diff_score: None,
|
||||
instruction_rows: vec![],
|
||||
data_diff: right_data_diff,
|
||||
data_reloc_diff: right_reloc_diffs,
|
||||
..Default::default()
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
@@ -379,7 +379,9 @@ pub enum HoverItem {
|
||||
}
|
||||
|
||||
pub fn symbol_context(obj: &Object, symbol_index: usize) -> Vec<ContextItem> {
|
||||
let symbol = &obj.symbols[symbol_index];
|
||||
let Some(symbol) = obj.symbols.get(symbol_index) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut out = Vec::new();
|
||||
out.push(ContextItem::Copy { value: symbol.name.clone(), label: None });
|
||||
if let Some(name) = &symbol.demangled_name {
|
||||
@@ -403,7 +405,9 @@ pub fn symbol_hover(
|
||||
addend: i64,
|
||||
override_color: Option<HoverItemColor>,
|
||||
) -> Vec<HoverItem> {
|
||||
let symbol = &obj.symbols[symbol_index];
|
||||
let Some(symbol) = obj.symbols.get(symbol_index) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let addend_str = match addend.cmp(&0i64) {
|
||||
Ordering::Greater => format!("+{addend:x}"),
|
||||
Ordering::Less => format!("-{:x}", -addend),
|
||||
|
||||
@@ -13,7 +13,8 @@ use crate::{
|
||||
code::{diff_code, no_diff_code},
|
||||
data::{
|
||||
diff_bss_section, diff_bss_symbol, diff_data_section, diff_data_symbol,
|
||||
diff_generic_section, no_diff_bss_section, no_diff_data_section,
|
||||
diff_generic_section, no_diff_bss_section, no_diff_data_section, no_diff_data_symbol,
|
||||
symbol_name_matches,
|
||||
},
|
||||
},
|
||||
obj::{InstructionRef, Object, Relocation, SectionKind, Symbol, SymbolFlag},
|
||||
@@ -44,6 +45,8 @@ pub struct SymbolDiff {
|
||||
pub match_percent: Option<f32>,
|
||||
pub diff_score: Option<(u64, u64)>,
|
||||
pub instruction_rows: Vec<InstructionDiffRow>,
|
||||
pub data_diff: Vec<DataDiff>,
|
||||
pub data_reloc_diff: Vec<DataRelocationDiff>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
@@ -163,7 +166,7 @@ impl ObjectDiff {
|
||||
target_symbol: None,
|
||||
match_percent: None,
|
||||
diff_score: None,
|
||||
instruction_rows: vec![],
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
for _ in obj.sections.iter() {
|
||||
@@ -262,7 +265,11 @@ pub fn diff_objs(
|
||||
left_out.symbols[left_symbol_ref] =
|
||||
no_diff_code(left_obj, left_symbol_ref, diff_config)?;
|
||||
}
|
||||
SectionKind::Data | SectionKind::Bss | SectionKind::Common => {
|
||||
SectionKind::Data => {
|
||||
left_out.symbols[left_symbol_ref] =
|
||||
no_diff_data_symbol(left_obj, left_symbol_ref)?;
|
||||
}
|
||||
SectionKind::Bss | SectionKind::Common => {
|
||||
// Nothing needs to be done
|
||||
}
|
||||
SectionKind::Unknown => unreachable!(),
|
||||
@@ -275,7 +282,11 @@ pub fn diff_objs(
|
||||
right_out.symbols[right_symbol_ref] =
|
||||
no_diff_code(right_obj, right_symbol_ref, diff_config)?;
|
||||
}
|
||||
SectionKind::Data | SectionKind::Bss | SectionKind::Common => {
|
||||
SectionKind::Data => {
|
||||
right_out.symbols[right_symbol_ref] =
|
||||
no_diff_data_symbol(right_obj, right_symbol_ref)?;
|
||||
}
|
||||
SectionKind::Bss | SectionKind::Common => {
|
||||
// Nothing needs to be done
|
||||
}
|
||||
SectionKind::Unknown => unreachable!(),
|
||||
@@ -575,47 +586,60 @@ fn matching_symbols(
|
||||
&mut matches,
|
||||
)?;
|
||||
}
|
||||
for (symbol_idx, symbol) in left.symbols.iter().enumerate() {
|
||||
if symbol.size == 0 || symbol.flags.contains(SymbolFlag::Ignored) {
|
||||
continue;
|
||||
}
|
||||
let section_kind = symbol_section_kind(left, symbol);
|
||||
if section_kind == SectionKind::Unknown {
|
||||
continue;
|
||||
}
|
||||
if left_used.contains(&symbol_idx) {
|
||||
continue;
|
||||
}
|
||||
let symbol_match = SymbolMatch {
|
||||
left: Some(symbol_idx),
|
||||
right: find_symbol(right, left, symbol, Some(&right_used)),
|
||||
prev: find_symbol(prev, left, symbol, None),
|
||||
section_kind,
|
||||
};
|
||||
matches.push(symbol_match);
|
||||
if let Some(right) = symbol_match.right {
|
||||
right_used.insert(right);
|
||||
// Do two passes for nameless literals. The first only pairs up perfect matches to ensure
|
||||
// those are correct first, while the second pass catches near matches.
|
||||
for fuzzy_literals in [false, true] {
|
||||
for (symbol_idx, symbol) in left.symbols.iter().enumerate() {
|
||||
if symbol.size == 0 || symbol.flags.contains(SymbolFlag::Ignored) {
|
||||
continue;
|
||||
}
|
||||
let section_kind = symbol_section_kind(left, symbol);
|
||||
if section_kind == SectionKind::Unknown {
|
||||
continue;
|
||||
}
|
||||
if left_used.contains(&symbol_idx) {
|
||||
continue;
|
||||
}
|
||||
let symbol_match = SymbolMatch {
|
||||
left: Some(symbol_idx),
|
||||
right: find_symbol(right, left, symbol_idx, Some(&right_used), fuzzy_literals),
|
||||
prev: find_symbol(prev, left, symbol_idx, None, fuzzy_literals),
|
||||
section_kind,
|
||||
};
|
||||
matches.push(symbol_match);
|
||||
if let Some(right) = symbol_match.right {
|
||||
left_used.insert(symbol_idx);
|
||||
right_used.insert(right);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(right) = right {
|
||||
for (symbol_idx, symbol) in right.symbols.iter().enumerate() {
|
||||
if symbol.size == 0 || symbol.flags.contains(SymbolFlag::Ignored) {
|
||||
continue;
|
||||
// Do two passes for nameless literals. The first only pairs up perfect matches to ensure
|
||||
// those are correct first, while the second pass catches near matches.
|
||||
for fuzzy_literals in [false, true] {
|
||||
for (symbol_idx, symbol) in right.symbols.iter().enumerate() {
|
||||
if symbol.size == 0 || symbol.flags.contains(SymbolFlag::Ignored) {
|
||||
continue;
|
||||
}
|
||||
let section_kind = symbol_section_kind(right, symbol);
|
||||
if section_kind == SectionKind::Unknown {
|
||||
continue;
|
||||
}
|
||||
if right_used.contains(&symbol_idx) {
|
||||
continue;
|
||||
}
|
||||
let symbol_match = SymbolMatch {
|
||||
left: None,
|
||||
right: Some(symbol_idx),
|
||||
prev: find_symbol(prev, right, symbol_idx, None, fuzzy_literals),
|
||||
section_kind,
|
||||
};
|
||||
matches.push(symbol_match);
|
||||
if symbol_match.prev.is_some() {
|
||||
right_used.insert(symbol_idx);
|
||||
}
|
||||
}
|
||||
let section_kind = symbol_section_kind(right, symbol);
|
||||
if section_kind == SectionKind::Unknown {
|
||||
continue;
|
||||
}
|
||||
if right_used.contains(&symbol_idx) {
|
||||
continue;
|
||||
}
|
||||
matches.push(SymbolMatch {
|
||||
left: None,
|
||||
right: Some(symbol_idx),
|
||||
prev: find_symbol(prev, right, symbol, None),
|
||||
section_kind,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(matches)
|
||||
@@ -637,7 +661,10 @@ where
|
||||
|
||||
fn symbol_section<'obj>(obj: &'obj Object, symbol: &Symbol) -> Option<(&'obj str, SectionKind)> {
|
||||
if let Some(section) = symbol.section.and_then(|section_idx| obj.sections.get(section_idx)) {
|
||||
Some((section.name.as_str(), section.kind))
|
||||
// Match x86 .rdata$r against .rdata$rs
|
||||
let section_name =
|
||||
section.name.split_once('$').map_or(section.name.as_str(), |(prefix, _)| prefix);
|
||||
Some((section_name, section.kind))
|
||||
} else if symbol.flags.contains(SymbolFlag::Common) {
|
||||
Some((".comm", SectionKind::Common))
|
||||
} else {
|
||||
@@ -653,52 +680,94 @@ fn symbol_section_kind(obj: &Object, symbol: &Symbol) -> SectionKind {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a symbol is a compiler-generated literal like @1234.
|
||||
fn is_symbol_compiler_generated_literal(symbol: &Symbol) -> bool {
|
||||
if !symbol.name.starts_with('@') {
|
||||
return false;
|
||||
}
|
||||
if !symbol.name[1..].chars().all(char::is_numeric) {
|
||||
// Exclude @stringBase0, @GUARD@, etc.
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn find_symbol(
|
||||
obj: Option<&Object>,
|
||||
in_obj: &Object,
|
||||
in_symbol: &Symbol,
|
||||
in_symbol_idx: usize,
|
||||
used: Option<&BTreeSet<usize>>,
|
||||
fuzzy_literals: bool,
|
||||
) -> Option<usize> {
|
||||
let in_symbol = &in_obj.symbols[in_symbol_idx];
|
||||
let obj = obj?;
|
||||
let (section_name, section_kind) = symbol_section(in_obj, in_symbol)?;
|
||||
|
||||
// Match compiler-generated symbols against each other (e.g. @251 -> @60)
|
||||
// If they are in the same section and have the same value
|
||||
if is_symbol_compiler_generated_literal(in_symbol)
|
||||
&& matches!(section_kind, SectionKind::Data | SectionKind::Bss)
|
||||
{
|
||||
let mut closest_match_symbol_idx = None;
|
||||
let mut closest_match_percent = 0.0;
|
||||
for (symbol_idx, symbol) in unmatched_symbols(obj, used) {
|
||||
let Some(section_index) = symbol.section else {
|
||||
continue;
|
||||
};
|
||||
if obj.sections[section_index].name != section_name {
|
||||
continue;
|
||||
}
|
||||
if !is_symbol_compiler_generated_literal(symbol) {
|
||||
continue;
|
||||
}
|
||||
match section_kind {
|
||||
SectionKind::Data => {
|
||||
// For data, pick the first symbol with exactly matching bytes and relocations.
|
||||
// If no symbols match exactly, and `fuzzy_literals` is true, pick the closest
|
||||
// plausible match instead.
|
||||
if let Ok((left_diff, _right_diff)) =
|
||||
diff_data_symbol(in_obj, obj, in_symbol_idx, symbol_idx)
|
||||
&& let Some(match_percent) = left_diff.match_percent
|
||||
&& (match_percent == 100.0
|
||||
|| (fuzzy_literals
|
||||
&& match_percent >= 50.0
|
||||
&& match_percent > closest_match_percent))
|
||||
{
|
||||
closest_match_symbol_idx = Some(symbol_idx);
|
||||
closest_match_percent = match_percent;
|
||||
if match_percent == 100.0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
SectionKind::Bss => {
|
||||
// For BSS, pick the first symbol that has the exact matching size.
|
||||
if in_symbol.size == symbol.size {
|
||||
closest_match_symbol_idx = Some(symbol_idx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
return closest_match_symbol_idx;
|
||||
}
|
||||
|
||||
// Try to find an exact name match
|
||||
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|(_, symbol)| {
|
||||
symbol.name == in_symbol.name && symbol_section_kind(obj, symbol) == section_kind
|
||||
}) {
|
||||
return Some(symbol_idx);
|
||||
}
|
||||
// Match compiler-generated symbols against each other (e.g. @251 -> @60)
|
||||
// If they are at the same address in the same section
|
||||
if in_symbol.name.starts_with('@')
|
||||
&& matches!(section_kind, SectionKind::Data | SectionKind::Bss)
|
||||
&& let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|(_, symbol)| {
|
||||
let Some(section_index) = symbol.section else {
|
||||
return false;
|
||||
};
|
||||
symbol.name.starts_with('@')
|
||||
&& symbol.address == in_symbol.address
|
||||
&& obj.sections[section_index].name == section_name
|
||||
})
|
||||
{
|
||||
|
||||
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|&(_, symbol)| {
|
||||
symbol_name_matches(&in_symbol.name, &symbol.name)
|
||||
&& symbol_section_kind(obj, symbol) == section_kind
|
||||
&& symbol_section(obj, symbol).is_some_and(|(name, _)| name == section_name)
|
||||
}) {
|
||||
return Some(symbol_idx);
|
||||
}
|
||||
// Match Metrowerks symbol$1234 against symbol$2345
|
||||
if let Some((prefix, suffix)) = in_symbol.name.split_once('$') {
|
||||
if !suffix.chars().all(char::is_numeric) {
|
||||
return None;
|
||||
}
|
||||
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|&(_, symbol)| {
|
||||
if let Some((p, s)) = symbol.name.split_once('$') {
|
||||
prefix == p
|
||||
&& s.chars().all(char::is_numeric)
|
||||
&& symbol_section_kind(obj, symbol) == section_kind
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
return Some(symbol_idx);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
@@ -749,3 +818,11 @@ fn find_section(
|
||||
s.kind == section_kind && s.name == name && !matches.iter().any(|m| m.right == Some(i))
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum DiffSide {
|
||||
/// The target/expected side of the diff.
|
||||
Target,
|
||||
/// The base side of the diff.
|
||||
Base,
|
||||
}
|
||||
|
||||
@@ -170,13 +170,6 @@ pub enum JobResult {
|
||||
CreateScratch(Option<Box<CreateScratchResult>>),
|
||||
}
|
||||
|
||||
fn should_cancel(rx: &Receiver<()>) -> bool {
|
||||
match rx.try_recv() {
|
||||
Ok(_) | Err(TryRecvError::Disconnected) => true,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn start_job(
|
||||
waker: Waker,
|
||||
title: &str,
|
||||
@@ -203,7 +196,6 @@ fn start_job(
|
||||
}
|
||||
});
|
||||
let id = JOB_ID.fetch_add(1, Ordering::Relaxed);
|
||||
// log::info!("Started job {}", id); TODO
|
||||
JobState { id, kind, handle: Some(handle), context, cancel: tx }
|
||||
}
|
||||
|
||||
@@ -228,3 +220,10 @@ fn update_status(
|
||||
context.waker.wake_by_ref();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn should_cancel(rx: &Receiver<()>) -> bool {
|
||||
match rx.try_recv() {
|
||||
Ok(_) | Err(TryRecvError::Disconnected) => true,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use typed_path::Utf8PlatformPathBuf;
|
||||
|
||||
use crate::{
|
||||
build::{BuildConfig, BuildStatus, run_make},
|
||||
diff::{DiffObjConfig, MappingConfig, ObjectDiff, diff_objs},
|
||||
diff::{DiffObjConfig, DiffSide, MappingConfig, ObjectDiff, diff_objs},
|
||||
jobs::{Job, JobContext, JobResult, JobState, start_job, update_status},
|
||||
obj::{Object, read},
|
||||
};
|
||||
@@ -117,7 +117,7 @@ fn run_build(
|
||||
&cancel,
|
||||
)?;
|
||||
step_idx += 1;
|
||||
match read::read(target_path.as_ref(), &config.diff_obj_config) {
|
||||
match read::read(target_path.as_ref(), &config.diff_obj_config, DiffSide::Target) {
|
||||
Ok(obj) => Some(obj),
|
||||
Err(e) => {
|
||||
first_status = BuildStatus {
|
||||
@@ -141,7 +141,7 @@ fn run_build(
|
||||
Some(base_path) if second_status.success => {
|
||||
update_status(context, format!("Loading base {base_path}"), step_idx, total, &cancel)?;
|
||||
step_idx += 1;
|
||||
match read::read(base_path.as_ref(), &config.diff_obj_config) {
|
||||
match read::read(base_path.as_ref(), &config.diff_obj_config, DiffSide::Base) {
|
||||
Ok(obj) => Some(obj),
|
||||
Err(e) => {
|
||||
second_status = BuildStatus {
|
||||
|
||||
@@ -12,10 +12,10 @@ use object::{Object as _, ObjectSection as _, ObjectSymbol as _};
|
||||
|
||||
use crate::{
|
||||
arch::{Arch, RelocationOverride, RelocationOverrideTarget, new_arch},
|
||||
diff::DiffObjConfig,
|
||||
diff::{DiffObjConfig, DiffSide},
|
||||
obj::{
|
||||
FlowAnalysisResult, Object, Relocation, RelocationFlags, Section, SectionData, SectionFlag,
|
||||
SectionKind, Symbol, SymbolFlag, SymbolKind,
|
||||
SectionKind, Symbol, SymbolFlag, SymbolFlagSet, SymbolKind,
|
||||
split_meta::{SPLITMETA_SECTION, SplitMeta},
|
||||
},
|
||||
util::{align_data_slice_to, align_u64_to, read_u16, read_u32},
|
||||
@@ -74,6 +74,14 @@ fn map_symbol(
|
||||
{
|
||||
flags |= SymbolFlag::Hidden;
|
||||
}
|
||||
if file.format() == object::BinaryFormat::Coff
|
||||
&& let Ok(name) = symbol.name()
|
||||
&& (name.starts_with("except_data_")
|
||||
|| name.starts_with("__unwind")
|
||||
|| name.starts_with("__catch"))
|
||||
{
|
||||
flags |= SymbolFlag::Hidden;
|
||||
}
|
||||
|
||||
let kind = match symbol.kind() {
|
||||
object::SymbolKind::Text => SymbolKind::Function,
|
||||
@@ -110,7 +118,7 @@ fn map_symbols(
|
||||
split_meta: Option<&SplitMeta>,
|
||||
) -> Result<(Vec<Symbol>, Vec<usize>)> {
|
||||
let symbol_count = obj_file.symbols().count();
|
||||
let mut symbols = Vec::<Symbol>::with_capacity(symbol_count);
|
||||
let mut symbols = Vec::<Symbol>::with_capacity(symbol_count + obj_file.sections().count());
|
||||
let mut symbol_indices = Vec::<usize>::with_capacity(symbol_count + 1);
|
||||
for obj_symbol in obj_file.symbols() {
|
||||
if symbol_indices.len() <= obj_symbol.index().0 {
|
||||
@@ -127,10 +135,56 @@ fn map_symbols(
|
||||
Ok((symbols, symbol_indices))
|
||||
}
|
||||
|
||||
/// Add an extra fake symbol to the start of each data section in order to allow the user to diff
|
||||
/// all of the data in the section at once by clicking on this fake symbol at the top of the list.
|
||||
fn add_section_symbols(sections: &[Section], symbols: &mut Vec<Symbol>) {
|
||||
for (section_idx, section) in sections.iter().enumerate() {
|
||||
if section.kind != SectionKind::Data {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Instead of naming the fake section symbol after `section.name` (e.g. ".data") we use
|
||||
// `section.id` (e.g. ".data-0") so that it is unique when multiple sections with the same
|
||||
// name exist and it also doesn't conflict with any real section symbols from the object.
|
||||
let name = if section.flags.contains(SectionFlag::Combined) {
|
||||
// For combined sections, `section.id` (e.g. ".data-combined") is inconsistent with
|
||||
// uncombined section IDs, so we add the "-0" suffix to the name to enable proper
|
||||
// pairing when one side had multiple sections combined and the other only had one
|
||||
// section to begin with.
|
||||
format!("[{}-0]", section.name)
|
||||
} else {
|
||||
format!("[{}]", section.id)
|
||||
};
|
||||
|
||||
// `section.size` can include extra padding, so instead prefer using the address that the
|
||||
// last symbol ends at when there are any symbols in the section.
|
||||
let size = symbols
|
||||
.iter()
|
||||
.filter(|s| {
|
||||
s.section == Some(section_idx) && s.kind == SymbolKind::Object && s.size > 0
|
||||
})
|
||||
.map(|s| s.address + s.size)
|
||||
.max()
|
||||
.unwrap_or(section.size);
|
||||
|
||||
symbols.push(Symbol {
|
||||
name,
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size,
|
||||
kind: SymbolKind::Section,
|
||||
section: Some(section_idx),
|
||||
flags: SymbolFlagSet::default() | SymbolFlag::Local,
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// When inferring a symbol's size, we ignore symbols that start with specific prefixes. They are
|
||||
/// usually emitted as branch targets and do not represent the start of a function or object.
|
||||
fn is_local_label(symbol: &Symbol) -> bool {
|
||||
const LABEL_PREFIXES: &[&str] = &[".L", "LAB_"];
|
||||
const LABEL_PREFIXES: &[&str] = &[".L", "LAB_", "switchD_"];
|
||||
symbol.size == 0
|
||||
&& symbol.flags.contains(SymbolFlag::Local)
|
||||
&& LABEL_PREFIXES.iter().any(|p| symbol.name.starts_with(p))
|
||||
@@ -208,7 +262,11 @@ fn infer_symbol_sizes(arch: &dyn Arch, symbols: &mut [Symbol], sections: &[Secti
|
||||
let section = §ions[section_idx];
|
||||
let next_address =
|
||||
next_symbol.map(|s| s.address).unwrap_or_else(|| section.address + section.size);
|
||||
let new_size = if section.kind == SectionKind::Code {
|
||||
let new_size = if symbol.kind == SymbolKind::Section && section.kind == SectionKind::Data {
|
||||
// Data sections already have always-visible section symbols created by objdiff to allow
|
||||
// diffing them, so no need to unhide these.
|
||||
0
|
||||
} else if section.kind == SectionKind::Code {
|
||||
arch.infer_function_size(symbol, section, next_address)?
|
||||
} else {
|
||||
next_address.saturating_sub(symbol.address)
|
||||
@@ -917,21 +975,25 @@ fn do_combine_sections(
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub fn read(obj_path: &std::path::Path, config: &DiffObjConfig) -> Result<Object> {
|
||||
pub fn read(
|
||||
obj_path: &std::path::Path,
|
||||
config: &DiffObjConfig,
|
||||
diff_side: DiffSide,
|
||||
) -> Result<Object> {
|
||||
let (data, timestamp) = {
|
||||
let file = std::fs::File::open(obj_path)?;
|
||||
let timestamp = filetime::FileTime::from_last_modification_time(&file.metadata()?);
|
||||
(unsafe { memmap2::Mmap::map(&file) }?, timestamp)
|
||||
};
|
||||
let mut obj = parse(&data, config)?;
|
||||
let mut obj = parse(&data, config, diff_side)?;
|
||||
obj.path = Some(obj_path.to_path_buf());
|
||||
obj.timestamp = Some(timestamp);
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<Object> {
|
||||
pub fn parse(data: &[u8], config: &DiffObjConfig, diff_side: DiffSide) -> Result<Object> {
|
||||
let obj_file = object::File::parse(data)?;
|
||||
let mut arch = new_arch(&obj_file)?;
|
||||
let mut arch = new_arch(&obj_file, diff_side)?;
|
||||
let split_meta = parse_split_meta(&obj_file)?;
|
||||
let (mut sections, section_indices) =
|
||||
map_sections(arch.as_ref(), &obj_file, split_meta.as_ref())?;
|
||||
@@ -942,6 +1004,7 @@ pub fn parse(data: &[u8], config: &DiffObjConfig) -> Result<Object> {
|
||||
if config.combine_data_sections || config.combine_text_sections {
|
||||
combine_sections(&mut sections, &mut symbols, config)?;
|
||||
}
|
||||
add_section_symbols(§ions, &mut symbols);
|
||||
arch.post_init(§ions, &symbols);
|
||||
let mut obj = Object {
|
||||
arch,
|
||||
|
||||
@@ -6,7 +6,12 @@ mod common;
|
||||
#[cfg(feature = "arm")]
|
||||
fn read_arm() {
|
||||
let diff_config = diff::DiffObjConfig { ..Default::default() };
|
||||
let obj = obj::read::parse(include_object!("data/arm/LinkStateItem.o"), &diff_config).unwrap();
|
||||
let obj = obj::read::parse(
|
||||
include_object!("data/arm/LinkStateItem.o"),
|
||||
&diff_config,
|
||||
diff::DiffSide::Base,
|
||||
)
|
||||
.unwrap();
|
||||
insta::assert_debug_snapshot!(obj);
|
||||
let symbol_idx =
|
||||
obj.symbols.iter().position(|s| s.name == "_ZN13LinkStateItem12OnStateLeaveEi").unwrap();
|
||||
@@ -20,7 +25,9 @@ fn read_arm() {
|
||||
#[cfg(feature = "arm")]
|
||||
fn read_thumb() {
|
||||
let diff_config = diff::DiffObjConfig { ..Default::default() };
|
||||
let obj = obj::read::parse(include_object!("data/arm/thumb.o"), &diff_config).unwrap();
|
||||
let obj =
|
||||
obj::read::parse(include_object!("data/arm/thumb.o"), &diff_config, diff::DiffSide::Base)
|
||||
.unwrap();
|
||||
insta::assert_debug_snapshot!(obj);
|
||||
let symbol_idx = obj
|
||||
.symbols
|
||||
@@ -37,7 +44,12 @@ fn read_thumb() {
|
||||
#[cfg(feature = "arm")]
|
||||
fn combine_text_sections() {
|
||||
let diff_config = diff::DiffObjConfig { combine_text_sections: true, ..Default::default() };
|
||||
let obj = obj::read::parse(include_object!("data/arm/enemy300.o"), &diff_config).unwrap();
|
||||
let obj = obj::read::parse(
|
||||
include_object!("data/arm/enemy300.o"),
|
||||
&diff_config,
|
||||
diff::DiffSide::Base,
|
||||
)
|
||||
.unwrap();
|
||||
let symbol_idx = obj.symbols.iter().position(|s| s.name == "Enemy300Draw").unwrap();
|
||||
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
|
||||
insta::assert_debug_snapshot!(diff.instruction_rows);
|
||||
|
||||
@@ -6,7 +6,9 @@ mod common;
|
||||
#[cfg(feature = "mips")]
|
||||
fn read_mips() {
|
||||
let diff_config = diff::DiffObjConfig { mips_register_prefix: true, ..Default::default() };
|
||||
let obj = obj::read::parse(include_object!("data/mips/main.c.o"), &diff_config).unwrap();
|
||||
let obj =
|
||||
obj::read::parse(include_object!("data/mips/main.c.o"), &diff_config, diff::DiffSide::Base)
|
||||
.unwrap();
|
||||
insta::assert_debug_snapshot!(obj);
|
||||
let symbol_idx = obj.symbols.iter().position(|s| s.name == "ControlEntry").unwrap();
|
||||
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
|
||||
@@ -19,9 +21,19 @@ fn read_mips() {
|
||||
#[cfg(feature = "mips")]
|
||||
fn cross_endian_diff() {
|
||||
let diff_config = diff::DiffObjConfig::default();
|
||||
let obj_be = obj::read::parse(include_object!("data/mips/code_be.o"), &diff_config).unwrap();
|
||||
let obj_be = obj::read::parse(
|
||||
include_object!("data/mips/code_be.o"),
|
||||
&diff_config,
|
||||
diff::DiffSide::Base,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(obj_be.endianness, object::Endianness::Big);
|
||||
let obj_le = obj::read::parse(include_object!("data/mips/code_le.o"), &diff_config).unwrap();
|
||||
let obj_le = obj::read::parse(
|
||||
include_object!("data/mips/code_le.o"),
|
||||
&diff_config,
|
||||
diff::DiffSide::Base,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(obj_le.endianness, object::Endianness::Little);
|
||||
let left_symbol_idx = obj_be.symbols.iter().position(|s| s.name == "func_00000000").unwrap();
|
||||
let right_symbol_idx =
|
||||
@@ -42,6 +54,11 @@ fn cross_endian_diff() {
|
||||
#[cfg(feature = "mips")]
|
||||
fn filter_non_matching() {
|
||||
let diff_config = diff::DiffObjConfig::default();
|
||||
let obj = obj::read::parse(include_object!("data/mips/vw_main.c.o"), &diff_config).unwrap();
|
||||
let obj = obj::read::parse(
|
||||
include_object!("data/mips/vw_main.c.o"),
|
||||
&diff_config,
|
||||
diff::DiffSide::Base,
|
||||
)
|
||||
.unwrap();
|
||||
insta::assert_debug_snapshot!(obj.symbols);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ mod common;
|
||||
#[cfg(feature = "ppc")]
|
||||
fn read_ppc() {
|
||||
let diff_config = diff::DiffObjConfig::default();
|
||||
let obj = obj::read::parse(include_object!("data/ppc/IObj.o"), &diff_config).unwrap();
|
||||
let obj =
|
||||
obj::read::parse(include_object!("data/ppc/IObj.o"), &diff_config, diff::DiffSide::Base)
|
||||
.unwrap();
|
||||
insta::assert_debug_snapshot!(obj);
|
||||
let symbol_idx =
|
||||
obj.symbols.iter().position(|s| s.name == "Type2Text__10SObjectTagFUi").unwrap();
|
||||
@@ -24,7 +26,12 @@ fn read_ppc() {
|
||||
#[cfg(feature = "ppc")]
|
||||
fn read_dwarf1_line_info() {
|
||||
let diff_config = diff::DiffObjConfig::default();
|
||||
let obj = obj::read::parse(include_object!("data/ppc/m_Do_hostIO.o"), &diff_config).unwrap();
|
||||
let obj = obj::read::parse(
|
||||
include_object!("data/ppc/m_Do_hostIO.o"),
|
||||
&diff_config,
|
||||
diff::DiffSide::Base,
|
||||
)
|
||||
.unwrap();
|
||||
let line_infos = obj
|
||||
.sections
|
||||
.iter()
|
||||
@@ -38,7 +45,12 @@ fn read_dwarf1_line_info() {
|
||||
#[cfg(feature = "ppc")]
|
||||
fn read_extab() {
|
||||
let diff_config = diff::DiffObjConfig::default();
|
||||
let obj = obj::read::parse(include_object!("data/ppc/NMWException.o"), &diff_config).unwrap();
|
||||
let obj = obj::read::parse(
|
||||
include_object!("data/ppc/NMWException.o"),
|
||||
&diff_config,
|
||||
diff::DiffSide::Base,
|
||||
)
|
||||
.unwrap();
|
||||
insta::assert_debug_snapshot!(obj);
|
||||
}
|
||||
|
||||
@@ -47,12 +59,18 @@ fn read_extab() {
|
||||
fn diff_ppc() {
|
||||
let diff_config = diff::DiffObjConfig::default();
|
||||
let mapping_config = diff::MappingConfig::default();
|
||||
let target_obj =
|
||||
obj::read::parse(include_object!("data/ppc/CDamageVulnerability_target.o"), &diff_config)
|
||||
.unwrap();
|
||||
let base_obj =
|
||||
obj::read::parse(include_object!("data/ppc/CDamageVulnerability_base.o"), &diff_config)
|
||||
.unwrap();
|
||||
let target_obj = obj::read::parse(
|
||||
include_object!("data/ppc/CDamageVulnerability_target.o"),
|
||||
&diff_config,
|
||||
diff::DiffSide::Target,
|
||||
)
|
||||
.unwrap();
|
||||
let base_obj = obj::read::parse(
|
||||
include_object!("data/ppc/CDamageVulnerability_base.o"),
|
||||
&diff_config,
|
||||
diff::DiffSide::Base,
|
||||
)
|
||||
.unwrap();
|
||||
let diff =
|
||||
diff::diff_objs(Some(&target_obj), Some(&base_obj), None, &diff_config, &mapping_config)
|
||||
.unwrap();
|
||||
@@ -90,7 +108,12 @@ fn diff_ppc() {
|
||||
#[cfg(feature = "ppc")]
|
||||
fn read_vmx128_coff() {
|
||||
let diff_config = diff::DiffObjConfig { combine_data_sections: true, ..Default::default() };
|
||||
let obj = obj::read::parse(include_object!("data/ppc/vmx128.obj"), &diff_config).unwrap();
|
||||
let obj = obj::read::parse(
|
||||
include_object!("data/ppc/vmx128.obj"),
|
||||
&diff_config,
|
||||
diff::DiffSide::Base,
|
||||
)
|
||||
.unwrap();
|
||||
insta::assert_debug_snapshot!(obj);
|
||||
let symbol_idx =
|
||||
obj.symbols.iter().position(|s| s.name == "?FloatingPointExample@@YAXXZ").unwrap();
|
||||
|
||||
@@ -6,7 +6,12 @@ mod common;
|
||||
#[cfg(feature = "x86")]
|
||||
fn read_x86() {
|
||||
let diff_config = diff::DiffObjConfig::default();
|
||||
let obj = obj::read::parse(include_object!("data/x86/staticdebug.obj"), &diff_config).unwrap();
|
||||
let obj = obj::read::parse(
|
||||
include_object!("data/x86/staticdebug.obj"),
|
||||
&diff_config,
|
||||
diff::DiffSide::Base,
|
||||
)
|
||||
.unwrap();
|
||||
insta::assert_debug_snapshot!(obj);
|
||||
let symbol_idx = obj.symbols.iter().position(|s| s.name == "?PrintThing@@YAXXZ").unwrap();
|
||||
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
|
||||
@@ -23,7 +28,9 @@ fn read_x86_combine_sections() {
|
||||
combine_text_sections: true,
|
||||
..Default::default()
|
||||
};
|
||||
let obj = obj::read::parse(include_object!("data/x86/rtest.obj"), &diff_config).unwrap();
|
||||
let obj =
|
||||
obj::read::parse(include_object!("data/x86/rtest.obj"), &diff_config, diff::DiffSide::Base)
|
||||
.unwrap();
|
||||
insta::assert_debug_snapshot!(obj.sections);
|
||||
}
|
||||
|
||||
@@ -31,7 +38,12 @@ fn read_x86_combine_sections() {
|
||||
#[cfg(feature = "x86")]
|
||||
fn read_x86_64() {
|
||||
let diff_config = diff::DiffObjConfig::default();
|
||||
let obj = obj::read::parse(include_object!("data/x86_64/vs2022.o"), &diff_config).unwrap();
|
||||
let obj = obj::read::parse(
|
||||
include_object!("data/x86_64/vs2022.o"),
|
||||
&diff_config,
|
||||
diff::DiffSide::Base,
|
||||
)
|
||||
.unwrap();
|
||||
insta::assert_debug_snapshot!(obj);
|
||||
let symbol_idx =
|
||||
obj.symbols.iter().position(|s| s.name == "?Dot@Vector@@QEAAMPEAU1@@Z").unwrap();
|
||||
@@ -45,7 +57,12 @@ fn read_x86_64() {
|
||||
#[cfg(feature = "x86")]
|
||||
fn display_section_ordering() {
|
||||
let diff_config = diff::DiffObjConfig::default();
|
||||
let obj = obj::read::parse(include_object!("data/x86/basenode.obj"), &diff_config).unwrap();
|
||||
let obj = obj::read::parse(
|
||||
include_object!("data/x86/basenode.obj"),
|
||||
&diff_config,
|
||||
diff::DiffSide::Base,
|
||||
)
|
||||
.unwrap();
|
||||
let obj_diff =
|
||||
diff::diff_objs(Some(&obj), None, None, &diff_config, &diff::MappingConfig::default())
|
||||
.unwrap()
|
||||
@@ -60,7 +77,12 @@ fn display_section_ordering() {
|
||||
#[cfg(feature = "x86")]
|
||||
fn read_x86_jumptable() {
|
||||
let diff_config = diff::DiffObjConfig::default();
|
||||
let obj = obj::read::parse(include_object!("data/x86/jumptable.o"), &diff_config).unwrap();
|
||||
let obj = obj::read::parse(
|
||||
include_object!("data/x86/jumptable.o"),
|
||||
&diff_config,
|
||||
diff::DiffSide::Base,
|
||||
)
|
||||
.unwrap();
|
||||
insta::assert_debug_snapshot!(obj);
|
||||
let symbol_idx = obj.symbols.iter().position(|s| s.name == "?test@@YAHH@Z").unwrap();
|
||||
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
|
||||
@@ -74,6 +96,11 @@ fn read_x86_jumptable() {
|
||||
#[cfg(feature = "x86")]
|
||||
fn read_x86_local_labels() {
|
||||
let diff_config = diff::DiffObjConfig::default();
|
||||
let obj = obj::read::parse(include_object!("data/x86/local_labels.obj"), &diff_config).unwrap();
|
||||
let obj = obj::read::parse(
|
||||
include_object!("data/x86/local_labels.obj"),
|
||||
&diff_config,
|
||||
diff::DiffSide::Base,
|
||||
)
|
||||
.unwrap();
|
||||
insta::assert_debug_snapshot!(obj);
|
||||
}
|
||||
|
||||
@@ -1507,6 +1507,19 @@ Object {
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.data-0]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 76,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
2,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
],
|
||||
sections: [
|
||||
Section {
|
||||
|
||||
@@ -449,4 +449,17 @@ expression: obj.symbols
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.data-0]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 0,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
2,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -10,10 +10,10 @@ expression: output
|
||||
[(Address(20), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglSleep", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||
[(Address(24), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]
|
||||
[(Address(28), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lw", 26), Normal, 10), (Argument(Opaque("$a1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%gp_rel("), Normal, 0), (Symbol(Symbol { name: "WorkEnd", demangled_name: None, address: 64, size: 4, kind: Object, section: Some(8), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$gp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||
[(Address(32), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lui", 20), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%hi("), Normal, 0), (Symbol(Symbol { name: "[.sdata]", demangled_name: None, address: 0, size: 64, kind: Section, section: Some(8), flags: FlagSet(Local), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||
[(Address(32), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lui", 20), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%hi("), Normal, 0), (Symbol(Symbol { name: "[.sdata]", demangled_name: None, address: 0, size: 0, kind: Section, section: Some(8), flags: FlagSet(Local), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||
[(Address(36), Normal, 5), (Spacing(4), Normal, 0), (Opcode("daddu", 97), Normal, 10), (Argument(Opaque("$a2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Eol, Normal, 0)]
|
||||
[(Address(40), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglSoundLoadEffect", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||
[(Address(44), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%lo("), Normal, 0), (Symbol(Symbol { name: "[.sdata]", demangled_name: None, address: 0, size: 64, kind: Section, section: Some(8), flags: FlagSet(Local), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||
[(Address(44), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%lo("), Normal, 0), (Symbol(Symbol { name: "[.sdata]", demangled_name: None, address: 0, size: 0, kind: Section, section: Some(8), flags: FlagSet(Local), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||
[(Address(48), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lui", 20), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%hi("), Normal, 0), (Symbol(Symbol { name: "PacketBottomNewVu1DropMicroCode", demangled_name: None, address: 0, size: 12, kind: Object, section: Some(7), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||
[(Address(52), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lw", 26), Normal, 10), (Argument(Opaque("$a1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%gp_rel("), Normal, 0), (Symbol(Symbol { name: "WorkEnd", demangled_name: None, address: 64, size: 4, kind: Object, section: Some(8), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$gp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||
[(Address(56), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglSoundLoadSwd", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
---
|
||||
source: objdiff-core/tests/arch_mips.rs
|
||||
assertion_line: 10
|
||||
expression: obj
|
||||
---
|
||||
Object {
|
||||
@@ -51,6 +52,7 @@ Object {
|
||||
{},
|
||||
],
|
||||
ignored_symbols: {},
|
||||
diff_side: Base,
|
||||
},
|
||||
endianness: Little,
|
||||
symbols: [
|
||||
@@ -110,7 +112,7 @@ Object {
|
||||
name: "[.sdata]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 64,
|
||||
size: 0,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
8,
|
||||
@@ -671,6 +673,45 @@ Object {
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.data-0]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 0,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
2,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.rodata-0]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 12,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
7,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.sdata-0]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 76,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
8,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
],
|
||||
sections: [
|
||||
Section {
|
||||
|
||||
@@ -2645,6 +2645,8 @@ expression: "(target_symbol_diff, base_symbol_diff)"
|
||||
arg_diff: [],
|
||||
},
|
||||
],
|
||||
data_diff: [],
|
||||
data_reloc_diff: [],
|
||||
},
|
||||
SymbolDiff {
|
||||
target_symbol: Some(
|
||||
@@ -5288,5 +5290,7 @@ expression: "(target_symbol_diff, base_symbol_diff)"
|
||||
arg_diff: [],
|
||||
},
|
||||
],
|
||||
data_diff: [],
|
||||
data_reloc_diff: [],
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
---
|
||||
source: objdiff-core/tests/arch_ppc.rs
|
||||
assertion_line: 70
|
||||
expression: sections_display
|
||||
---
|
||||
[
|
||||
@@ -37,7 +36,7 @@ expression: sections_display
|
||||
),
|
||||
symbols: [
|
||||
SectionDisplaySymbol {
|
||||
symbol: 2,
|
||||
symbol: 16,
|
||||
is_mapping_symbol: false,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -308,6 +308,32 @@ Object {
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[extab-0]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 40,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
1,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[extabindex-0]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 36,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
2,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
],
|
||||
sections: [
|
||||
Section {
|
||||
|
||||
@@ -43,7 +43,7 @@ Object {
|
||||
name: "[.ctors]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 4,
|
||||
size: 0,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
1,
|
||||
@@ -157,6 +157,19 @@ Object {
|
||||
0,
|
||||
),
|
||||
},
|
||||
Symbol {
|
||||
name: "[.ctors-0]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 4,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
1,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
],
|
||||
sections: [
|
||||
Section {
|
||||
|
||||
@@ -1101,6 +1101,45 @@ Object {
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.XBLD$W-0]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 16,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
2,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.rdata-0]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 416,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
4,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.pdata-0]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 40,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
6,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
],
|
||||
sections: [
|
||||
Section {
|
||||
|
||||
@@ -124,6 +124,19 @@ Object {
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.data-0]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 10,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
1,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
],
|
||||
sections: [
|
||||
Section {
|
||||
|
||||
@@ -854,6 +854,201 @@ Object {
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.xdata-0]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 8,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
7,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.pdata-0]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 12,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
8,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.xdata-1]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 8,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
9,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.pdata-1]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 12,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
10,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.xdata-2]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 8,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
11,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.pdata-2]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 12,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
12,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.xdata-3]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 8,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
13,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.pdata-3]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 12,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
14,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.rdata-0]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 256,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
15,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.xdata-4]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 20,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
16,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.pdata-4]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 12,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
17,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.rtc$IMZ-0]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 8,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
19,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.rtc$TMZ-0]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 8,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
20,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.rdata-1]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 4,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
21,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
Symbol {
|
||||
name: "[.rdata-2]",
|
||||
demangled_name: None,
|
||||
address: 0,
|
||||
size: 4,
|
||||
kind: Section,
|
||||
section: Some(
|
||||
22,
|
||||
),
|
||||
flags: FlagSet(Local),
|
||||
align: None,
|
||||
virtual_address: None,
|
||||
},
|
||||
],
|
||||
sections: [
|
||||
Section {
|
||||
|
||||
@@ -115,7 +115,8 @@ fn get_hover_item_color_for_diff_kind(diff_kind: DataDiffKind) -> HoverItemColor
|
||||
pub(crate) fn data_row_ui(
|
||||
ui: &mut egui::Ui,
|
||||
obj: Option<&Object>,
|
||||
address: usize,
|
||||
base_address: usize,
|
||||
row_address: usize,
|
||||
diffs: &[(DataDiff, Vec<DataRelocationDiff>)],
|
||||
appearance: &Appearance,
|
||||
column: usize,
|
||||
@@ -127,7 +128,7 @@ pub(crate) fn data_row_ui(
|
||||
}
|
||||
let mut job = LayoutJob::default();
|
||||
write_text(
|
||||
format!("{address:08x}: ").as_str(),
|
||||
format!("{row_address:08x}: ").as_str(),
|
||||
appearance.text_color,
|
||||
&mut job,
|
||||
appearance.code_font.clone(),
|
||||
@@ -135,7 +136,7 @@ pub(crate) fn data_row_ui(
|
||||
// The offset shown on the side of the GUI, shifted by insertions/deletions.
|
||||
let mut cur_addr = 0usize;
|
||||
// The offset into the actual bytes of the section on this side, ignoring differences.
|
||||
let mut cur_addr_actual = address;
|
||||
let mut cur_addr_actual = base_address + row_address;
|
||||
for (diff, reloc_diffs) in diffs {
|
||||
let base_color = get_color_for_diff_kind(diff.kind, appearance);
|
||||
if diff.data.is_empty() {
|
||||
@@ -211,13 +212,14 @@ pub(crate) fn data_row_ui(
|
||||
pub(crate) fn split_diffs(
|
||||
diffs: &[DataDiff],
|
||||
reloc_diffs: &[DataRelocationDiff],
|
||||
symbol_offset_in_section: usize,
|
||||
) -> Vec<Vec<(DataDiff, Vec<DataRelocationDiff>)>> {
|
||||
let mut split_diffs = Vec::<Vec<(DataDiff, Vec<DataRelocationDiff>)>>::new();
|
||||
let mut row_diffs = Vec::<(DataDiff, Vec<DataRelocationDiff>)>::new();
|
||||
// The offset shown on the side of the GUI, shifted by insertions/deletions.
|
||||
let mut cur_addr = 0usize;
|
||||
// The offset into the actual bytes of the section on this side, ignoring differences.
|
||||
let mut cur_addr_actual = 0usize;
|
||||
let mut cur_addr_actual = symbol_offset_in_section;
|
||||
for diff in diffs {
|
||||
let mut cur_len = 0usize;
|
||||
while cur_len < diff.len {
|
||||
|
||||
@@ -2,10 +2,10 @@ use egui::{Id, Layout, RichText, ScrollArea, TextEdit, Ui, Widget, text::LayoutJ
|
||||
use objdiff_core::{
|
||||
build::BuildStatus,
|
||||
diff::{
|
||||
DiffObjConfig, ObjectDiff, SectionDiff, SymbolDiff,
|
||||
DiffObjConfig, ObjectDiff, SymbolDiff,
|
||||
display::{ContextItem, HoverItem, HoverItemColor, SymbolFilter, SymbolNavigationKind},
|
||||
},
|
||||
obj::{Object, Section, Symbol},
|
||||
obj::{Object, Symbol},
|
||||
};
|
||||
use time::format_description;
|
||||
|
||||
@@ -25,17 +25,10 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum SelectedSymbol {
|
||||
Symbol(usize),
|
||||
Section(usize),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct DiffColumnContext<'a> {
|
||||
status: &'a BuildStatus,
|
||||
obj: Option<&'a (Object, ObjectDiff)>,
|
||||
section: Option<(&'a Section, &'a SectionDiff, usize)>,
|
||||
symbol: Option<(&'a Symbol, &'a SymbolDiff, usize)>,
|
||||
}
|
||||
|
||||
@@ -46,49 +39,28 @@ impl<'a> DiffColumnContext<'a> {
|
||||
obj: Option<&'a (Object, ObjectDiff)>,
|
||||
selected_symbol: Option<&SymbolRefByName>,
|
||||
) -> Self {
|
||||
let selected_symbol = match view {
|
||||
let selected_symbol_idx = match view {
|
||||
View::SymbolDiff => None,
|
||||
View::FunctionDiff | View::ExtabDiff => match (obj, selected_symbol) {
|
||||
(Some(obj), Some(s)) => {
|
||||
obj.0.symbol_by_name(&s.symbol_name).map(SelectedSymbol::Symbol)
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
View::DataDiff => match (obj, selected_symbol) {
|
||||
(Some(obj), Some(SymbolRefByName { section_name: Some(section_name), .. })) => {
|
||||
find_section(&obj.0, section_name).map(SelectedSymbol::Section)
|
||||
}
|
||||
View::FunctionDiff | View::DataDiff | View::ExtabDiff => match (obj, selected_symbol) {
|
||||
(Some(obj), Some(s)) => obj.0.symbol_by_name(&s.symbol_name),
|
||||
_ => None,
|
||||
},
|
||||
};
|
||||
let (section, symbol) = match (obj, selected_symbol) {
|
||||
(Some((obj, obj_diff)), Some(SelectedSymbol::Symbol(symbol_ref))) => {
|
||||
let symbol = match (obj, selected_symbol_idx) {
|
||||
(Some((obj, obj_diff)), Some(symbol_ref)) => {
|
||||
let symbol = &obj.symbols[symbol_ref];
|
||||
(
|
||||
symbol.section.map(|section_idx| {
|
||||
(&obj.sections[section_idx], &obj_diff.sections[section_idx], section_idx)
|
||||
}),
|
||||
Some((symbol, &obj_diff.symbols[symbol_ref], symbol_ref)),
|
||||
)
|
||||
Some((symbol, &obj_diff.symbols[symbol_ref], symbol_ref))
|
||||
}
|
||||
(Some((obj, obj_diff)), Some(SelectedSymbol::Section(section_idx))) => (
|
||||
Some((&obj.sections[section_idx], &obj_diff.sections[section_idx], section_idx)),
|
||||
None,
|
||||
),
|
||||
_ => (None, None),
|
||||
_ => None,
|
||||
};
|
||||
Self { status, obj, section, symbol }
|
||||
Self { status, obj, symbol }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn has_symbol(&self) -> bool { self.section.is_some() || self.symbol.is_some() }
|
||||
pub fn has_symbol(&self) -> bool { self.symbol.is_some() }
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> Option<&str> {
|
||||
self.symbol
|
||||
.map(|(symbol, _, _)| symbol.name.as_str())
|
||||
.or_else(|| self.section.map(|(section, _, _)| section.name.as_str()))
|
||||
}
|
||||
pub fn id(&self) -> Option<&str> { self.symbol.map(|(symbol, _, _)| symbol.name.as_str()) }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
@@ -133,10 +105,7 @@ pub fn diff_view_ui(
|
||||
{
|
||||
navigation.right_symbol = Some(target_symbol_ref);
|
||||
}
|
||||
} else if navigation.left_symbol.is_some()
|
||||
&& left_ctx.obj.is_some()
|
||||
&& left_ctx.section.is_none()
|
||||
{
|
||||
} else if navigation.left_symbol.is_some() && left_ctx.obj.is_some() {
|
||||
// Clear selection if symbol goes missing
|
||||
navigation.left_symbol = None;
|
||||
}
|
||||
@@ -147,10 +116,7 @@ pub fn diff_view_ui(
|
||||
{
|
||||
navigation.left_symbol = Some(target_symbol_ref);
|
||||
}
|
||||
} else if navigation.right_symbol.is_some()
|
||||
&& right_ctx.obj.is_some()
|
||||
&& right_ctx.section.is_none()
|
||||
{
|
||||
} else if navigation.right_symbol.is_some() && right_ctx.obj.is_some() {
|
||||
// Clear selection if symbol goes missing
|
||||
navigation.right_symbol = None;
|
||||
}
|
||||
@@ -225,12 +191,6 @@ pub fn diff_view_ui(
|
||||
{
|
||||
ret = Some(action);
|
||||
}
|
||||
} else if let Some((section, _, _)) = left_ctx.section {
|
||||
ui.label(
|
||||
RichText::new(section.name.clone())
|
||||
.font(appearance.code_font.clone())
|
||||
.color(appearance.highlight_color),
|
||||
);
|
||||
} else if right_ctx.has_symbol() {
|
||||
ui.label(
|
||||
RichText::new("Choose target symbol")
|
||||
@@ -363,12 +323,6 @@ pub fn diff_view_ui(
|
||||
{
|
||||
ret = Some(action);
|
||||
}
|
||||
} else if let Some((section, _, _)) = right_ctx.section {
|
||||
ui.label(
|
||||
RichText::new(section.name.clone())
|
||||
.font(appearance.code_font.clone())
|
||||
.color(appearance.highlight_color),
|
||||
);
|
||||
} else if left_ctx.has_symbol() {
|
||||
ui.label(
|
||||
RichText::new("Choose base symbol")
|
||||
@@ -509,17 +463,17 @@ pub fn diff_view_ui(
|
||||
View::DataDiff,
|
||||
Some((left_obj, _left_diff)),
|
||||
Some((right_obj, _right_diff)),
|
||||
Some((_left_section, left_section_diff, _left_symbol_idx)),
|
||||
Some((_right_section, right_section_diff, _right_symbol_idx)),
|
||||
Some((left_symbol, left_symbol_diff, _left_symbol_idx)),
|
||||
Some((right_symbol, right_symbol_diff, _right_symbol_idx)),
|
||||
) =
|
||||
(state.current_view, left_ctx.obj, right_ctx.obj, left_ctx.section, right_ctx.section)
|
||||
(state.current_view, left_ctx.obj, right_ctx.obj, left_ctx.symbol, right_ctx.symbol)
|
||||
{
|
||||
// Joint diff view
|
||||
hotkeys::check_scroll_hotkeys(ui, true);
|
||||
let left_total_bytes =
|
||||
left_section_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
|
||||
left_symbol_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
|
||||
let right_total_bytes =
|
||||
right_section_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
|
||||
right_symbol_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
|
||||
if left_total_bytes != right_total_bytes {
|
||||
ui.label("Data size mismatch");
|
||||
return;
|
||||
@@ -528,10 +482,16 @@ pub fn diff_view_ui(
|
||||
return;
|
||||
}
|
||||
let total_rows = (left_total_bytes - 1) / BYTES_PER_ROW + 1;
|
||||
let left_diffs =
|
||||
split_diffs(&left_section_diff.data_diff, &left_section_diff.reloc_diff);
|
||||
let right_diffs =
|
||||
split_diffs(&right_section_diff.data_diff, &right_section_diff.reloc_diff);
|
||||
let left_diffs = split_diffs(
|
||||
&left_symbol_diff.data_diff,
|
||||
&left_symbol_diff.data_reloc_diff,
|
||||
left_symbol.address as usize,
|
||||
);
|
||||
let right_diffs = split_diffs(
|
||||
&right_symbol_diff.data_diff,
|
||||
&right_symbol_diff.data_reloc_diff,
|
||||
right_symbol.address as usize,
|
||||
);
|
||||
render_table(
|
||||
ui,
|
||||
available_width,
|
||||
@@ -540,13 +500,14 @@ pub fn diff_view_ui(
|
||||
total_rows,
|
||||
|row, column| {
|
||||
let i = row.index();
|
||||
let address = i * BYTES_PER_ROW;
|
||||
let row_offset = i * BYTES_PER_ROW;
|
||||
row.col(|ui| {
|
||||
if column == 0 {
|
||||
data_row_ui(
|
||||
ui,
|
||||
Some(left_obj),
|
||||
address,
|
||||
left_symbol.address as usize,
|
||||
row_offset,
|
||||
&left_diffs[i],
|
||||
appearance,
|
||||
column,
|
||||
@@ -555,7 +516,8 @@ pub fn diff_view_ui(
|
||||
data_row_ui(
|
||||
ui,
|
||||
Some(right_obj),
|
||||
address,
|
||||
right_symbol.address as usize,
|
||||
row_offset,
|
||||
&right_diffs[i],
|
||||
appearance,
|
||||
column,
|
||||
@@ -649,11 +611,46 @@ fn diff_col_ui(
|
||||
if !ctx.status.success {
|
||||
build_log_ui(ui, ctx.status, appearance);
|
||||
} else if let Some((obj, diff)) = ctx.obj {
|
||||
if let Some((_symbol, symbol_diff, symbol_idx)) = ctx.symbol {
|
||||
if let Some((symbol, symbol_diff, symbol_idx)) = ctx.symbol {
|
||||
hotkeys::check_scroll_hotkeys(ui, false);
|
||||
let ctx = FunctionDiffContext { obj, diff, symbol_ref: Some(symbol_idx) };
|
||||
if state.current_view == View::ExtabDiff {
|
||||
extab_ui(ui, ctx, appearance, column);
|
||||
} else if state.current_view == View::DataDiff {
|
||||
hotkeys::check_scroll_hotkeys(ui, false);
|
||||
let total_bytes =
|
||||
symbol_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
|
||||
if total_bytes == 0 {
|
||||
return ret;
|
||||
}
|
||||
let total_rows = (total_bytes - 1) / BYTES_PER_ROW + 1;
|
||||
let diffs = split_diffs(
|
||||
&symbol_diff.data_diff,
|
||||
&symbol_diff.data_reloc_diff,
|
||||
symbol.address as usize,
|
||||
);
|
||||
render_table(
|
||||
ui,
|
||||
available_width / 2.0,
|
||||
1,
|
||||
appearance.code_font.size,
|
||||
total_rows,
|
||||
|row, _column| {
|
||||
let i = row.index();
|
||||
let row_offset = i * BYTES_PER_ROW;
|
||||
row.col(|ui| {
|
||||
data_row_ui(
|
||||
ui,
|
||||
Some(obj),
|
||||
symbol.address as usize,
|
||||
row_offset,
|
||||
&diffs[i],
|
||||
appearance,
|
||||
column,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
} else {
|
||||
render_table(
|
||||
ui,
|
||||
@@ -678,29 +675,6 @@ fn diff_col_ui(
|
||||
},
|
||||
);
|
||||
}
|
||||
} else if let Some((_section, section_diff, _section_idx)) = ctx.section {
|
||||
hotkeys::check_scroll_hotkeys(ui, false);
|
||||
let total_bytes =
|
||||
section_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len);
|
||||
if total_bytes == 0 {
|
||||
return ret;
|
||||
}
|
||||
let total_rows = (total_bytes - 1) / BYTES_PER_ROW + 1;
|
||||
let diffs = split_diffs(§ion_diff.data_diff, §ion_diff.reloc_diff);
|
||||
render_table(
|
||||
ui,
|
||||
available_width / 2.0,
|
||||
1,
|
||||
appearance.code_font.size,
|
||||
total_rows,
|
||||
|row, _column| {
|
||||
let i = row.index();
|
||||
let address = i * BYTES_PER_ROW;
|
||||
row.col(|ui| {
|
||||
data_row_ui(ui, Some(obj), address, &diffs[i], appearance, column);
|
||||
});
|
||||
},
|
||||
);
|
||||
} else if let Some((_other_symbol, _other_symbol_diff, other_symbol_idx)) = other_ctx.symbol
|
||||
{
|
||||
if let Some(action) = symbol_list_ui(
|
||||
@@ -796,10 +770,6 @@ fn missing_obj_ui(ui: &mut Ui, appearance: &Appearance) {
|
||||
});
|
||||
}
|
||||
|
||||
fn find_section(obj: &Object, section_name: &str) -> Option<usize> {
|
||||
obj.sections.iter().position(|section| section.name == section_name)
|
||||
}
|
||||
|
||||
pub fn hover_items_ui(ui: &mut Ui, items: Vec<HoverItem>, appearance: &Appearance) {
|
||||
for item in items {
|
||||
match item {
|
||||
|
||||
@@ -233,7 +233,6 @@ impl DiffViewState {
|
||||
let resolved_nav = resolve_navigation(nav.kind, resolved_left, resolved_right);
|
||||
if (resolved_nav.left_symbol.is_some() && resolved_nav.right_symbol.is_some())
|
||||
|| (resolved_nav.left_symbol.is_none() && resolved_nav.right_symbol.is_none())
|
||||
|| resolved_nav.view != View::FunctionDiff
|
||||
{
|
||||
// Regular navigation
|
||||
if state.is_selecting_symbol() {
|
||||
@@ -416,14 +415,8 @@ fn resolve_navigation(
|
||||
},
|
||||
(SectionKind::Data, SectionKind::Data) => ResolvedNavigation {
|
||||
view: View::DataDiff,
|
||||
left_symbol: Some(SymbolRefByName {
|
||||
symbol_name: "".to_string(),
|
||||
section_name: Some(left.section.name.clone()),
|
||||
}),
|
||||
right_symbol: Some(SymbolRefByName {
|
||||
symbol_name: "".to_string(),
|
||||
section_name: Some(right.section.name.clone()),
|
||||
}),
|
||||
left_symbol: Some(left.symbol_ref),
|
||||
right_symbol: Some(right.symbol_ref),
|
||||
},
|
||||
_ => ResolvedNavigation::default(),
|
||||
},
|
||||
@@ -438,14 +431,8 @@ fn resolve_navigation(
|
||||
},
|
||||
SectionKind::Data => ResolvedNavigation {
|
||||
view: View::DataDiff,
|
||||
left_symbol: Some(SymbolRefByName {
|
||||
symbol_name: "".to_string(),
|
||||
section_name: Some(left.section.name.clone()),
|
||||
}),
|
||||
right_symbol: Some(SymbolRefByName {
|
||||
symbol_name: "".to_string(),
|
||||
section_name: Some(left.section.name.clone()),
|
||||
}),
|
||||
left_symbol: Some(left.symbol_ref),
|
||||
right_symbol: None,
|
||||
},
|
||||
_ => ResolvedNavigation::default(),
|
||||
},
|
||||
@@ -460,14 +447,8 @@ fn resolve_navigation(
|
||||
},
|
||||
SectionKind::Data => ResolvedNavigation {
|
||||
view: View::DataDiff,
|
||||
left_symbol: Some(SymbolRefByName {
|
||||
symbol_name: "".to_string(),
|
||||
section_name: Some(right.section.name.clone()),
|
||||
}),
|
||||
right_symbol: Some(SymbolRefByName {
|
||||
symbol_name: "".to_string(),
|
||||
section_name: Some(right.section.name.clone()),
|
||||
}),
|
||||
left_symbol: None,
|
||||
right_symbol: Some(right.symbol_ref),
|
||||
},
|
||||
_ => ResolvedNavigation::default(),
|
||||
},
|
||||
|
||||
6
objdiff-wasm/.cargo/config.toml
Normal file
6
objdiff-wasm/.cargo/config.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[build]
|
||||
target = "wasm32-wasip2"
|
||||
|
||||
[unstable]
|
||||
build-std = ["panic_abort", "core", "alloc"]
|
||||
build-std-features = ["compiler-builtins-mem"]
|
||||
@@ -17,12 +17,13 @@ build = "build.rs"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
default = []
|
||||
std = ["objdiff-core/std"]
|
||||
|
||||
[dependencies]
|
||||
log = { version = "0.4", default-features = false }
|
||||
regex = { version = "1.11", default-features = false, features = ["unicode-case"] }
|
||||
wit-bindgen = { version = "0.44", default-features = false, features = ["macros"] }
|
||||
xxhash-rust = { version = "0.8", default-features = false, features = ["xxh3"] }
|
||||
|
||||
[dependencies.objdiff-core]
|
||||
@@ -33,8 +34,5 @@ features = ["arm", "arm64", "mips", "ppc", "superh", "x86", "dwarf"]
|
||||
[target.'cfg(target_family = "wasm")'.dependencies]
|
||||
talc = { version = "4.4", default-features = false, features = ["lock_api"] }
|
||||
|
||||
[target.'cfg(target_os = "wasi")'.dependencies]
|
||||
wit-bindgen = { version = "0.43", default-features = false, features = ["macros"] }
|
||||
|
||||
[build-dependencies]
|
||||
wit-deps = "0.5"
|
||||
|
||||
4
objdiff-wasm/package-lock.json
generated
4
objdiff-wasm/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "objdiff-wasm",
|
||||
"version": "3.0.0",
|
||||
"version": "3.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "objdiff-wasm",
|
||||
"version": "3.0.0",
|
||||
"version": "3.1.0",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "objdiff-wasm",
|
||||
"version": "3.0.0",
|
||||
"version": "3.1.0",
|
||||
"description": "A local diffing tool for decompilation projects.",
|
||||
"author": {
|
||||
"name": "Luke Street",
|
||||
@@ -19,8 +19,8 @@
|
||||
"types": "dist/objdiff.d.ts",
|
||||
"scripts": {
|
||||
"build": "npm run build:wasm && npm run build:transpile && npm run build:lib",
|
||||
"build:wasm": "cargo +nightly -Zbuild-std=panic_abort,core,alloc -Zbuild-std-features=compiler-builtins-mem build --target wasm32-wasip2 --release --no-default-features",
|
||||
"build:transpile": "jco transpile ../target/wasm32-wasip2/release/objdiff_wasm.wasm --no-nodejs-compat --no-wasi-shim --no-namespaced-exports --map wasi:logging/logging=./wasi-logging.js --optimize -o pkg --name objdiff",
|
||||
"build:wasm": "cargo build --profile release-min --no-default-features",
|
||||
"build:transpile": "jco transpile ../target/wasm32-wasip2/release-min/objdiff_wasm.wasm --no-nodejs-compat --no-wasi-shim --no-namespaced-exports --map wasi:logging/logging=./wasi-logging.js --optimize -o pkg --name objdiff",
|
||||
"build:lib": "rslib build"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
4
objdiff-wasm/rust-toolchain.toml
Normal file
4
objdiff-wasm/rust-toolchain.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
components = ["rust-src"]
|
||||
targets = ["wasm32-wasip2"]
|
||||
@@ -1,3 +1,4 @@
|
||||
#![allow(clippy::derivable_impls)]
|
||||
use alloc::{
|
||||
format,
|
||||
rc::{Rc, Weak},
|
||||
@@ -23,7 +24,7 @@ wit_bindgen::generate!({
|
||||
|
||||
use exports::objdiff::core::{
|
||||
diff::{
|
||||
DiffConfigBorrow, DiffResult, Guest as GuestDiff, GuestDiffConfig, GuestObject,
|
||||
DiffConfigBorrow, DiffResult, DiffSide, Guest as GuestDiff, GuestDiffConfig, GuestObject,
|
||||
GuestObjectDiff, MappingConfig, Object, ObjectBorrow, ObjectDiff, ObjectDiffBorrow,
|
||||
SymbolFlags, SymbolInfo, SymbolKind, SymbolRef,
|
||||
},
|
||||
@@ -223,7 +224,7 @@ impl GuestDisplay for Component {
|
||||
let symbol_display = from_symbol_ref(symbol_ref);
|
||||
diff::display::symbol_context(obj, symbol_display.symbol as usize)
|
||||
.into_iter()
|
||||
.map(|item| ContextItem::from(item))
|
||||
.map(ContextItem::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -235,7 +236,7 @@ impl GuestDisplay for Component {
|
||||
let symbol_display = from_symbol_ref(symbol_ref);
|
||||
diff::display::symbol_hover(obj, symbol_display.symbol as usize, addend, override_color)
|
||||
.into_iter()
|
||||
.map(|item| HoverItem::from(item))
|
||||
.map(HoverItem::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -282,7 +283,7 @@ impl GuestDisplay for Component {
|
||||
};
|
||||
diff::display::instruction_context(obj, resolved, &ins)
|
||||
.into_iter()
|
||||
.map(|item| ContextItem::from(item))
|
||||
.map(ContextItem::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -331,7 +332,7 @@ impl GuestDisplay for Component {
|
||||
};
|
||||
diff::display::instruction_hover(obj, resolved, &ins)
|
||||
.into_iter()
|
||||
.map(|item| HoverItem::from(item))
|
||||
.map(HoverItem::from)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@@ -469,8 +470,21 @@ unsafe impl Sync for ObjectCache {}
|
||||
|
||||
static OBJECT_CACHE: ObjectCache = ObjectCache::new();
|
||||
|
||||
impl From<DiffSide> for objdiff_core::diff::DiffSide {
|
||||
fn from(value: DiffSide) -> Self {
|
||||
match value {
|
||||
DiffSide::Target => Self::Target,
|
||||
DiffSide::Base => Self::Base,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GuestObject for ResourceObject {
|
||||
fn parse(data: Vec<u8>, diff_config: DiffConfigBorrow) -> Result<Object, String> {
|
||||
fn parse(
|
||||
data: Vec<u8>,
|
||||
diff_config: DiffConfigBorrow,
|
||||
diff_side: DiffSide,
|
||||
) -> Result<Object, String> {
|
||||
let hash = xxh3_64(&data);
|
||||
let mut cached = None;
|
||||
OBJECT_CACHE.borrow_mut().retain(|c| {
|
||||
@@ -486,7 +500,9 @@ impl GuestObject for ResourceObject {
|
||||
return Ok(Object::new(ResourceObject(obj, hash)));
|
||||
}
|
||||
let diff_config = diff_config.get::<ResourceDiffConfig>().0.borrow();
|
||||
let obj = Rc::new(obj::read::parse(&data, &diff_config).map_err(|e| e.to_string())?);
|
||||
let obj = Rc::new(
|
||||
obj::read::parse(&data, &diff_config, diff_side.into()).map_err(|e| e.to_string())?,
|
||||
);
|
||||
OBJECT_CACHE.borrow_mut().push(CachedObject(Rc::downgrade(&obj), hash));
|
||||
Ok(Object::new(ResourceObject(obj, hash)))
|
||||
}
|
||||
@@ -527,9 +543,7 @@ impl GuestObjectDiff for ResourceObjectDiff {
|
||||
fn get_symbol(&self, symbol_ref: SymbolRef) -> Option<SymbolInfo> {
|
||||
let obj = self.0.as_ref();
|
||||
let symbol_display = from_symbol_ref(symbol_ref);
|
||||
let Some(symbol) = obj.symbols.get(symbol_display.symbol) else {
|
||||
return None;
|
||||
};
|
||||
let symbol = obj.symbols.get(symbol_display.symbol)?;
|
||||
Some(SymbolInfo {
|
||||
id: to_symbol_ref(symbol_display),
|
||||
name: symbol.name.clone(),
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(target_os = "wasi")]
|
||||
mod api;
|
||||
#[cfg(target_os = "wasi")]
|
||||
mod logging;
|
||||
|
||||
#[cfg(all(target_os = "wasi", not(feature = "std")))]
|
||||
|
||||
@@ -19,6 +19,7 @@ interface diff {
|
||||
parse: static func(
|
||||
data: list<u8>,
|
||||
config: borrow<diff-config>,
|
||||
side: diff-side,
|
||||
) -> result<object, string>;
|
||||
|
||||
hash: func() -> u64;
|
||||
@@ -80,6 +81,11 @@ interface diff {
|
||||
config: borrow<diff-config>,
|
||||
mapping: mapping-config,
|
||||
) -> result<diff-result, string>;
|
||||
|
||||
enum diff-side {
|
||||
target,
|
||||
base,
|
||||
}
|
||||
}
|
||||
|
||||
interface display {
|
||||
|
||||
Reference in New Issue
Block a user