mirror of
https://github.com/encounter/objdiff.git
synced 2026-07-10 12:18:36 -07:00
Compare commits
192 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6bcac60df8 | |||
| b34108fa24 | |||
| 5f9e9063f8 | |||
| a3e11149f7 | |||
| d22c785610 | |||
| 5035f8ec36 | |||
| b8f401b0ba | |||
| 282783c081 | |||
| f5ae76e64d | |||
| dbabdbff11 | |||
| 2fdba8842e | |||
| 0fe4fadbc3 | |||
| 04be189e51 | |||
| 4d0a273d65 | |||
| 73917c12a8 | |||
| 2cd1e2e0ce | |||
| a6ddc506f8 | |||
| b8d7b3bc1a | |||
| 02daae417c | |||
| 8b1b4a9a79 | |||
| 13f1267eac | |||
| 3cebee6766 | |||
| ab0538e9a4 | |||
| b643265809 | |||
| 7a65e07e9b | |||
| 3c4092b965 | |||
| 0f599bea2b | |||
| 9927fc35b6 | |||
| 16fb6a491f | |||
| 2541aef71a | |||
| 7ad1f95bf7 | |||
| 66c879a95d | |||
| d53c358d2f | |||
| f24c017545 | |||
| 3b8603ab22 | |||
| 86b7d6b229 | |||
| 3f82781808 | |||
| 926dd06ce9 | |||
| de70b6fca0 | |||
| 79542a4725 | |||
| 0ebd2ae459 | |||
| 08ebea8bd2 | |||
| 46b8890159 | |||
| 8356fe670d | |||
| 7834185223 | |||
| 216dd70d5b | |||
| 976e629f08 | |||
| 67b28b7da1 | |||
| c0b9bfae21 | |||
| f3f4b0482a | |||
| 278683f103 | |||
| 6ad60de32a | |||
| 90c93164ab | |||
| c02eb31dbb | |||
| 86d92866aa | |||
| 51c3af2bbe | |||
| d0b8b449d9 | |||
| 32f5f202f7 | |||
| 481dbc185a | |||
| 26a4cc79cf | |||
| 5c96c2ee51 | |||
| d162fe847e | |||
| 2a24eb5aec | |||
| f7c291bd55 | |||
| 03a578c1bb | |||
| d09ef8e207 | |||
| 63552a58ae | |||
| 827f4a42bd | |||
| b2dcecc5d8 | |||
| 67b237eab6 | |||
| 66da80ff69 | |||
| 0a85f540f2 | |||
| d79b5b1233 | |||
| 8f5519cb6a | |||
| 19ec08be9a | |||
| 2ad0898efa | |||
| 03f2bcb8b1 | |||
| 781071761a | |||
| 572afa8551 | |||
| 90e81fad7e | |||
| 7a8efb4c88 | |||
| 56dac46280 | |||
| 1866158092 | |||
| fe8e7029f6 | |||
| e2c70342c9 | |||
| e6035b00df | |||
| 97bcfe23d4 | |||
| d684b622b5 | |||
| c698750068 | |||
| a06d3455ae | |||
| fbdaa89cc0 | |||
| f7cb494a62 | |||
| 7cc6ed2b43 | |||
| 532b684682 | |||
| fb1d434bbc | |||
| 23009bf9a3 | |||
| 6fb4bb8855 | |||
| a138dfa907 | |||
| 0b95613768 | |||
| 5d4b33a500 | |||
| f2a591356e | |||
| 8d24ec6373 | |||
| 58430d947b | |||
| 1533125f9d | |||
| 5654060dc8 | |||
| 84079c3934 | |||
| 48804dc2e3 | |||
| 8cfa8b7dab | |||
| 93a4d7e55d | |||
| 7c424a7966 | |||
| 678210d58a | |||
| 4302821615 | |||
| c4b4244b59 | |||
| 52c138bf06 | |||
| 813c8aa539 | |||
| 0f0aaab795 | |||
| b21892be31 | |||
| 247d6da94b | |||
| bd95faa9c3 | |||
| 2c57e4960f | |||
| cff4be2979 | |||
| e1da90943c | |||
| b5713db333 | |||
| 4c3f5e9836 | |||
| e9ce02feb0 | |||
| 9a378d85ed | |||
| c8ff45f2c8 | |||
| 34e4513c69 | |||
| a015971c20 | |||
| e67d5998b3 | |||
| 91bc23edfc | |||
| c9c3b32376 | |||
| 0dc123b064 | |||
| 1e62d4664c | |||
| 1205e8ceb4 | |||
| c917cad5f0 | |||
| dd653329f5 | |||
| f5d3d5f10a | |||
| c327ed3ea8 | |||
| 85fb18a21a | |||
| 00ad0d8094 | |||
| 8fac63c42c | |||
| 0fb7f3901c | |||
| 3385f58341 | |||
| 60b227f45e | |||
| 127ae5ae44 | |||
| 5f48e69775 | |||
| 8756eee07b | |||
| bd3ed0d5ad | |||
| e638d0b17a | |||
| f58616b6dd | |||
| e9762e24c2 | |||
| dab79d96a1 | |||
| a57e5db983 | |||
| d0afd3b83e | |||
| a367af612b | |||
| 22052ea10b | |||
| f7c3501eae | |||
| 07ef93f16a | |||
| 8e8ab6bef8 | |||
| e865f3d598 | |||
| 2b13e9886a | |||
| 1750af736a | |||
| 731b604c24 | |||
| 2d643eb071 | |||
| 0c48d711c7 | |||
| 34220a8e26 | |||
| 0c9e5526d4 | |||
| 3db0727469 | |||
| 8b5bf21f38 | |||
| b77df77000 | |||
| 7e08f9715b | |||
| 3c05852d00 | |||
| a51ff44be1 | |||
| d225cac205 | |||
| 737b3782db | |||
| 1d782243e0 | |||
| a1499f475d | |||
| f263e490e3 | |||
| d0e6c5c057 | |||
| e1c51ac297 | |||
| 39b1b49985 | |||
| 6c7160ab7e | |||
| 644d4762f0 | |||
| b40fae5140 | |||
| fbf85632ab | |||
| 73a89d2768 | |||
| a162c2f840 | |||
| a474b27d55 | |||
| 3d7f2b70dc | |||
| fe886f862d | |||
| 2bcbc34850 |
+4
-5
@@ -1,5 +1,4 @@
|
|||||||
[target.x86_64-pc-windows-msvc]
|
# statically link the C runtime so the executable does not depend on
|
||||||
linker = "rust-lld"
|
# that shared/dynamic library.
|
||||||
|
[target.'cfg(all(target_env = "msvc", target_os = "windows"))']
|
||||||
[target.aarch64-pc-windows-msvc]
|
rustflags = ["-C", "target-feature=+crt-static"]
|
||||||
linker = "rust-lld"
|
|
||||||
|
|||||||
+108
-30
@@ -8,6 +8,10 @@ on:
|
|||||||
- "LICENSE*"
|
- "LICENSE*"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
# For npm publish provenance
|
||||||
|
id-token: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
BUILD_PROFILE: release-lto
|
BUILD_PROFILE: release-lto
|
||||||
CARGO_INCREMENTAL: 0
|
CARGO_INCREMENTAL: 0
|
||||||
@@ -24,7 +28,7 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get -y install libgtk-3-dev
|
sudo apt-get -y install libgtk-3-dev
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
@@ -32,9 +36,9 @@ jobs:
|
|||||||
- name: Cache Rust workspace
|
- name: Cache Rust workspace
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
- name: Cargo check
|
- name: Cargo check
|
||||||
run: cargo check --all-targets --all-features
|
run: cargo check --all-targets --all-features --workspace
|
||||||
- name: Cargo clippy
|
- name: Cargo clippy
|
||||||
run: cargo clippy --all-targets --all-features
|
run: cargo clippy --all-targets --all-features --workspace
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
name: Format
|
name: Format
|
||||||
@@ -43,7 +47,7 @@ jobs:
|
|||||||
RUSTFLAGS: -D warnings
|
RUSTFLAGS: -D warnings
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
# We use nightly options in rustfmt.toml
|
# We use nightly options in rustfmt.toml
|
||||||
uses: dtolnay/rust-toolchain@nightly
|
uses: dtolnay/rust-toolchain@nightly
|
||||||
@@ -58,12 +62,12 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
checks:
|
checks:
|
||||||
- advisories
|
# - advisories
|
||||||
- bans licenses sources
|
- bans licenses sources
|
||||||
# Prevent new advisories from failing CI
|
# Prevent new advisories from failing CI
|
||||||
continue-on-error: ${{ matrix.checks == 'advisories' }}
|
continue-on-error: ${{ matrix.checks == 'advisories' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: EmbarkStudios/cargo-deny-action@v2
|
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||||
with:
|
with:
|
||||||
command: check ${{ matrix.checks }}
|
command: check ${{ matrix.checks }}
|
||||||
@@ -72,7 +76,7 @@ jobs:
|
|||||||
name: Test
|
name: Test
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
platform: [ubuntu-latest, windows-latest, macos-latest]
|
platform: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
@@ -82,13 +86,13 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get -y install libgtk-3-dev
|
sudo apt-get -y install libgtk-3-dev
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
- name: Cache Rust workspace
|
- name: Cache Rust workspace
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
- name: Cargo test
|
- name: Cargo test
|
||||||
run: cargo test --release --features all
|
run: cargo test --release --all-features --workspace
|
||||||
|
|
||||||
build-cli:
|
build-cli:
|
||||||
name: Build objdiff-cli
|
name: Build objdiff-cli
|
||||||
@@ -141,14 +145,15 @@ jobs:
|
|||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
- name: Install uv
|
||||||
|
if: matrix.build == 'zigbuild'
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
- name: Install cargo-zigbuild
|
- name: Install cargo-zigbuild
|
||||||
if: matrix.build == 'zigbuild'
|
if: matrix.build == 'zigbuild'
|
||||||
run: |
|
run: |
|
||||||
python3 -m venv .venv
|
uv tool install cargo-zigbuild==0.20.1 --with-executables-from ziglang==0.15.1
|
||||||
. .venv/bin/activate
|
echo "CARGO_ZIGBUILD_ZIG_PATH=$(uv tool dir)/cargo-zigbuild/bin/python-zig" >> $GITHUB_ENV
|
||||||
echo PATH=$PATH >> $GITHUB_ENV
|
|
||||||
pip install ziglang==0.13.0.post1 cargo-zigbuild==0.19.8
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
@@ -162,7 +167,7 @@ jobs:
|
|||||||
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
||||||
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
|
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
|
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
|
||||||
path: |
|
path: |
|
||||||
@@ -208,14 +213,15 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get -y install ${{ matrix.packages }}
|
sudo apt-get -y install ${{ matrix.packages }}
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
- name: Install uv
|
||||||
|
if: matrix.build == 'zigbuild'
|
||||||
|
uses: astral-sh/setup-uv@v7
|
||||||
- name: Install cargo-zigbuild
|
- name: Install cargo-zigbuild
|
||||||
if: matrix.build == 'zigbuild'
|
if: matrix.build == 'zigbuild'
|
||||||
run: |
|
run: |
|
||||||
python3 -m venv .venv
|
uv tool install cargo-zigbuild==0.20.1 --with-executables-from ziglang==0.15.1
|
||||||
. .venv/bin/activate
|
echo "CARGO_ZIGBUILD_ZIG_PATH=$(uv tool dir)/cargo-zigbuild/bin/python-zig" >> $GITHUB_ENV
|
||||||
echo PATH=$PATH >> $GITHUB_ENV
|
|
||||||
pip install ziglang==0.13.0.post1 cargo-zigbuild==0.19.8
|
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
@@ -229,7 +235,7 @@ jobs:
|
|||||||
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
||||||
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
|
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
|
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
|
||||||
path: |
|
path: |
|
||||||
@@ -242,29 +248,37 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@nightly
|
uses: dtolnay/rust-toolchain@nightly
|
||||||
with:
|
with:
|
||||||
components: rust-src
|
components: rust-src
|
||||||
- name: Cache Rust workspace
|
- name: Cache Rust workspace
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v6.4.0
|
||||||
|
with:
|
||||||
|
node-version: lts/*
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm -C objdiff-wasm install
|
run: npm -C objdiff-wasm ci
|
||||||
- name: Build
|
- name: Build
|
||||||
run: npm -C objdiff-wasm run build
|
run: npm -C objdiff-wasm run build
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: wasm
|
||||||
|
path: objdiff-wasm/dist/
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
release:
|
check-version:
|
||||||
name: Release
|
name: Check package versions
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build-cli, build-gui]
|
needs: [ build-cli, build-gui, build-wasm ]
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
- name: Check git tag against Cargo version
|
- name: Check git tag against package versions
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -eou pipefail
|
set -eou pipefail
|
||||||
@@ -276,9 +290,24 @@ jobs:
|
|||||||
echo "::error::Git tag doesn't match the Cargo version! ($tag != $version)"
|
echo "::error::Git tag doesn't match the Cargo version! ($tag != $version)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
version="v$(jq -r .version objdiff-wasm/package.json)"
|
||||||
|
if [ "$tag" != "$version" ]; then
|
||||||
|
echo "::error::Git tag doesn't match the npm version! ($tag != $version)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
release-github:
|
||||||
|
name: Release (GitHub)
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [ check-version ]
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
steps:
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
|
pattern: objdiff-*
|
||||||
path: artifacts
|
path: artifacts
|
||||||
- name: Rename artifacts
|
- name: Rename artifacts
|
||||||
working-directory: artifacts
|
working-directory: artifacts
|
||||||
@@ -308,3 +337,52 @@ jobs:
|
|||||||
files: out/*
|
files: out/*
|
||||||
draft: true
|
draft: true
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
|
|
||||||
|
release-cargo:
|
||||||
|
name: Release (Cargo)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [ check-version ]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- name: Setup Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Publish
|
||||||
|
env:
|
||||||
|
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||||
|
run: cargo publish -p objdiff-core
|
||||||
|
|
||||||
|
release-npm:
|
||||||
|
name: Release (npm)
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [ check-version ]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v6.4.0
|
||||||
|
with:
|
||||||
|
node-version: lts/*
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
- name: Download artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: wasm
|
||||||
|
path: objdiff-wasm/dist
|
||||||
|
- name: Publish
|
||||||
|
working-directory: objdiff-wasm
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
version=$(jq -r '.version' package.json)
|
||||||
|
tag="latest"
|
||||||
|
# Check for prerelease by looking for a dash
|
||||||
|
case "$version" in
|
||||||
|
*-*)
|
||||||
|
tag=$(echo "$version" | sed -e 's/^[^-]*-//' -e 's/\..*$//')
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
echo "Publishing version $version with tag '$tag'..."
|
||||||
|
npm publish --provenance --access public --tag "$tag"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ repos:
|
|||||||
description: Run cargo clippy on all project files.
|
description: Run cargo clippy on all project files.
|
||||||
language: system
|
language: system
|
||||||
entry: cargo
|
entry: cargo
|
||||||
args: ["+nightly", "clippy", "--all-targets", "--all-features"]
|
args: ["+nightly", "clippy", "--all-targets", "--all-features", "--workspace"]
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
- id: cargo-deny
|
- id: cargo-deny
|
||||||
name: cargo deny
|
name: cargo deny
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Repository Guidelines
|
||||||
|
|
||||||
|
## Project Structure & Module Organization
|
||||||
|
|
||||||
|
- `objdiff-core`: core library with diffing logic and shared components
|
||||||
|
- `objdiff-cli`: CLI/TUI with `ratatui`
|
||||||
|
- `objdiff-gui`: GUI with `eframe`/`egui`
|
||||||
|
- `objdiff-wasm`: WebAssembly bindings
|
||||||
|
|
||||||
|
objdiff has three main frontends: GUI, CLI/TUI, and [web](https://github.com/encounter/objdiff-web) (utilizing `objdiff-wasm`).
|
||||||
|
|
||||||
|
`objdiff-gui` is the most fully-featured and is the primary target for new features.
|
||||||
|
|
||||||
|
`objdiff-cli` has an interactive TUI `diff` mode (powered by `ratatui`) and a non-interactive `report` mode to generate detailed progress reports (e.g. for <https://decomp.dev>).
|
||||||
|
|
||||||
|
`objdiff-wasm` is compiled into a [WebAssembly Component](https://component-model.bytecodealliance.org/). The [API](objdiff-wasm/wit/objdiff.wit) is defined using [WIT](https://component-model.bytecodealliance.org/design/wit.html). The web interface is separate, but mirrors the GUI in functionality.
|
||||||
|
|
||||||
|
## Build, Test, and Development Commands
|
||||||
|
|
||||||
|
Run `cargo build` for a debug build and `cargo build --release` for an optimized build.
|
||||||
|
The CLI can be exercised with `cargo run --release --bin objdiff-cli -- --help`.
|
||||||
|
|
||||||
|
The WASM build is not included in the workspace by default due to differences in toolchain and target; build it with `npm -C objdiff-wasm run build`. (nightly required)
|
||||||
|
In general, do NOT use `--workspace`, as it will include the WASM crate and likely fail. Formatting and linting commands are exempt.
|
||||||
|
|
||||||
|
Pre-commit tasks:
|
||||||
|
|
||||||
|
- `cargo test` to run the test suite
|
||||||
|
- `cargo +nightly fmt --all` to format code (nightly required)
|
||||||
|
- `cargo +nightly clippy --all-targets --all-features --workspace -- -D warnings` to lint code (nightly required)
|
||||||
|
- `cargo deny check` (`cargo install --locked cargo-deny` if needed) if dependencies change
|
||||||
|
|
||||||
|
## Testing Guidelines
|
||||||
|
|
||||||
|
Favor focused unit tests adjacent to the code, and integration scenarios in `objdiff-core/tests`.
|
||||||
|
Integration tests utilize snapshots with the `insta` crate; run `curl -LsSf https://insta.rs/install.sh | sh` if needed.
|
||||||
|
To generate updated snapshots, use `cargo insta test`, review diffs manually, then accept changes with `cargo insta accept` (or simply `mv` to accept specific snapshots).
|
||||||
|
|
||||||
|
## Configuration Tips
|
||||||
|
|
||||||
|
- `config.schema.json`: JSON schema for the user-facing project config file (`objdiff.json`)
|
||||||
|
- `objdiff-core/config-schema.json`: Internal options schema to generate `DiffObjConfig` (see `objdiff-core/config_gen.rs`)
|
||||||
|
|
||||||
|
The project configuration (`objdiff.json`) is intended to be generated by the projects' build system. It includes paths to the target (expected) and base (current) object files, along with build commands and file watch patterns. See `README.md` for a summary of key options.
|
||||||
Generated
+2718
-1475
File diff suppressed because it is too large
Load Diff
+17
-7
@@ -5,18 +5,28 @@ members = [
|
|||||||
"objdiff-gui",
|
"objdiff-gui",
|
||||||
"objdiff-wasm",
|
"objdiff-wasm",
|
||||||
]
|
]
|
||||||
|
default-members = [
|
||||||
|
"objdiff-cli",
|
||||||
|
"objdiff-core",
|
||||||
|
"objdiff-gui",
|
||||||
|
# Exclude objdiff-wasm by default
|
||||||
|
]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
version = "3.7.3"
|
||||||
|
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]
|
[profile.release-lto]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
lto = "fat"
|
lto = "fat"
|
||||||
strip = "debuginfo"
|
strip = "debuginfo"
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
||||||
[workspace.package]
|
[profile.release-min]
|
||||||
version = "3.0.0-beta.5"
|
inherits = "release-lto"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
opt-level = "z"
|
||||||
edition = "2024"
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
repository = "https://github.com/encounter/objdiff"
|
|
||||||
rust-version = "1.85"
|
|
||||||
|
|||||||
@@ -7,20 +7,23 @@ A local diffing tool for decompilation projects. Inspired by [decomp.me](https:/
|
|||||||
|
|
||||||
Features:
|
Features:
|
||||||
|
|
||||||
- Compare entire object files: functions and data.
|
- Compare entire object files: functions and data
|
||||||
- Built-in symbol demangling for C++. (CodeWarrior, Itanium & MSVC)
|
- Built-in C++ symbol demangling (GCC, MSVC, CodeWarrior, Itanium)
|
||||||
- Automatic rebuild on source file changes.
|
- Automatic rebuild on source file changes
|
||||||
- Project integration via [configuration file](#configuration).
|
- Project integration via [configuration file](#configuration)
|
||||||
- Search and filter all of a project's objects and quickly switch.
|
- Search and filter objects with quick switching
|
||||||
- Click to highlight all instances of values and registers.
|
- 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:
|
Supports:
|
||||||
|
|
||||||
- PowerPC 750CL (GameCube, Wii)
|
|
||||||
- MIPS (N64, PS1, PS2, PSP)
|
|
||||||
- x86 (COFF only at the moment)
|
|
||||||
- ARM (GBA, DS, 3DS)
|
- ARM (GBA, DS, 3DS)
|
||||||
- ARM64 (Switch, experimental)
|
- ARM64 (Switch)
|
||||||
|
- MIPS (N64, PS1, PS2, PSP)
|
||||||
|
- PowerPC (GameCube, Wii, PS3, Xbox 360)
|
||||||
|
- SuperH (Saturn, Dreamcast)
|
||||||
|
- x86, x86_64 (PC)
|
||||||
|
|
||||||
See [Usage](#usage) for more information.
|
See [Usage](#usage) for more information.
|
||||||
|
|
||||||
@@ -39,7 +42,7 @@ For Linux and macOS, run `chmod +x objdiff-*` to make the binary executable.
|
|||||||
|
|
||||||
### CLI
|
### 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
|
## Screenshots
|
||||||
|
|
||||||
@@ -48,33 +51,30 @@ CLI binaries can be found on the [releases page](https://github.com/encounter/ob
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
objdiff works by comparing two relocatable object files (`.o`). The objects are expected to have the same relative path
|
objdiff compares two relocatable object files (`.o`). Here's how it works:
|
||||||
from the "target" and "base" directories.
|
|
||||||
|
|
||||||
For example, if the target ("expected") object is located at `build/asm/MetroTRK/mslsupp.o` and the base ("actual")
|
1. **Create an `objdiff.json` configuration file** in your project root (or generate it with your build script).
|
||||||
object is located at `build/src/MetroTRK/mslsupp.o`, the following configuration would be used:
|
This file lists **all objects in the project** with their target ("expected") and base ("current") paths.
|
||||||
|
|
||||||
- Target build directory: `build/asm`
|
2. **Load the project** in objdiff.
|
||||||
- Base build directory: `build/src`
|
|
||||||
- Object: `MetroTRK/mslsupp.o`
|
|
||||||
|
|
||||||
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
|
4. **objdiff automatically:**
|
||||||
$ make build/asm/MetroTRK/mslsupp.o # Only if "Build target object" is enabled
|
- Executes the build system to compile the base object (from current source code)
|
||||||
$ make build/src/MetroTRK/mslsupp.o
|
- 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
|
## 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.
|
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.
|
file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it from being committed.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -89,22 +89,31 @@ file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it
|
|||||||
"build_base": true,
|
"build_base": true,
|
||||||
"watch_patterns": [
|
"watch_patterns": [
|
||||||
"*.c",
|
"*.c",
|
||||||
|
"*.cc",
|
||||||
"*.cp",
|
"*.cp",
|
||||||
"*.cpp",
|
"*.cpp",
|
||||||
"*.cxx",
|
"*.cxx",
|
||||||
|
"*.c++",
|
||||||
"*.h",
|
"*.h",
|
||||||
|
"*.hh",
|
||||||
"*.hp",
|
"*.hp",
|
||||||
"*.hpp",
|
"*.hpp",
|
||||||
"*.hxx",
|
"*.hxx",
|
||||||
|
"*.h++",
|
||||||
|
"*.pch",
|
||||||
|
"*.pch++",
|
||||||
|
"*.inc",
|
||||||
"*.s",
|
"*.s",
|
||||||
"*.S",
|
"*.S",
|
||||||
"*.asm",
|
"*.asm",
|
||||||
"*.inc",
|
|
||||||
"*.py",
|
"*.py",
|
||||||
"*.yml",
|
"*.yml",
|
||||||
"*.txt",
|
"*.txt",
|
||||||
"*.json"
|
"*.json"
|
||||||
],
|
],
|
||||||
|
"ignore_patterns": [
|
||||||
|
"build/**/*"
|
||||||
|
],
|
||||||
"units": [
|
"units": [
|
||||||
{
|
{
|
||||||
"name": "main/MetroTRK/mslsupp",
|
"name": "main/MetroTRK/mslsupp",
|
||||||
@@ -118,74 +127,69 @@ file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it
|
|||||||
|
|
||||||
### Schema
|
### 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.
|
#### Build Configuration
|
||||||
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`.
|
|
||||||
|
|
||||||
`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.
|
**`custom_args`** _(optional)_
|
||||||
`make path/to/target.o`).
|
Additional arguments to pass to the build command prior to the object path.
|
||||||
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.
|
|
||||||
|
|
||||||
`build_base`: If true, objdiff will tell the build system to build the base objects before diffing (e.g. `make path/to/base.o`).
|
**`build_target`** _(default: `false`)_
|
||||||
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.
|
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.
|
**`build_base`** _(default: `true`)_
|
||||||
([Supported syntax](https://docs.rs/globset/latest/globset/#syntax))
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
`units` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
|
#### File Watching
|
||||||
|
|
||||||
> `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.
|
**`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.
|
||||||
> `target_path`: Path to the "target" or "expected" object from the project root.
|
|
||||||
> This object is the **intended result** of the match.
|
**`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)).
|
||||||
> `base_path`: Path to the "base" or "actual" object from the project root.
|
|
||||||
> This object is built from the **current source code**.
|
#### Units (Objects)
|
||||||
>
|
|
||||||
> `metadata.auto_generated` _(optional)_: Hides the object from the object list, but still includes it in reports.
|
**`units`** _(optional)_
|
||||||
>
|
If specified, objdiff displays a list of objects in the sidebar for easy navigation. Each unit contains:
|
||||||
> `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".
|
- **`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
|
## Building
|
||||||
|
|
||||||
Install Rust via [rustup](https://rustup.rs).
|
Install Rust via [rustup](https://rustup.rs).
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ git clone https://github.com/encounter/objdiff.git
|
git clone https://github.com/encounter/objdiff.git
|
||||||
$ cd objdiff
|
cd objdiff
|
||||||
$ cargo run --release
|
cargo run --release
|
||||||
```
|
```
|
||||||
|
|
||||||
Or using `cargo install`.
|
Or install directly with cargo:
|
||||||
|
|
||||||
```shell
|
```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.
|
Install `pre-commit` to run linting and formatting automatically:
|
||||||
|
|
||||||
[`uv`](https://github.com/astral-sh/uv#installation) is recommended to manage Python version and tools.
|
|
||||||
|
|
||||||
Rust nightly is required for `cargo +nightly fmt` and `cargo +nightly clippy`.
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ cargo install --locked cargo-deny
|
rustup toolchain install nightly # Required for cargo fmt/clippy
|
||||||
$ rustup toolchain install nightly
|
cargo install --locked cargo-deny # https://github.com/EmbarkStudios/cargo-deny
|
||||||
$ uv tool install pre-commit
|
uv tool install pre-commit # https://docs.astral.sh/uv, or use pipx or pip
|
||||||
$ pre-commit install
|
pre-commit install
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
+63
-10
@@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"custom_make": {
|
"custom_make": {
|
||||||
"type": "string",
|
"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": [
|
"examples": [
|
||||||
"make",
|
"make",
|
||||||
"ninja"
|
"ninja"
|
||||||
@@ -41,39 +41,55 @@
|
|||||||
},
|
},
|
||||||
"build_target": {
|
"build_target": {
|
||||||
"type": "boolean",
|
"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
|
"default": false
|
||||||
},
|
},
|
||||||
"build_base": {
|
"build_base": {
|
||||||
"type": "boolean",
|
"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
|
"default": true
|
||||||
},
|
},
|
||||||
"watch_patterns": {
|
"watch_patterns": {
|
||||||
"type": "array",
|
"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": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"default": [
|
"default": [
|
||||||
"*.c",
|
"*.c",
|
||||||
|
"*.cc",
|
||||||
"*.cp",
|
"*.cp",
|
||||||
"*.cpp",
|
"*.cpp",
|
||||||
"*.cxx",
|
"*.cxx",
|
||||||
|
"*.c++",
|
||||||
"*.h",
|
"*.h",
|
||||||
|
"*.hh",
|
||||||
"*.hp",
|
"*.hp",
|
||||||
"*.hpp",
|
"*.hpp",
|
||||||
"*.hxx",
|
"*.hxx",
|
||||||
|
"*.h++",
|
||||||
|
"*.pch",
|
||||||
|
"*.pch++",
|
||||||
|
"*.inc",
|
||||||
"*.s",
|
"*.s",
|
||||||
"*.S",
|
"*.S",
|
||||||
"*.asm",
|
"*.asm",
|
||||||
"*.inc",
|
|
||||||
"*.py",
|
"*.py",
|
||||||
"*.yml",
|
"*.yml",
|
||||||
"*.txt",
|
"*.txt",
|
||||||
"*.json"
|
"*.json"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"ignore_patterns": {
|
||||||
|
"type": "array",
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
"build/**/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
"objects": {
|
"objects": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "Use units instead.",
|
"description": "Use units instead.",
|
||||||
@@ -95,6 +111,25 @@
|
|||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/$defs/progress_category"
|
"$ref": "#/$defs/progress_category"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Diff configuration options that should be applied automatically when the project is loaded.",
|
||||||
|
"additionalProperties": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"examples": [
|
||||||
|
{
|
||||||
|
"demangler": "gnu_legacy"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"$defs": {
|
"$defs": {
|
||||||
@@ -103,7 +138,7 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"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": {
|
"path": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -112,11 +147,11 @@
|
|||||||
},
|
},
|
||||||
"target_path": {
|
"target_path": {
|
||||||
"type": "string",
|
"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": {
|
"base_path": {
|
||||||
"type": "string",
|
"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": {
|
"reverse_fn_order": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
@@ -140,6 +175,20 @@
|
|||||||
"additionalProperties": {
|
"additionalProperties": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Diff configuration options that should be applied when this unit is active.",
|
||||||
|
"additionalProperties": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -175,6 +224,10 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "If true, objdiff will run the build command with the context file as an argument to generate it.",
|
"description": "If true, objdiff will run the build command with the context file as an argument to generate it.",
|
||||||
"default": false
|
"default": false
|
||||||
|
},
|
||||||
|
"preset_id": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "The decomp.me preset ID to use for the scratch.\nCompiler and flags in the config will take precedence over the preset, but the preset is useful for organizational purposes."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -187,7 +240,7 @@
|
|||||||
"properties": {
|
"properties": {
|
||||||
"complete": {
|
"complete": {
|
||||||
"type": "boolean",
|
"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": {
|
"reverse_fn_order": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
@@ -207,7 +260,7 @@
|
|||||||
},
|
},
|
||||||
"auto_generated": {
|
"auto_generated": {
|
||||||
"type": "boolean",
|
"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,14 @@ ignore = [
|
|||||||
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
#"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" },
|
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
||||||
{ id = "RUSTSEC-2024-0436", reason = "Unmaintained paste crate is an indirect dependency" },
|
{ id = "RUSTSEC-2024-0436", reason = "Unmaintained paste crate is an indirect dependency" },
|
||||||
|
{ id = "RUSTSEC-2025-0052", reason = "Unmaintained async-std crate is an indirect dependency" },
|
||||||
|
{ id = "RUSTSEC-2025-0134", reason = "Unmaintained rustls-pemfile crate is an indirect dependency" },
|
||||||
|
{ id = "RUSTSEC-2026-0098", reason = "Build-time objdiff-wasm wit-deps dependency still uses reqwest 0.11; latest wit-deps has no safe upgrade path" },
|
||||||
|
{ id = "RUSTSEC-2026-0099", reason = "Build-time objdiff-wasm wit-deps dependency still uses reqwest 0.11; latest wit-deps has no safe upgrade path" },
|
||||||
|
{ id = "RUSTSEC-2026-0104", reason = "Build-time objdiff-wasm wit-deps dependency still uses reqwest 0.11; latest wit-deps has no safe upgrade path" },
|
||||||
|
{ id = "RUSTSEC-2026-0192", reason = "Unmaintained ttf-parser is an egui 0.33 indirect dependency; egui 0.35 requires raising MSRV from 1.88 to 1.92" },
|
||||||
|
{ id = "RUSTSEC-2026-0194", reason = "Build-time wayland-scanner dependency parses vendored protocol XML; no newer stable wayland-scanner release is available" },
|
||||||
|
{ id = "RUSTSEC-2026-0195", reason = "Build-time wayland-scanner dependency parses vendored protocol XML; no newer stable wayland-scanner release is available" },
|
||||||
]
|
]
|
||||||
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
||||||
# If this is false, then it uses a built-in git library.
|
# If this is false, then it uses a built-in git library.
|
||||||
@@ -103,6 +111,7 @@ allow = [
|
|||||||
"0BSD",
|
"0BSD",
|
||||||
"OFL-1.1",
|
"OFL-1.1",
|
||||||
"Ubuntu-font-1.0",
|
"Ubuntu-font-1.0",
|
||||||
|
"CDLA-Permissive-2.0",
|
||||||
]
|
]
|
||||||
# The confidence threshold for detecting a license from license text.
|
# The confidence threshold for detecting a license from license text.
|
||||||
# The higher the value, the more closely the license text must be to the
|
# The higher the value, the more closely the license text must be to the
|
||||||
@@ -240,10 +249,6 @@ allow-git = []
|
|||||||
[sources.allow-org]
|
[sources.allow-org]
|
||||||
# github.com organizations to allow git sources for
|
# github.com organizations to allow git sources for
|
||||||
github = [
|
github = [
|
||||||
"CelestialAmber", # cwextab, rlwinmdec
|
|
||||||
"Decompollaborate", # rabbitizer
|
|
||||||
"enarx", # flagset
|
|
||||||
"encounter",
|
|
||||||
]
|
]
|
||||||
# gitlab.com organizations to allow git sources for
|
# gitlab.com organizations to allow git sources for
|
||||||
gitlab = []
|
gitlab = []
|
||||||
|
|||||||
@@ -15,20 +15,20 @@ publish = false
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
argp = "0.4"
|
argp = "0.4"
|
||||||
crossterm = "0.28"
|
crossterm = "0.29"
|
||||||
enable-ansi-support = "0.2"
|
enable-ansi-support = "0.3"
|
||||||
memmap2 = "0.9"
|
memmap2 = "0.9"
|
||||||
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
objdiff-core = { path = "../objdiff-core", features = ["all"] }
|
||||||
prost = "0.13"
|
prost = "0.14"
|
||||||
ratatui = "0.29"
|
ratatui = "0.30"
|
||||||
rayon = "1.10"
|
rayon = "1.11"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
supports-color = "3.0"
|
supports-color = "3.0"
|
||||||
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
time = { version = "0.3", features = ["formatting", "local-offset"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
typed-path = "0.10"
|
typed-path = "0.12"
|
||||||
|
|
||||||
[target.'cfg(target_env = "musl")'.dependencies]
|
[target.'cfg(target_env = "musl")'.dependencies]
|
||||||
mimalloc = "0.1"
|
mimalloc = "0.1"
|
||||||
|
|||||||
+89
-80
@@ -1,7 +1,8 @@
|
|||||||
|
#![allow(unused)] // TODO
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::BTreeMap,
|
||||||
io::stdout,
|
io::stdout,
|
||||||
mem,
|
mem,
|
||||||
str::FromStr,
|
|
||||||
sync::{
|
sync::{
|
||||||
Arc,
|
Arc,
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
@@ -26,13 +27,11 @@ use objdiff_core::{
|
|||||||
watcher::{Watcher, create_watcher},
|
watcher::{Watcher, create_watcher},
|
||||||
},
|
},
|
||||||
config::{
|
config::{
|
||||||
ProjectConfig, ProjectObject, ProjectObjectMetadata, build_globset,
|
ProjectConfig, ProjectObject, ProjectObjectMetadata, ProjectOptions, apply_project_options,
|
||||||
|
build_globset,
|
||||||
path::{check_path_buf, platform_path, platform_path_serde_option},
|
path::{check_path_buf, platform_path, platform_path_serde_option},
|
||||||
},
|
},
|
||||||
diff::{
|
diff::{self, DiffObjConfig, DiffSide, MappingConfig, ObjectDiff},
|
||||||
self, ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig, MappingConfig,
|
|
||||||
ObjectDiff,
|
|
||||||
},
|
|
||||||
jobs::{
|
jobs::{
|
||||||
Job, JobQueue, JobResult,
|
Job, JobQueue, JobResult,
|
||||||
objdiff::{ObjDiffConfig, start_build},
|
objdiff::{ObjDiffConfig, start_build},
|
||||||
@@ -43,6 +42,7 @@ use ratatui::prelude::*;
|
|||||||
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
cmd::apply_config_args,
|
||||||
util::{
|
util::{
|
||||||
output::{OutputFormat, write_output},
|
output::{OutputFormat, write_output},
|
||||||
term::crossterm_panic_handler,
|
term::crossterm_panic_handler,
|
||||||
@@ -78,23 +78,16 @@ pub struct Args {
|
|||||||
#[argp(option, short = 'c')]
|
#[argp(option, short = 'c')]
|
||||||
/// Configuration property (key=value)
|
/// Configuration property (key=value)
|
||||||
config: Vec<String>,
|
config: Vec<String>,
|
||||||
#[argp(option, short = 'm')]
|
|
||||||
/// Symbol mapping (target=base)
|
|
||||||
mapping: Vec<String>,
|
|
||||||
#[argp(option)]
|
|
||||||
/// Left symbol name for selection
|
|
||||||
selecting_left: Option<String>,
|
|
||||||
#[argp(option)]
|
|
||||||
/// Right symbol name for selection
|
|
||||||
selecting_right: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(args: Args) -> Result<()> {
|
pub fn run(args: Args) -> Result<()> {
|
||||||
let (target_path, base_path, project_config) =
|
let (target_path, base_path, project_config, unit_options, symbol_mappings) =
|
||||||
match (&args.target, &args.base, &args.project, &args.unit) {
|
match (&args.target, &args.base, &args.project, &args.unit) {
|
||||||
(Some(_), Some(_), None, None)
|
(Some(_), Some(_), None, None)
|
||||||
| (Some(_), None, None, None)
|
| (Some(_), None, None, None)
|
||||||
| (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None),
|
| (None, Some(_), None, None) => {
|
||||||
|
(args.target.clone(), args.base.clone(), None, None, BTreeMap::new())
|
||||||
|
}
|
||||||
(None, None, p, u) => {
|
(None, None, p, u) => {
|
||||||
let project = match p {
|
let project = match p {
|
||||||
Some(project) => project.clone(),
|
Some(project) => project.clone(),
|
||||||
@@ -106,7 +99,7 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
let Some((project_config, project_config_info)) =
|
let Some((project_config, project_config_info)) =
|
||||||
objdiff_core::config::try_project_config(project.as_ref())
|
objdiff_core::config::try_project_config(project.as_ref())
|
||||||
else {
|
else {
|
||||||
bail!("Project config not found in {}", &project)
|
bail!("Project config not found in {}", project)
|
||||||
};
|
};
|
||||||
let project_config = project_config.with_context(|| {
|
let project_config = project_config.with_context(|| {
|
||||||
format!("Reading project config {}", project_config_info.path.display())
|
format!("Reading project config {}", project_config_info.path.display())
|
||||||
@@ -119,28 +112,32 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
.base_dir
|
.base_dir
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|p| project.join(p.with_platform_encoding()));
|
.map(|p| project.join(p.with_platform_encoding()));
|
||||||
let objects = project_config
|
let units = project_config.units.as_deref().unwrap_or_default();
|
||||||
.units
|
let objects = units
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.enumerate()
|
||||||
.map(|o| {
|
.map(|(idx, o)| {
|
||||||
ObjectConfig::new(
|
(
|
||||||
o,
|
ObjectConfig::new(
|
||||||
&project,
|
o,
|
||||||
target_obj_dir.as_deref(),
|
&project,
|
||||||
base_obj_dir.as_deref(),
|
target_obj_dir.as_deref(),
|
||||||
|
base_obj_dir.as_deref(),
|
||||||
|
),
|
||||||
|
idx,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let object = if let Some(u) = u {
|
let (object, unit_idx) = if let Some(u) = u {
|
||||||
objects
|
objects
|
||||||
.iter()
|
.iter()
|
||||||
.find(|obj| obj.name == *u)
|
.find(|(obj, _)| obj.name == *u)
|
||||||
|
.map(|(obj, idx)| (obj, *idx))
|
||||||
.ok_or_else(|| anyhow!("Unit not found: {}", u))?
|
.ok_or_else(|| anyhow!("Unit not found: {}", u))?
|
||||||
} else if let Some(symbol_name) = &args.symbol {
|
} else if let Some(symbol_name) = &args.symbol {
|
||||||
let mut idx = None;
|
let mut idx = None;
|
||||||
let mut count = 0usize;
|
let mut count = 0usize;
|
||||||
for (i, obj) in objects.iter().enumerate() {
|
for (i, (obj, unit_idx)) in objects.iter().enumerate() {
|
||||||
if obj
|
if obj
|
||||||
.target_path
|
.target_path
|
||||||
.as_deref()
|
.as_deref()
|
||||||
@@ -148,7 +145,7 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
.transpose()?
|
.transpose()?
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
idx = Some(i);
|
idx = Some((i, *unit_idx));
|
||||||
count += 1;
|
count += 1;
|
||||||
if count > 1 {
|
if count > 1 {
|
||||||
break;
|
break;
|
||||||
@@ -157,7 +154,7 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
}
|
}
|
||||||
match (count, idx) {
|
match (count, idx) {
|
||||||
(0, None) => bail!("Symbol not found: {}", symbol_name),
|
(0, None) => bail!("Symbol not found: {}", symbol_name),
|
||||||
(1, Some(i)) => &objects[i],
|
(1, Some((i, unit_idx))) => (&objects[i].0, unit_idx),
|
||||||
(2.., Some(_)) => bail!(
|
(2.., Some(_)) => bail!(
|
||||||
"Multiple instances of {} were found, try specifying a unit",
|
"Multiple instances of {} were found, try specifying a unit",
|
||||||
symbol_name
|
symbol_name
|
||||||
@@ -167,83 +164,84 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
bail!("Must specify one of: symbol, project and unit, target and base objects")
|
bail!("Must specify one of: symbol, project and unit, target and base objects")
|
||||||
};
|
};
|
||||||
|
let unit_options = units.get(unit_idx).and_then(|u| u.options().cloned());
|
||||||
let target_path = object.target_path.clone();
|
let target_path = object.target_path.clone();
|
||||||
let base_path = object.base_path.clone();
|
let base_path = object.base_path.clone();
|
||||||
(target_path, base_path, Some(project_config))
|
let symbol_mappings = object.symbol_mappings.clone();
|
||||||
|
(target_path, base_path, Some(project_config), unit_options, symbol_mappings)
|
||||||
}
|
}
|
||||||
_ => bail!("Either target and base or project and unit must be specified"),
|
_ => bail!("Either target and base or project and unit must be specified"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(output) = &args.output {
|
if let Some(output) = &args.output {
|
||||||
run_oneshot(&args, output, target_path.as_deref(), base_path.as_deref())
|
run_oneshot(
|
||||||
|
&args,
|
||||||
|
output,
|
||||||
|
target_path.as_deref(),
|
||||||
|
base_path.as_deref(),
|
||||||
|
project_config.as_ref(),
|
||||||
|
unit_options,
|
||||||
|
&symbol_mappings,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
run_interactive(args, target_path, base_path, project_config)
|
run_interactive(args, target_path, base_path, project_config, unit_options, symbol_mappings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)> {
|
|
||||||
let mut diff_config = DiffObjConfig::default();
|
|
||||||
for config in &args.config {
|
|
||||||
let (key, value) = config.split_once('=').context("--config expects \"key=value\"")?;
|
|
||||||
let property_id = ConfigPropertyId::from_str(key)
|
|
||||||
.map_err(|()| anyhow!("Invalid configuration property: {}", key))?;
|
|
||||||
diff_config.set_property_value_str(property_id, value).map_err(|()| {
|
|
||||||
let mut options = String::new();
|
|
||||||
match property_id.kind() {
|
|
||||||
ConfigPropertyKind::Boolean => {
|
|
||||||
options = "true, false".to_string();
|
|
||||||
}
|
|
||||||
ConfigPropertyKind::Choice(variants) => {
|
|
||||||
for (i, variant) in variants.iter().enumerate() {
|
|
||||||
if i > 0 {
|
|
||||||
options.push_str(", ");
|
|
||||||
}
|
|
||||||
options.push_str(variant.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
anyhow!("Invalid value for {}. Expected one of: {}", property_id.name(), options)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
let mut mapping_config = MappingConfig {
|
|
||||||
mappings: Default::default(),
|
|
||||||
selecting_left: args.selecting_left.clone(),
|
|
||||||
selecting_right: args.selecting_right.clone(),
|
|
||||||
};
|
|
||||||
for mapping in &args.mapping {
|
|
||||||
let (target, base) =
|
|
||||||
mapping.split_once('=').context("--mapping expects \"target=base\"")?;
|
|
||||||
mapping_config.mappings.insert(target.to_string(), base.to_string());
|
|
||||||
}
|
|
||||||
Ok((diff_config, mapping_config))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_oneshot(
|
fn run_oneshot(
|
||||||
args: &Args,
|
args: &Args,
|
||||||
output: &Utf8PlatformPath,
|
output: &Utf8PlatformPath,
|
||||||
target_path: Option<&Utf8PlatformPath>,
|
target_path: Option<&Utf8PlatformPath>,
|
||||||
base_path: Option<&Utf8PlatformPath>,
|
base_path: Option<&Utf8PlatformPath>,
|
||||||
|
project_config: Option<&ProjectConfig>,
|
||||||
|
unit_options: Option<ProjectOptions>,
|
||||||
|
symbol_mappings: &BTreeMap<String, String>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||||
let (diff_config, mapping_config) = build_config_from_args(args)?;
|
let (diff_config, mapping_config) =
|
||||||
|
build_config_from_args(args, project_config, unit_options.as_ref(), symbol_mappings)?;
|
||||||
let target = target_path
|
let target = target_path
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {}", p))
|
obj::read::read(p.as_ref(), &diff_config, DiffSide::Target)
|
||||||
|
.with_context(|| format!("Loading {p}"))
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let base = base_path
|
let base = base_path
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {}", p))
|
obj::read::read(p.as_ref(), &diff_config, DiffSide::Base)
|
||||||
|
.with_context(|| format!("Loading {p}"))
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let result =
|
let result =
|
||||||
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
|
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
|
||||||
let left = target.as_ref().and_then(|o| result.left.as_ref().map(|d| (o, d)));
|
let left = target.as_ref().zip(result.left.as_ref());
|
||||||
let right = base.as_ref().and_then(|o| result.right.as_ref().map(|d| (o, d)));
|
let right = base.as_ref().zip(result.right.as_ref());
|
||||||
write_output(&DiffResult::new(left, right), Some(output), output_format)?;
|
let diff_result = DiffResult::new(left, right, &diff_config)?;
|
||||||
|
write_output(&diff_result, Some(output), output_format)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_config_from_args(
|
||||||
|
args: &Args,
|
||||||
|
project_config: Option<&ProjectConfig>,
|
||||||
|
unit_options: Option<&ProjectOptions>,
|
||||||
|
symbol_mappings: &BTreeMap<String, String>,
|
||||||
|
) -> Result<(DiffObjConfig, MappingConfig)> {
|
||||||
|
let mut diff_config = DiffObjConfig::default();
|
||||||
|
if let Some(options) = project_config.and_then(|config| config.options.as_ref()) {
|
||||||
|
apply_project_options(&mut diff_config, options)?;
|
||||||
|
}
|
||||||
|
if let Some(options) = unit_options {
|
||||||
|
apply_project_options(&mut diff_config, options)?;
|
||||||
|
}
|
||||||
|
apply_config_args(&mut diff_config, &args.config)?;
|
||||||
|
Ok((diff_config, MappingConfig {
|
||||||
|
mappings: symbol_mappings.clone(),
|
||||||
|
selecting_left: None,
|
||||||
|
selecting_right: None,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
pub jobs: JobQueue,
|
pub jobs: JobQueue,
|
||||||
pub waker: Arc<TermWaker>,
|
pub waker: Arc<TermWaker>,
|
||||||
@@ -302,6 +300,7 @@ pub struct ObjectConfig {
|
|||||||
pub base_path: Option<Utf8PlatformPathBuf>,
|
pub base_path: Option<Utf8PlatformPathBuf>,
|
||||||
pub metadata: ProjectObjectMetadata,
|
pub metadata: ProjectObjectMetadata,
|
||||||
pub complete: Option<bool>,
|
pub complete: Option<bool>,
|
||||||
|
pub symbol_mappings: BTreeMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectConfig {
|
impl ObjectConfig {
|
||||||
@@ -331,6 +330,7 @@ impl ObjectConfig {
|
|||||||
base_path,
|
base_path,
|
||||||
metadata: object.metadata.clone().unwrap_or_default(),
|
metadata: object.metadata.clone().unwrap_or_default(),
|
||||||
complete: object.complete(),
|
complete: object.complete(),
|
||||||
|
symbol_mappings: object.symbol_mappings.clone().unwrap_or_default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -380,11 +380,18 @@ fn run_interactive(
|
|||||||
target_path: Option<Utf8PlatformPathBuf>,
|
target_path: Option<Utf8PlatformPathBuf>,
|
||||||
base_path: Option<Utf8PlatformPathBuf>,
|
base_path: Option<Utf8PlatformPathBuf>,
|
||||||
project_config: Option<ProjectConfig>,
|
project_config: Option<ProjectConfig>,
|
||||||
|
unit_options: Option<ProjectOptions>,
|
||||||
|
symbol_mappings: BTreeMap<String, String>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let Some(symbol_name) = &args.symbol else { bail!("Interactive mode requires a symbol name") };
|
let Some(symbol_name) = &args.symbol else { bail!("Interactive mode requires a symbol name") };
|
||||||
let time_format = time::format_description::parse_borrowed::<2>("[hour]:[minute]:[second]")
|
let time_format = time::format_description::parse_borrowed::<2>("[hour]:[minute]:[second]")
|
||||||
.context("Failed to parse time format")?;
|
.context("Failed to parse time format")?;
|
||||||
let (diff_obj_config, mapping_config) = build_config_from_args(&args)?;
|
let (diff_obj_config, mapping_config) = build_config_from_args(
|
||||||
|
&args,
|
||||||
|
project_config.as_ref(),
|
||||||
|
unit_options.as_ref(),
|
||||||
|
&symbol_mappings,
|
||||||
|
)?;
|
||||||
let mut state = AppState {
|
let mut state = AppState {
|
||||||
jobs: Default::default(),
|
jobs: Default::default(),
|
||||||
waker: Default::default(),
|
waker: Default::default(),
|
||||||
@@ -406,10 +413,12 @@ fn run_interactive(
|
|||||||
};
|
};
|
||||||
if let (Some(project_dir), Some(project_config)) = (&state.project_dir, &state.project_config) {
|
if let (Some(project_dir), Some(project_config)) = (&state.project_dir, &state.project_config) {
|
||||||
let watch_patterns = project_config.build_watch_patterns()?;
|
let watch_patterns = project_config.build_watch_patterns()?;
|
||||||
|
let ignore_patterns = project_config.build_ignore_patterns()?;
|
||||||
state.watcher = Some(create_watcher(
|
state.watcher = Some(create_watcher(
|
||||||
state.modified.clone(),
|
state.modified.clone(),
|
||||||
project_dir.as_ref(),
|
project_dir.as_ref(),
|
||||||
build_globset(&watch_patterns)?,
|
build_globset(&watch_patterns)?,
|
||||||
|
build_globset(&ignore_patterns)?,
|
||||||
Waker::from(state.waker.clone()),
|
Waker::from(state.waker.clone()),
|
||||||
)?);
|
)?);
|
||||||
}
|
}
|
||||||
@@ -423,7 +432,7 @@ fn run_interactive(
|
|||||||
stdout(),
|
stdout(),
|
||||||
EnterAlternateScreen,
|
EnterAlternateScreen,
|
||||||
EnableMouseCapture,
|
EnableMouseCapture,
|
||||||
SetTitle(format!("{} - objdiff", symbol_name)),
|
SetTitle(format!("{symbol_name} - objdiff")),
|
||||||
)?;
|
)?;
|
||||||
let backend = CrosstermBackend::new(stdout());
|
let backend = CrosstermBackend::new(stdout());
|
||||||
let mut terminal = Terminal::new(backend)?;
|
let mut terminal = Terminal::new(backend)?;
|
||||||
|
|||||||
@@ -1,2 +1,33 @@
|
|||||||
pub mod diff;
|
pub mod diff;
|
||||||
pub mod report;
|
pub mod report;
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use anyhow::{Context, Result, anyhow};
|
||||||
|
use objdiff_core::diff::{ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig};
|
||||||
|
|
||||||
|
pub fn apply_config_args(diff_config: &mut DiffObjConfig, args: &[String]) -> Result<()> {
|
||||||
|
for config in args {
|
||||||
|
let (key, value) = config.split_once('=').context("--config expects \"key=value\"")?;
|
||||||
|
let property_id = ConfigPropertyId::from_str(key)
|
||||||
|
.map_err(|()| anyhow!("Invalid configuration property: {}", key))?;
|
||||||
|
diff_config.set_property_value_str(property_id, value).map_err(|()| {
|
||||||
|
let mut options = String::new();
|
||||||
|
match property_id.kind() {
|
||||||
|
ConfigPropertyKind::Boolean => {
|
||||||
|
options = "true, false".to_string();
|
||||||
|
}
|
||||||
|
ConfigPropertyKind::Choice(variants) => {
|
||||||
|
for (i, variant) in variants.iter().enumerate() {
|
||||||
|
if i > 0 {
|
||||||
|
options.push_str(", ");
|
||||||
|
}
|
||||||
|
options.push_str(variant.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
anyhow!("Invalid value for {}. Expected one of: {}", property_id.name(), options)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
+100
-36
@@ -7,9 +7,9 @@ use objdiff_core::{
|
|||||||
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, REPORT_VERSION,
|
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, REPORT_VERSION,
|
||||||
Report, ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
|
Report, ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
|
||||||
},
|
},
|
||||||
config::path::platform_path,
|
config::{ProjectObject, ProjectOptions, apply_project_options, path::platform_path},
|
||||||
diff, obj,
|
diff,
|
||||||
obj::{SectionKind, SymbolFlag},
|
obj::{self, SectionKind, SymbolFlag, SymbolKind},
|
||||||
};
|
};
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||||
@@ -17,7 +17,7 @@ use tracing::{info, warn};
|
|||||||
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cmd::diff::ObjectConfig,
|
cmd::{apply_config_args, diff::ObjectConfig},
|
||||||
util::output::{OutputFormat, write_output},
|
util::output::{OutputFormat, write_output},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,6 +52,9 @@ pub struct GenerateArgs {
|
|||||||
#[argp(option, short = 'f')]
|
#[argp(option, short = 'f')]
|
||||||
/// Output format (json, json-pretty, proto) (default: json)
|
/// Output format (json, json-pretty, proto) (default: json)
|
||||||
format: Option<String>,
|
format: Option<String>,
|
||||||
|
#[argp(option, short = 'c')]
|
||||||
|
/// Configuration property (key=value)
|
||||||
|
config: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromArgs, PartialEq, Debug)]
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
@@ -80,6 +83,14 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn generate(args: GenerateArgs) -> Result<()> {
|
fn generate(args: GenerateArgs) -> Result<()> {
|
||||||
|
let base_diff_config = diff::DiffObjConfig {
|
||||||
|
function_reloc_diffs: diff::FunctionRelocDiffs::None,
|
||||||
|
combine_data_sections: true,
|
||||||
|
combine_text_sections: true,
|
||||||
|
ppc_calculate_pool_relocations: false,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||||
let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new("."));
|
let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new("."));
|
||||||
info!("Loading project {}", project_dir);
|
info!("Loading project {}", project_dir);
|
||||||
@@ -89,39 +100,61 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err),
|
Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err),
|
||||||
None => bail!("No project configuration found"),
|
None => bail!("No project configuration found"),
|
||||||
};
|
};
|
||||||
info!(
|
|
||||||
"Generating report for {} units (using {} threads)",
|
|
||||||
project.units().len(),
|
|
||||||
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
|
||||||
);
|
|
||||||
|
|
||||||
let target_obj_dir =
|
let target_obj_dir =
|
||||||
project.target_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
project.target_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
||||||
let base_obj_dir =
|
let base_obj_dir =
|
||||||
project.base_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
project.base_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
||||||
let objects = project
|
let project_units = project.units.as_deref().unwrap_or_default();
|
||||||
.units
|
let objects = project_units
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.enumerate()
|
||||||
.map(|o| {
|
.map(|(idx, o)| {
|
||||||
ObjectConfig::new(o, project_dir, target_obj_dir.as_deref(), base_obj_dir.as_deref())
|
(
|
||||||
|
ObjectConfig::new(
|
||||||
|
o,
|
||||||
|
project_dir,
|
||||||
|
target_obj_dir.as_deref(),
|
||||||
|
base_obj_dir.as_deref(),
|
||||||
|
),
|
||||||
|
idx,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
info!(
|
||||||
|
"Generating report for {} units (using {} threads)",
|
||||||
|
objects.len(),
|
||||||
|
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
||||||
|
);
|
||||||
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let mut units = vec![];
|
let mut units = vec![];
|
||||||
let mut existing_functions: HashSet<String> = HashSet::new();
|
let mut existing_functions: HashSet<String> = HashSet::new();
|
||||||
if args.deduplicate {
|
if args.deduplicate {
|
||||||
// If deduplicating, we need to run single-threaded
|
// If deduplicating, we need to run single-threaded
|
||||||
for object in &objects {
|
for (object, unit_idx) in &objects {
|
||||||
if let Some(unit) = report_object(object, Some(&mut existing_functions))? {
|
let diff_config = build_unit_diff_config(
|
||||||
|
&base_diff_config,
|
||||||
|
project.options.as_ref(),
|
||||||
|
project_units.get(*unit_idx).and_then(ProjectObject::options),
|
||||||
|
&args.config,
|
||||||
|
)?;
|
||||||
|
if let Some(unit) = report_object(object, &diff_config, Some(&mut existing_functions))?
|
||||||
|
{
|
||||||
units.push(unit);
|
units.push(unit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let vec = objects
|
let vec = objects
|
||||||
.par_iter()
|
.par_iter()
|
||||||
.map(|object| report_object(object, None))
|
.map(|(object, unit_idx)| {
|
||||||
|
let diff_config = build_unit_diff_config(
|
||||||
|
&base_diff_config,
|
||||||
|
project.options.as_ref(),
|
||||||
|
project_units.get(*unit_idx).and_then(ProjectObject::options),
|
||||||
|
&args.config,
|
||||||
|
)?;
|
||||||
|
report_object(object, &diff_config, None)
|
||||||
|
})
|
||||||
.collect::<Result<Vec<Option<ReportUnit>>>>()?;
|
.collect::<Result<Vec<Option<ReportUnit>>>>()?;
|
||||||
units = vec.into_iter().flatten().collect();
|
units = vec.into_iter().flatten().collect();
|
||||||
}
|
}
|
||||||
@@ -143,8 +176,27 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_unit_diff_config(
|
||||||
|
base: &diff::DiffObjConfig,
|
||||||
|
project_options: Option<&ProjectOptions>,
|
||||||
|
unit_options: Option<&ProjectOptions>,
|
||||||
|
cli_args: &[String],
|
||||||
|
) -> Result<diff::DiffObjConfig> {
|
||||||
|
let mut diff_config = base.clone();
|
||||||
|
if let Some(options) = project_options {
|
||||||
|
apply_project_options(&mut diff_config, options)?;
|
||||||
|
}
|
||||||
|
if let Some(options) = unit_options {
|
||||||
|
apply_project_options(&mut diff_config, options)?;
|
||||||
|
}
|
||||||
|
// CLI args override project and unit options
|
||||||
|
apply_config_args(&mut diff_config, cli_args)?;
|
||||||
|
Ok(diff_config)
|
||||||
|
}
|
||||||
|
|
||||||
fn report_object(
|
fn report_object(
|
||||||
object: &ObjectConfig,
|
object: &ObjectConfig,
|
||||||
|
diff_config: &diff::DiffObjConfig,
|
||||||
mut existing_functions: Option<&mut HashSet<String>>,
|
mut existing_functions: Option<&mut HashSet<String>>,
|
||||||
) -> Result<Option<ReportUnit>> {
|
) -> Result<Option<ReportUnit>> {
|
||||||
match (&object.target_path, &object.base_path) {
|
match (&object.target_path, &object.base_path) {
|
||||||
@@ -158,29 +210,29 @@ fn report_object(
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
let diff_config = diff::DiffObjConfig {
|
let mapping_config = diff::MappingConfig {
|
||||||
function_reloc_diffs: diff::FunctionRelocDiffs::None,
|
mappings: object.symbol_mappings.clone(),
|
||||||
..Default::default()
|
selecting_left: None,
|
||||||
|
selecting_right: None,
|
||||||
};
|
};
|
||||||
let mapping_config = diff::MappingConfig::default();
|
|
||||||
let target = object
|
let target = object
|
||||||
.target_path
|
.target_path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
obj::read::read(p.as_ref(), &diff_config)
|
obj::read::read(p.as_ref(), diff_config, diff::DiffSide::Target)
|
||||||
.with_context(|| format!("Failed to open {}", p))
|
.with_context(|| format!("Failed to open {p}"))
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let base = object
|
let base = object
|
||||||
.base_path
|
.base_path
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
obj::read::read(p.as_ref(), &diff_config)
|
obj::read::read(p.as_ref(), diff_config, diff::DiffSide::Base)
|
||||||
.with_context(|| format!("Failed to open {}", p))
|
.with_context(|| format!("Failed to open {p}"))
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let result =
|
let result =
|
||||||
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
|
diff::diff_objs(target.as_ref(), base.as_ref(), None, diff_config, &mapping_config)?;
|
||||||
|
|
||||||
let metadata = ReportUnitMetadata {
|
let metadata = ReportUnitMetadata {
|
||||||
complete: object.metadata.complete,
|
complete: object.metadata.complete,
|
||||||
@@ -218,6 +270,7 @@ fn report_object(
|
|||||||
demangled_name: None,
|
demangled_name: None,
|
||||||
virtual_address: section.virtual_address,
|
virtual_address: section.virtual_address,
|
||||||
}),
|
}),
|
||||||
|
address: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
match section.kind {
|
match section.kind {
|
||||||
@@ -236,16 +289,16 @@ fn report_object(
|
|||||||
|| symbol.size == 0
|
|| symbol.size == 0
|
||||||
|| symbol.flags.contains(SymbolFlag::Hidden)
|
|| symbol.flags.contains(SymbolFlag::Hidden)
|
||||||
|| symbol.flags.contains(SymbolFlag::Ignored)
|
|| symbol.flags.contains(SymbolFlag::Ignored)
|
||||||
|
|| symbol.kind == SymbolKind::Section
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(existing_functions) = &mut existing_functions {
|
if let Some(existing_functions) = &mut existing_functions
|
||||||
if (symbol.flags.contains(SymbolFlag::Global)
|
&& (symbol.flags.contains(SymbolFlag::Global)
|
||||||
|| symbol.flags.contains(SymbolFlag::Weak))
|
|| symbol.flags.contains(SymbolFlag::Weak))
|
||||||
&& !existing_functions.insert(symbol.name.clone())
|
&& !existing_functions.insert(symbol.name.clone())
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
|
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
|
||||||
// Support cases where we don't have a target object,
|
// Support cases where we don't have a target object,
|
||||||
@@ -265,6 +318,7 @@ fn report_object(
|
|||||||
demangled_name: symbol.demangled_name.clone(),
|
demangled_name: symbol.demangled_name.clone(),
|
||||||
virtual_address: symbol.virtual_address,
|
virtual_address: symbol.virtual_address,
|
||||||
}),
|
}),
|
||||||
|
address: symbol.address.checked_sub(section.address),
|
||||||
});
|
});
|
||||||
if match_percent == 100.0 {
|
if match_percent == 100.0 {
|
||||||
measures.matched_functions += 1;
|
measures.matched_functions += 1;
|
||||||
@@ -272,6 +326,16 @@ fn report_object(
|
|||||||
measures.total_functions += 1;
|
measures.total_functions += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sections.sort_by(|a, b| a.name.cmp(&b.name));
|
||||||
|
let reverse_fn_order = object.metadata.reverse_fn_order.unwrap_or(false);
|
||||||
|
functions.sort_by(|a, b| {
|
||||||
|
if reverse_fn_order {
|
||||||
|
b.address.unwrap_or(0).cmp(&a.address.unwrap_or(0))
|
||||||
|
} else {
|
||||||
|
a.address.unwrap_or(u64::MAX).cmp(&b.address.unwrap_or(u64::MAX))
|
||||||
|
}
|
||||||
|
.then_with(|| a.size.cmp(&b.size))
|
||||||
|
});
|
||||||
if metadata.complete.unwrap_or(false) {
|
if metadata.complete.unwrap_or(false) {
|
||||||
measures.complete_code = measures.total_code;
|
measures.complete_code = measures.total_code;
|
||||||
measures.complete_data = measures.total_data;
|
measures.complete_data = measures.total_data;
|
||||||
@@ -411,8 +475,8 @@ fn read_report(path: &Utf8PlatformPath) -> Result<Report> {
|
|||||||
std::io::stdin().read_to_end(&mut data)?;
|
std::io::stdin().read_to_end(&mut data)?;
|
||||||
return Report::parse(&data).with_context(|| "Failed to load report from stdin");
|
return Report::parse(&data).with_context(|| "Failed to load report from stdin");
|
||||||
}
|
}
|
||||||
let file = File::open(path).with_context(|| format!("Failed to open {}", path))?;
|
let file = File::open(path).with_context(|| format!("Failed to open {path}"))?;
|
||||||
let mmap =
|
let mmap =
|
||||||
unsafe { memmap2::Mmap::map(&file) }.with_context(|| format!("Failed to map {}", path))?;
|
unsafe { memmap2::Mmap::map(&file) }.with_context(|| format!("Failed to map {path}"))?;
|
||||||
Report::parse(mmap.as_ref()).with_context(|| format!("Failed to load report {}", path))
|
Report::parse(mmap.as_ref()).with_context(|| format!("Failed to load report {path}"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#![allow(unused)] // TODO
|
||||||
use core::cmp::Ordering;
|
use core::cmp::Ordering;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
@@ -87,7 +88,7 @@ impl UiView for FunctionDiffUi {
|
|||||||
.and_then(|(_, _, d)| d.match_percent)
|
.and_then(|(_, _, d)| d.match_percent)
|
||||||
{
|
{
|
||||||
line_r.spans.push(Span::styled(
|
line_r.spans.push(Span::styled(
|
||||||
format!("{:.2}% ", percent),
|
format!("{percent:.2}% "),
|
||||||
Style::new().fg(match_percent_color(percent)),
|
Style::new().fg(match_percent_color(percent)),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -97,7 +98,7 @@ impl UiView for FunctionDiffUi {
|
|||||||
.and_then(|t| t.format(&state.time_format).ok())
|
.and_then(|t| t.format(&state.time_format).ok())
|
||||||
.unwrap_or_else(|| "N/A".to_string());
|
.unwrap_or_else(|| "N/A".to_string());
|
||||||
line_r.spans.push(Span::styled(
|
line_r.spans.push(Span::styled(
|
||||||
format!("Last reload: {}", reload_time),
|
format!("Last reload: {reload_time}"),
|
||||||
Style::new().fg(Color::White),
|
Style::new().fg(Color::White),
|
||||||
));
|
));
|
||||||
line_r.spans.push(Span::styled(
|
line_r.spans.push(Span::styled(
|
||||||
@@ -170,32 +171,31 @@ impl UiView for FunctionDiffUi {
|
|||||||
|
|
||||||
let mut prev_text = None;
|
let mut prev_text = None;
|
||||||
let mut prev_margin_text = None;
|
let mut prev_margin_text = None;
|
||||||
if self.three_way {
|
if self.three_way
|
||||||
if let Some((obj, symbol_idx, symbol_diff)) =
|
&& let Some((obj, symbol_idx, symbol_diff)) =
|
||||||
get_symbol(state.prev_obj.as_ref(), self.prev_sym)
|
get_symbol(state.prev_obj.as_ref(), self.prev_sym)
|
||||||
{
|
{
|
||||||
let mut text = Text::default();
|
let mut text = Text::default();
|
||||||
let rect = content_chunks[4].inner(Margin::new(0, 1));
|
let rect = content_chunks[4].inner(Margin::new(0, 1));
|
||||||
self.print_sym(
|
self.print_sym(
|
||||||
&mut text,
|
&mut text,
|
||||||
obj,
|
obj,
|
||||||
symbol_idx,
|
symbol_idx,
|
||||||
symbol_diff,
|
symbol_diff,
|
||||||
&state.diff_obj_config,
|
&state.diff_obj_config,
|
||||||
rect,
|
rect,
|
||||||
&self.right_highlight,
|
&self.right_highlight,
|
||||||
result,
|
result,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
max_width = max_width.max(text.width());
|
max_width = max_width.max(text.width());
|
||||||
prev_text = Some(text);
|
prev_text = Some(text);
|
||||||
|
|
||||||
// Render margin
|
// Render margin
|
||||||
let mut text = Text::default();
|
let mut text = Text::default();
|
||||||
let rect = content_chunks[3].inner(Margin::new(1, 1));
|
let rect = content_chunks[3].inner(Margin::new(1, 1));
|
||||||
self.print_margin(&mut text, symbol_diff, rect);
|
self.print_margin(&mut text, symbol_diff, rect);
|
||||||
prev_margin_text = Some(text);
|
prev_margin_text = Some(text);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let max_scroll_x =
|
let max_scroll_x =
|
||||||
@@ -379,7 +379,6 @@ impl UiView for FunctionDiffUi {
|
|||||||
}
|
}
|
||||||
// Reload
|
// Reload
|
||||||
KeyCode::Char('r') => {
|
KeyCode::Char('r') => {
|
||||||
result.redraw = true;
|
|
||||||
return EventControlFlow::Reload;
|
return EventControlFlow::Reload;
|
||||||
}
|
}
|
||||||
// Scroll right
|
// Scroll right
|
||||||
@@ -401,7 +400,6 @@ impl UiView for FunctionDiffUi {
|
|||||||
FunctionRelocDiffs::DataValue => FunctionRelocDiffs::All,
|
FunctionRelocDiffs::DataValue => FunctionRelocDiffs::All,
|
||||||
FunctionRelocDiffs::All => FunctionRelocDiffs::None,
|
FunctionRelocDiffs::All => FunctionRelocDiffs::None,
|
||||||
};
|
};
|
||||||
result.redraw = true;
|
|
||||||
return EventControlFlow::Reload;
|
return EventControlFlow::Reload;
|
||||||
}
|
}
|
||||||
// Toggle three-way diff
|
// Toggle three-way diff
|
||||||
@@ -450,11 +448,11 @@ impl UiView for FunctionDiffUi {
|
|||||||
|
|
||||||
fn reload(&mut self, state: &AppState) -> Result<()> {
|
fn reload(&mut self, state: &AppState) -> Result<()> {
|
||||||
let left_sym =
|
let left_sym =
|
||||||
state.left_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name));
|
state.left_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
|
||||||
let right_sym =
|
let right_sym =
|
||||||
state.right_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name));
|
state.right_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
|
||||||
let prev_sym =
|
let prev_sym =
|
||||||
state.prev_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name));
|
state.prev_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
|
||||||
self.num_rows = match (
|
self.num_rows = match (
|
||||||
get_symbol(state.left_obj.as_ref(), left_sym),
|
get_symbol(state.left_obj.as_ref(), left_sym),
|
||||||
get_symbol(state.right_obj.as_ref(), right_sym),
|
get_symbol(state.right_obj.as_ref(), right_sym),
|
||||||
@@ -538,15 +536,16 @@ impl FunctionDiffUi {
|
|||||||
let label_text = match segment.text {
|
let label_text = match segment.text {
|
||||||
DiffText::Basic(text) => text.to_string(),
|
DiffText::Basic(text) => text.to_string(),
|
||||||
DiffText::Line(num) => format!("{num} "),
|
DiffText::Line(num) => format!("{num} "),
|
||||||
DiffText::Address(addr) => format!("{:x}:", addr),
|
DiffText::Address(addr) => format!("{addr:x}:"),
|
||||||
DiffText::Opcode(mnemonic, _op) => format!("{mnemonic} "),
|
DiffText::Opcode(mnemonic, _op) => format!("{mnemonic} "),
|
||||||
DiffText::Argument(arg) => arg.to_string(),
|
DiffText::Argument(arg) => arg.to_string(),
|
||||||
DiffText::BranchDest(addr) => format!("{addr:x}"),
|
DiffText::BranchDest(addr) => format!("{addr:x}"),
|
||||||
|
DiffText::BranchArrow(_) => " ~> ".to_string(),
|
||||||
DiffText::Symbol(sym) => {
|
DiffText::Symbol(sym) => {
|
||||||
sym.demangled_name.as_ref().unwrap_or(&sym.name).clone()
|
sym.demangled_name.as_ref().unwrap_or(&sym.name).clone()
|
||||||
}
|
}
|
||||||
DiffText::Addend(addend) => match addend.cmp(&0i64) {
|
DiffText::Addend(addend) => match addend.cmp(&0i64) {
|
||||||
Ordering::Greater => format!("+{:#x}", addend),
|
Ordering::Greater => format!("+{addend:#x}"),
|
||||||
Ordering::Less => format!("-{:#x}", -addend),
|
Ordering::Less => format!("-{:#x}", -addend),
|
||||||
_ => String::new(),
|
_ => String::new(),
|
||||||
},
|
},
|
||||||
@@ -561,15 +560,18 @@ impl FunctionDiffUi {
|
|||||||
let len = label_text.len();
|
let len = label_text.len();
|
||||||
let highlighted =
|
let highlighted =
|
||||||
highlight_kind != HighlightKind::None && *highlight == highlight_kind;
|
highlight_kind != HighlightKind::None && *highlight == highlight_kind;
|
||||||
if let Some((cx, cy)) = result.click_xy {
|
if let Some((cx, cy)) = result.click_xy
|
||||||
if cx >= sx && cx < sx + len as u16 && cy == sy {
|
&& cx >= sx
|
||||||
new_highlight = Some(highlight_kind);
|
&& cx < sx + len as u16
|
||||||
}
|
&& cy == sy
|
||||||
|
{
|
||||||
|
new_highlight = Some(highlight_kind);
|
||||||
}
|
}
|
||||||
let mut style = Style::new().fg(match segment.color {
|
let mut style = Style::new().fg(match segment.color {
|
||||||
DiffTextColor::Normal => Color::Gray,
|
DiffTextColor::Normal => Color::Gray,
|
||||||
DiffTextColor::Dim => Color::DarkGray,
|
DiffTextColor::Dim => Color::DarkGray,
|
||||||
DiffTextColor::Bright => Color::White,
|
DiffTextColor::Bright => Color::White,
|
||||||
|
DiffTextColor::DataFlow => Color::LightCyan,
|
||||||
DiffTextColor::Replace => Color::Cyan,
|
DiffTextColor::Replace => Color::Cyan,
|
||||||
DiffTextColor::Delete => Color::Red,
|
DiffTextColor::Delete => Color::Red,
|
||||||
DiffTextColor::Insert => Color::Green,
|
DiffTextColor::Insert => Color::Green,
|
||||||
@@ -650,12 +652,3 @@ fn get_symbol(
|
|||||||
let sym = sym?;
|
let sym = sym?;
|
||||||
Some((obj, sym, &diff.symbols[sym]))
|
Some((obj, sym, &diff.symbols[sym]))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_function(obj: &Object, name: &str) -> Option<usize> {
|
|
||||||
for (symbol_idx, symbol) in obj.symbols.iter().enumerate() {
|
|
||||||
if symbol.name == name {
|
|
||||||
return Some(symbol_idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#![allow(unused)] // TODO
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossterm::event::Event;
|
use crossterm::event::Event;
|
||||||
use ratatui::Frame;
|
use ratatui::Frame;
|
||||||
|
|||||||
+52
-37
@@ -27,6 +27,7 @@ all = [
|
|||||||
"mips",
|
"mips",
|
||||||
"ppc",
|
"ppc",
|
||||||
"x86",
|
"x86",
|
||||||
|
"superh"
|
||||||
]
|
]
|
||||||
# Implicit, used to check if any arch is enabled
|
# Implicit, used to check if any arch is enabled
|
||||||
any-arch = [
|
any-arch = [
|
||||||
@@ -40,6 +41,8 @@ any-arch = [
|
|||||||
"dep:regex",
|
"dep:regex",
|
||||||
"dep:similar",
|
"dep:similar",
|
||||||
"dep:syn",
|
"dep:syn",
|
||||||
|
"dep:encoding_rs",
|
||||||
|
"demangler",
|
||||||
]
|
]
|
||||||
bindings = [
|
bindings = [
|
||||||
"dep:prost",
|
"dep:prost",
|
||||||
@@ -60,7 +63,10 @@ config = [
|
|||||||
"dep:semver",
|
"dep:semver",
|
||||||
"dep:typed-path",
|
"dep:typed-path",
|
||||||
]
|
]
|
||||||
dwarf = ["dep:gimli"]
|
dwarf = [
|
||||||
|
"dep:gimli",
|
||||||
|
"dep:typed-arena",
|
||||||
|
]
|
||||||
serde = [
|
serde = [
|
||||||
"dep:pbjson",
|
"dep:pbjson",
|
||||||
"dep:pbjson-build",
|
"dep:pbjson-build",
|
||||||
@@ -75,41 +81,45 @@ std = [
|
|||||||
"object/std",
|
"object/std",
|
||||||
"prost?/std",
|
"prost?/std",
|
||||||
"serde?/std",
|
"serde?/std",
|
||||||
|
"similar?/std",
|
||||||
|
"typed-arena?/std",
|
||||||
"typed-path?/std",
|
"typed-path?/std",
|
||||||
"dep:filetime",
|
"dep:filetime",
|
||||||
"dep:memmap2",
|
"dep:memmap2",
|
||||||
]
|
]
|
||||||
mips = [
|
mips = [
|
||||||
"any-arch",
|
"any-arch",
|
||||||
"dep:cpp_demangle",
|
|
||||||
"dep:cwdemangle",
|
|
||||||
"dep:rabbitizer",
|
"dep:rabbitizer",
|
||||||
]
|
]
|
||||||
ppc = [
|
ppc = [
|
||||||
"any-arch",
|
"any-arch",
|
||||||
"dep:cwdemangle",
|
|
||||||
"dep:cwextab",
|
"dep:cwextab",
|
||||||
"dep:ppc750cl",
|
"dep:powerpc",
|
||||||
"dep:rlwinmdec",
|
"dep:rlwinmdec",
|
||||||
]
|
]
|
||||||
x86 = [
|
x86 = [
|
||||||
"any-arch",
|
"any-arch",
|
||||||
"dep:cpp_demangle",
|
|
||||||
"dep:iced-x86",
|
"dep:iced-x86",
|
||||||
"dep:msvc-demangler",
|
|
||||||
]
|
]
|
||||||
arm = [
|
arm = [
|
||||||
"any-arch",
|
"any-arch",
|
||||||
"dep:arm-attr",
|
"dep:arm-attr",
|
||||||
"dep:cpp_demangle",
|
|
||||||
"dep:unarm",
|
"dep:unarm",
|
||||||
]
|
]
|
||||||
arm64 = [
|
arm64 = [
|
||||||
"any-arch",
|
"any-arch",
|
||||||
"dep:cpp_demangle",
|
|
||||||
"dep:yaxpeax-arch",
|
"dep:yaxpeax-arch",
|
||||||
"dep:yaxpeax-arm",
|
"dep:yaxpeax-arm",
|
||||||
]
|
]
|
||||||
|
superh = [
|
||||||
|
"any-arch",
|
||||||
|
]
|
||||||
|
demangler = [
|
||||||
|
"dep:cpp_demangle",
|
||||||
|
"dep:cwdemangle",
|
||||||
|
"dep:gnuv2_demangle",
|
||||||
|
"dep:msvc-demangler",
|
||||||
|
]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["all"]
|
features = ["all"]
|
||||||
@@ -117,18 +127,18 @@ features = ["all"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1.0", default-features = false }
|
anyhow = { version = "1.0", default-features = false }
|
||||||
filetime = { version = "0.2", optional = true }
|
filetime = { version = "0.2", optional = true }
|
||||||
flagset = { version = "0.4", default-features = false, optional = true, git = "https://github.com/enarx/flagset.git", rev = "a1fe9369b3741e43fec45da1998e83b9d78966a2" }
|
flagset = { version = "0.4", default-features = false, optional = true }
|
||||||
itertools = { version = "0.14", default-features = false, features = ["use_alloc"] }
|
itertools = { version = "0.15", default-features = false, features = ["use_alloc"] }
|
||||||
log = { version = "0.4", default-features = false, optional = true }
|
log = { version = "0.4", default-features = false, optional = true }
|
||||||
memmap2 = { version = "0.9", optional = true }
|
memmap2 = { version = "0.9", optional = true }
|
||||||
num-traits = { version = "0.2", default-features = false, optional = true }
|
num-traits = { version = "0.2", default-features = false, optional = true }
|
||||||
object = { version = "0.36", default-features = false, features = ["read_core", "elf", "pe"] }
|
object = { version = "0.39.1", default-features = false, features = ["read_core", "elf", "coff"] }
|
||||||
pbjson = { version = "0.7", default-features = false, optional = true }
|
pbjson = { version = "0.9", default-features = false, optional = true }
|
||||||
prost = { version = "0.13", default-features = false, features = ["prost-derive"], optional = true }
|
prost = { version = "0.14", default-features = false, features = ["derive"], optional = true }
|
||||||
regex = { version = "1.11", default-features = false, features = [], optional = true }
|
regex = { version = "1.12", default-features = false, features = [], optional = true }
|
||||||
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
|
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
|
||||||
similar = { version = "2.7", default-features = false, optional = true, git = "https://github.com/encounter/similar.git", branch = "no_std" }
|
similar = { version = "3.1", default-features = false, features = ["hashbrown"], optional = true }
|
||||||
typed-path = { version = "0.10", default-features = false, optional = true }
|
typed-path = { version = "0.12", default-features = false, optional = true }
|
||||||
|
|
||||||
# config
|
# config
|
||||||
globset = { version = "0.4", default-features = false, optional = true }
|
globset = { version = "0.4", default-features = false, optional = true }
|
||||||
@@ -136,24 +146,22 @@ semver = { version = "1.0", default-features = false, optional = true }
|
|||||||
serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true }
|
serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true }
|
||||||
|
|
||||||
# dwarf
|
# dwarf
|
||||||
gimli = { version = "0.31", default-features = false, features = ["read"], optional = true }
|
gimli = { version = "0.34.0", default-features = false, features = ["read"], optional = true }
|
||||||
|
typed-arena = { version = "2.0", default-features = false, optional = true }
|
||||||
|
|
||||||
# ppc
|
# ppc
|
||||||
cwdemangle = { version = "1.0", optional = true }
|
cwextab = { version = "1.1", optional = true }
|
||||||
cwextab = { version = "1.0", optional = true, git = "https://github.com/CelestialAmber/cwextab.git" }
|
powerpc = { version = "0.4", optional = true }
|
||||||
ppc750cl = { version = "0.3", optional = true }
|
rlwinmdec = { version = "1.1", optional = true }
|
||||||
rlwinmdec = { version = "1.1", optional = true, git = "https://github.com/CelestialAmber/rlwinmdec.git" }
|
|
||||||
|
|
||||||
# mips
|
# mips
|
||||||
rabbitizer = { git = "https://github.com/Decompollaborate/rabbitizer.git", branch = "🦀", default-features = false, features = ["all_extensions"], optional = true }
|
rabbitizer = { version = "2.0.0-alpha.9", default-features = false, features = ["all_extensions"], optional = true }
|
||||||
|
|
||||||
# x86
|
# x86
|
||||||
cpp_demangle = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
|
|
||||||
iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true }
|
iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true }
|
||||||
msvc-demangler = { version = "0.11", optional = true }
|
|
||||||
|
|
||||||
# arm
|
# arm
|
||||||
unarm = { version = "1.7", optional = true }
|
unarm = { version = "2.1", optional = true }
|
||||||
arm-attr = { version = "0.2", optional = true }
|
arm-attr = { version = "0.2", optional = true }
|
||||||
|
|
||||||
# arm64
|
# arm64
|
||||||
@@ -161,31 +169,38 @@ yaxpeax-arch = { version = "0.3", default-features = false, optional = true }
|
|||||||
yaxpeax-arm = { version = "0.3", default-features = false, optional = true }
|
yaxpeax-arm = { version = "0.3", default-features = false, optional = true }
|
||||||
|
|
||||||
# build
|
# build
|
||||||
notify = { version = "8.0.0", optional = true }
|
notify = { version = "8.2.0", optional = true }
|
||||||
notify-debouncer-full = { version = "0.5.0", optional = true }
|
notify-debouncer-full = { version = "0.6.0", optional = true }
|
||||||
shell-escape = { version = "0.1", optional = true }
|
shell-escape = { version = "0.1", optional = true }
|
||||||
tempfile = { version = "3.17", optional = true }
|
tempfile = { version = "3.23", optional = true }
|
||||||
time = { version = "0.3", optional = true }
|
time = { version = "0.3", optional = true }
|
||||||
|
encoding_rs = { version = "0.8.35", optional = true }
|
||||||
|
|
||||||
|
# demangler
|
||||||
|
cpp_demangle = { version = "0.5", optional = true, default-features = false, features = ["alloc"] }
|
||||||
|
cwdemangle = { version = "1.0", optional = true }
|
||||||
|
gnuv2_demangle = { version = "0.4", optional = true }
|
||||||
|
msvc-demangler = { version = "0.11", optional = true }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winapi = { version = "0.3", optional = true }
|
winapi = { version = "0.3", optional = true, features = ["winbase"] }
|
||||||
|
|
||||||
# For Linux static binaries, use rustls
|
# For Linux static binaries, use rustls
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "rustls-tls"], optional = true }
|
reqwest = { version = "0.13", default-features = false, features = ["blocking", "json", "multipart", "rustls"], optional = true }
|
||||||
self_update = { version = "0.42", default-features = false, features = ["rustls"], optional = true }
|
self_update = { version = "1.0.0-rc.3", default-features = false, features = ["reqwest", "rustls", "github"], optional = true }
|
||||||
|
|
||||||
# For all other platforms, use native TLS
|
# For all other platforms, use native TLS
|
||||||
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
||||||
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "default-tls"], optional = true }
|
reqwest = { version = "0.13", default-features = false, features = ["blocking", "json", "multipart", "default-tls"], optional = true }
|
||||||
self_update = { version = "0.42", optional = true }
|
self_update = { version = "1.0.0-rc.3", default-features = false, features = ["reqwest", "native-tls", "github"], optional = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
heck = { version = "0.5", optional = true }
|
heck = { version = "0.5", optional = true }
|
||||||
pbjson-build = { version = "0.7", optional = true }
|
pbjson-build = { version = "0.9", optional = true }
|
||||||
prettyplease = { version = "0.2", optional = true }
|
prettyplease = { version = "0.2", optional = true }
|
||||||
proc-macro2 = { version = "1.0", optional = true }
|
proc-macro2 = { version = "1.0", optional = true }
|
||||||
prost-build = { version = "0.13", optional = true }
|
prost-build = { version = "0.14", optional = true }
|
||||||
quote = { version = "1.0", optional = true }
|
quote = { version = "1.0", optional = true }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = { version = "1.0" }
|
serde_json = { version = "1.0" }
|
||||||
@@ -194,4 +209,4 @@ syn = { version = "2.0", optional = true }
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# Enable all features for tests
|
# Enable all features for tests
|
||||||
objdiff-core = { path = ".", features = ["all"] }
|
objdiff-core = { path = ".", features = ["all"] }
|
||||||
insta = "1.42"
|
insta = "1.43"
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ objdiff-core contains the core functionality of [objdiff](https://github.com/enc
|
|||||||
## Crate feature flags
|
## Crate feature flags
|
||||||
|
|
||||||
- **`all`**: Enables all main features.
|
- **`all`**: Enables all main features.
|
||||||
|
- **`bindings`**: Enables serialization and deserialization of objdiff data structures.
|
||||||
- **`config`**: Enables objdiff configuration file support.
|
- **`config`**: Enables objdiff configuration file support.
|
||||||
- **`dwarf`**: Enables extraction of line number information from DWARF debug sections.
|
- **`dwarf`**: Enables extraction of line number information from DWARF debug sections.
|
||||||
- **`mips`**: Enables the MIPS backend powered by [rabbitizer](https://github.com/Decompollaborate/rabbitizer). (Note: C library with Rust bindings)
|
|
||||||
- **`ppc`**: Enables the PowerPC backend powered by [ppc750cl](https://github.com/encounter/ppc750cl).
|
|
||||||
- **`x86`**: Enables the x86 backend powered by [iced-x86](https://crates.io/crates/iced-x86).
|
|
||||||
- **`arm`**: Enables the ARM backend powered by [unarm](https://github.com/AetiasHax/unarm).
|
|
||||||
- **`arm64`**: Enables the ARM64 backend powered by [yaxpeax-arm](https://github.com/iximeow/yaxpeax-arm).
|
- **`arm64`**: Enables the ARM64 backend powered by [yaxpeax-arm](https://github.com/iximeow/yaxpeax-arm).
|
||||||
- **`bindings`**: Enables serialization and deserialization of objdiff data structures.
|
- **`arm`**: Enables the ARM backend powered by [unarm](https://github.com/AetiasHax/unarm).
|
||||||
|
- **`mips`**: Enables the MIPS backend powered by [rabbitizer](https://github.com/Decompollaborate/rabbitizer).
|
||||||
|
- **`ppc`**: Enables the PowerPC backend powered by [powerpc](https://github.com/encounter/powerpc-rs).
|
||||||
|
- **`superh`**: Enables the SuperH backend powered by an included disassembler.
|
||||||
|
- **`x86`**: Enables the x86 backend powered by [iced-x86](https://crates.io/crates/iced-x86).
|
||||||
|
|||||||
+165
-11
@@ -25,6 +25,56 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "demangler",
|
||||||
|
"type": "choice",
|
||||||
|
"default": "auto",
|
||||||
|
"name": "Demangler",
|
||||||
|
"description": "Which demangler should be used to demangle each symbol.",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"value": "auto",
|
||||||
|
"name": "Auto",
|
||||||
|
"description": "Try to automatically guess the mangling format."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "none",
|
||||||
|
"name": "None",
|
||||||
|
"description": "Disable demangling."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "codewarrior",
|
||||||
|
"name": "CodeWarrior"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "itanium",
|
||||||
|
"name": "Itanium"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "msvc",
|
||||||
|
"name": "MSVC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "gnu_legacy",
|
||||||
|
"name": "GNU g++ (Legacy)",
|
||||||
|
"description": "Use the old GNU mangling ABI. Used up to g++ 2.9.x"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "analyzeDataFlow",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"name": "(Experimental) Perform data flow analysis",
|
||||||
|
"description": "Use data flow analysis to display known information about register contents where possible"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "showDataFlow",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"name": "Show data flow",
|
||||||
|
"description": "Show data flow analysis results in place of register name where present"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "spaceBetweenArgs",
|
"id": "spaceBetweenArgs",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
@@ -32,6 +82,27 @@
|
|||||||
"name": "Space between args",
|
"name": "Space between args",
|
||||||
"description": "Adds a space between arguments in the diff output."
|
"description": "Adds a space between arguments in the diff output."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "showSymbolSizes",
|
||||||
|
"type": "choice",
|
||||||
|
"default": "off",
|
||||||
|
"name": "Show symbol sizes",
|
||||||
|
"description": "Shows symbol sizes in the symbol view.",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"value": "off",
|
||||||
|
"name": "Off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "hex",
|
||||||
|
"name": "Hex"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "decimal",
|
||||||
|
"name": "Decimal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "combineDataSections",
|
"id": "combineDataSections",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
@@ -46,6 +117,51 @@
|
|||||||
"name": "Combine text sections",
|
"name": "Combine text sections",
|
||||||
"description": "Combines all text sections into one."
|
"description": "Combines all text sections into one."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "preferredStringEncoding",
|
||||||
|
"type": "choice",
|
||||||
|
"default": "auto",
|
||||||
|
"name": "Preferred string encoding",
|
||||||
|
"description": "Which encoding to use when diffing string literals used in functions.",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"value": "auto",
|
||||||
|
"name": "Auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "ascii",
|
||||||
|
"name": "ASCII"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "utf_8",
|
||||||
|
"name": "UTF-8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "shift_jis",
|
||||||
|
"name": "Shift JIS"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "windows_1252",
|
||||||
|
"name": "Windows-1252"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "euc_jp",
|
||||||
|
"name": "EUC-JP"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "big5",
|
||||||
|
"name": "Big5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "utf_16be",
|
||||||
|
"name": "UTF-16BE"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "utf_16le",
|
||||||
|
"name": "UTF-16LE"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "arm.archVersion",
|
"id": "arm.archVersion",
|
||||||
"type": "choice",
|
"type": "choice",
|
||||||
@@ -57,20 +173,43 @@
|
|||||||
"value": "auto",
|
"value": "auto",
|
||||||
"name": "Auto"
|
"name": "Auto"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"value": "v4",
|
||||||
|
"name": "ARMv4"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"value": "v4t",
|
"value": "v4t",
|
||||||
"name": "ARMv4T (GBA)"
|
"name": "ARMv4T (GBA)"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"value": "v5t",
|
||||||
|
"name": "ARMv5T"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"value": "v5te",
|
"value": "v5te",
|
||||||
"name": "ARMv5TE (DS)"
|
"name": "ARMv5TE (DS)"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"value": "v5tej",
|
||||||
|
"name": "ARMv5TEJ"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "v6",
|
||||||
|
"name": "ARMv6"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"value": "v6k",
|
"value": "v6k",
|
||||||
"name": "ARMv6K (3DS)"
|
"name": "ARMv6K (3DS)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "arm.vfpV2",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"name": "VFPv2 instructions",
|
||||||
|
"description": "Adds floating-point instructions from VFPv2."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "arm.unifiedSyntax",
|
"id": "arm.unifiedSyntax",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
@@ -144,6 +283,10 @@
|
|||||||
"value": "o32",
|
"value": "o32",
|
||||||
"name": "O32"
|
"name": "O32"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"value": "o64",
|
||||||
|
"name": "O64"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"value": "n32",
|
"value": "n32",
|
||||||
"name": "N32"
|
"name": "N32"
|
||||||
@@ -151,6 +294,14 @@
|
|||||||
{
|
{
|
||||||
"value": "n64",
|
"value": "n64",
|
||||||
"name": "N64"
|
"name": "N64"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "eabi32",
|
||||||
|
"name": "eabi32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "eabi64",
|
||||||
|
"name": "eabi64"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -194,6 +345,13 @@
|
|||||||
"name": "Register '$' prefix",
|
"name": "Register '$' prefix",
|
||||||
"description": "Display MIPS register names with a '$' prefix."
|
"description": "Display MIPS register names with a '$' prefix."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "mips.prodgSnAsInvertedRegs",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"name": "ProDG SN AS VU register order",
|
||||||
|
"description": "Changes the register order for specific R5900EE instructions to match the syntax used by the SN AS from ProDG (-snas flag)."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "ppc.calculatePoolRelocations",
|
"id": "ppc.calculatePoolRelocations",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
@@ -232,7 +390,10 @@
|
|||||||
"id": "general",
|
"id": "general",
|
||||||
"name": "General",
|
"name": "General",
|
||||||
"properties": [
|
"properties": [
|
||||||
|
"preferredStringEncoding",
|
||||||
"functionRelocDiffs",
|
"functionRelocDiffs",
|
||||||
|
"demangler",
|
||||||
|
"showSymbolSizes",
|
||||||
"spaceBetweenArgs",
|
"spaceBetweenArgs",
|
||||||
"combineDataSections",
|
"combineDataSections",
|
||||||
"combineTextSections"
|
"combineTextSections"
|
||||||
@@ -243,6 +404,7 @@
|
|||||||
"name": "ARM",
|
"name": "ARM",
|
||||||
"properties": [
|
"properties": [
|
||||||
"arm.archVersion",
|
"arm.archVersion",
|
||||||
|
"arm.vfpV2",
|
||||||
"arm.unifiedSyntax",
|
"arm.unifiedSyntax",
|
||||||
"arm.avRegisters",
|
"arm.avRegisters",
|
||||||
"arm.r9Usage",
|
"arm.r9Usage",
|
||||||
@@ -254,25 +416,17 @@
|
|||||||
{
|
{
|
||||||
"id": "mips",
|
"id": "mips",
|
||||||
"name": "MIPS",
|
"name": "MIPS",
|
||||||
"properties": [
|
"properties": ["mips.abi", "mips.instrCategory", "mips.registerPrefix", "mips.prodgSnAsInvertedRegs"]
|
||||||
"mips.abi",
|
|
||||||
"mips.instrCategory",
|
|
||||||
"mips.registerPrefix"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "ppc",
|
"id": "ppc",
|
||||||
"name": "PowerPC",
|
"name": "PowerPC",
|
||||||
"properties": [
|
"properties": ["ppc.calculatePoolRelocations", "analyzeDataFlow"]
|
||||||
"ppc.calculatePoolRelocations"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "x86",
|
"id": "x86",
|
||||||
"name": "x86",
|
"name": "x86",
|
||||||
"properties": [
|
"properties": ["x86.formatter"]
|
||||||
"x86.formatter"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,10 +60,10 @@ pub struct ConfigGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build_doc(name: &str, description: Option<&str>) -> TokenStream {
|
fn build_doc(name: &str, description: Option<&str>) -> TokenStream {
|
||||||
let mut doc = format!(" {}", name);
|
let mut doc = format!(" {name}");
|
||||||
let mut out = quote! { #[doc = #doc] };
|
let mut out = quote! { #[doc = #doc] };
|
||||||
if let Some(description) = description {
|
if let Some(description) = description {
|
||||||
doc = format!(" {}", description);
|
doc = format!(" {description}");
|
||||||
out.extend(quote! { #[doc = ""] });
|
out.extend(quote! { #[doc = ""] });
|
||||||
out.extend(quote! { #[doc = #doc] });
|
out.extend(quote! { #[doc = #doc] });
|
||||||
}
|
}
|
||||||
@@ -443,9 +443,9 @@ pub fn generate_diff_config() {
|
|||||||
}
|
}
|
||||||
impl core::fmt::Display for ConfigPropertyValue {
|
impl core::fmt::Display for ConfigPropertyValue {
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
match self {
|
match *self {
|
||||||
ConfigPropertyValue::Boolean(value) => write!(f, "{}", value),
|
ConfigPropertyValue::Boolean(value) => write!(f, "{value}"),
|
||||||
ConfigPropertyValue::Choice(value) => write!(f, "{}", value),
|
ConfigPropertyValue::Choice(value) => f.write_str(value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+127
-121
@@ -2,107 +2,148 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package objdiff.diff;
|
package objdiff.diff;
|
||||||
|
|
||||||
// A symbol
|
// Top-level diff result containing left and right object diffs
|
||||||
message Symbol {
|
message DiffResult {
|
||||||
// Name of the symbol
|
optional DiffObject left = 1;
|
||||||
|
optional DiffObject right = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diff information for a single object file
|
||||||
|
message DiffObject {
|
||||||
|
repeated DiffSection sections = 1;
|
||||||
|
repeated DiffSymbol symbols = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diff information for a section
|
||||||
|
message DiffSection {
|
||||||
|
string name = 1;
|
||||||
|
DiffSectionKind kind = 2;
|
||||||
|
uint64 size = 3;
|
||||||
|
uint64 address = 4;
|
||||||
|
optional float match_percent = 5;
|
||||||
|
repeated DiffDataSegment data_diff = 6;
|
||||||
|
repeated DiffDataRelocation reloc_diff = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DiffSectionKind {
|
||||||
|
SECTION_UNKNOWN = 0;
|
||||||
|
SECTION_CODE = 1;
|
||||||
|
SECTION_DATA = 2;
|
||||||
|
SECTION_BSS = 3;
|
||||||
|
SECTION_COMMON = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symbol with diff information
|
||||||
|
message DiffSymbol {
|
||||||
string name = 1;
|
string name = 1;
|
||||||
// Demangled name of the symbol
|
|
||||||
optional string demangled_name = 2;
|
optional string demangled_name = 2;
|
||||||
// Symbol address
|
|
||||||
uint64 address = 3;
|
uint64 address = 3;
|
||||||
// Symbol size
|
|
||||||
uint64 size = 4;
|
uint64 size = 4;
|
||||||
// Bitmask of SymbolFlag
|
DiffSymbolFlags flags = 5;
|
||||||
uint32 flags = 5;
|
DiffSymbolKind kind = 10;
|
||||||
|
// The symbol index in the _other_ object that this symbol was diffed against
|
||||||
|
optional uint32 target_symbol = 6;
|
||||||
|
optional float match_percent = 7;
|
||||||
|
// Instruction diff rows (for code symbols)
|
||||||
|
repeated DiffInstructionRow instructions = 8;
|
||||||
|
// Data diff (for data symbols)
|
||||||
|
repeated DiffDataSegment data_diff = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Symbol visibility flags
|
enum DiffSymbolKind {
|
||||||
enum SymbolFlag {
|
SYMBOL_UNKNOWN = 0;
|
||||||
SYMBOL_NONE = 0;
|
SYMBOL_FUNCTION = 1;
|
||||||
SYMBOL_GLOBAL = 1;
|
SYMBOL_OBJECT = 2;
|
||||||
SYMBOL_LOCAL = 2;
|
SYMBOL_SECTION = 3;
|
||||||
SYMBOL_WEAK = 4;
|
|
||||||
SYMBOL_COMMON = 8;
|
|
||||||
SYMBOL_HIDDEN = 16;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A single parsed instruction
|
// Symbol flags
|
||||||
message Instruction {
|
message DiffSymbolFlags {
|
||||||
// Instruction address
|
bool global = 1;
|
||||||
|
bool local = 2;
|
||||||
|
bool weak = 3;
|
||||||
|
bool common = 4;
|
||||||
|
bool hidden = 5;
|
||||||
|
bool ignored = 6;
|
||||||
|
bool size_inferred = 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A single instruction diff row
|
||||||
|
message DiffInstructionRow {
|
||||||
|
DiffKind diff_kind = 1;
|
||||||
|
optional DiffInstruction instruction = 2;
|
||||||
|
repeated DiffInstructionArgDiff arg_diff = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parsed instruction information
|
||||||
|
message DiffInstruction {
|
||||||
uint64 address = 1;
|
uint64 address = 1;
|
||||||
// Instruction size
|
|
||||||
uint32 size = 2;
|
uint32 size = 2;
|
||||||
// Instruction opcode
|
// Formatted instruction string
|
||||||
uint32 opcode = 3;
|
string formatted = 3;
|
||||||
// Instruction mnemonic
|
// Formatted instruction parts
|
||||||
string mnemonic = 4;
|
repeated DiffInstructionPart parts = 4;
|
||||||
// Instruction formatted string
|
// Relocation information (if present)
|
||||||
string formatted = 5;
|
optional DiffRelocation relocation = 5;
|
||||||
// Original (unsimplified) instruction string
|
// Branch destination address
|
||||||
optional string original = 6;
|
optional uint64 branch_dest = 6;
|
||||||
// Instruction arguments
|
// Source line number (if present)
|
||||||
repeated Argument arguments = 7;
|
optional uint32 line_number = 7;
|
||||||
// Instruction relocation
|
}
|
||||||
optional Relocation relocation = 8;
|
|
||||||
// Instruction branch destination
|
// An instruction part
|
||||||
optional uint64 branch_dest = 9;
|
message DiffInstructionPart {
|
||||||
// Instruction line number
|
oneof part {
|
||||||
optional uint32 line_number = 10;
|
// Basic text (whitespace, punctuation, etc.)
|
||||||
|
string basic = 1;
|
||||||
|
// Opcode/mnemonic
|
||||||
|
DiffOpcode opcode = 2;
|
||||||
|
// Argument
|
||||||
|
DiffInstructionArg arg = 3;
|
||||||
|
// Separator between arguments (comma)
|
||||||
|
bool separator = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message DiffOpcode {
|
||||||
|
// Mnemonic string
|
||||||
|
string mnemonic = 1;
|
||||||
|
// Base opcode ID
|
||||||
|
uint32 opcode = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// An instruction argument
|
// An instruction argument
|
||||||
message Argument {
|
message DiffInstructionArg {
|
||||||
oneof value {
|
oneof arg {
|
||||||
// Plain text
|
// Signed immediate value
|
||||||
string plain_text = 1;
|
sint64 signed = 1;
|
||||||
// Value
|
// Unsigned immediate value
|
||||||
ArgumentValue argument = 2;
|
|
||||||
// Relocation
|
|
||||||
ArgumentRelocation relocation = 3;
|
|
||||||
// Branch destination
|
|
||||||
uint64 branch_dest = 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// An instruction argument value
|
|
||||||
message ArgumentValue {
|
|
||||||
oneof value {
|
|
||||||
// Signed integer
|
|
||||||
int64 signed = 1;
|
|
||||||
// Unsigned integer
|
|
||||||
uint64 unsigned = 2;
|
uint64 unsigned = 2;
|
||||||
// Opaque value
|
// Opaque string value (register names, etc.)
|
||||||
string opaque = 3;
|
string opaque = 3;
|
||||||
|
// Relocation
|
||||||
|
bool reloc = 4;
|
||||||
|
// Branch destination address
|
||||||
|
uint64 branch_dest = 5;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marker type for relocation arguments
|
// Relocation information
|
||||||
message ArgumentRelocation {
|
message DiffRelocation {
|
||||||
}
|
|
||||||
|
|
||||||
message Relocation {
|
|
||||||
uint32 type = 1;
|
uint32 type = 1;
|
||||||
string type_name = 2;
|
string type_name = 2;
|
||||||
RelocationTarget target = 3;
|
uint32 target_symbol = 3;
|
||||||
|
int64 addend = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RelocationTarget {
|
// Argument diff information
|
||||||
uint32 symbol_index = 1;
|
message DiffInstructionArgDiff {
|
||||||
int64 addend = 2;
|
// If set, this argument differs from the other side.
|
||||||
}
|
// The value is a rotating index for coloring.
|
||||||
|
|
||||||
message InstructionDiffRow {
|
|
||||||
DiffKind diff_kind = 1;
|
|
||||||
optional Instruction instruction = 2;
|
|
||||||
optional InstructionBranchFrom branch_from = 3;
|
|
||||||
optional InstructionBranchTo branch_to = 4;
|
|
||||||
repeated ArgumentDiff arg_diff = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ArgumentDiff {
|
|
||||||
optional uint32 diff_index = 1;
|
optional uint32 diff_index = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Diff kind for instructions and data
|
||||||
enum DiffKind {
|
enum DiffKind {
|
||||||
DIFF_NONE = 0;
|
DIFF_NONE = 0;
|
||||||
DIFF_REPLACE = 1;
|
DIFF_REPLACE = 1;
|
||||||
@@ -112,54 +153,19 @@ enum DiffKind {
|
|||||||
DIFF_ARG_MISMATCH = 5;
|
DIFF_ARG_MISMATCH = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message InstructionBranchFrom {
|
// Data diff segment
|
||||||
repeated uint32 instruction_index = 1;
|
message DiffDataSegment {
|
||||||
uint32 branch_index = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InstructionBranchTo {
|
|
||||||
uint32 instruction_index = 1;
|
|
||||||
uint32 branch_index = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SymbolDiff {
|
|
||||||
Symbol symbol = 1;
|
|
||||||
repeated InstructionDiffRow instruction_rows = 2;
|
|
||||||
optional float match_percent = 3;
|
|
||||||
// The symbol index in the _other_ object that this symbol was diffed against
|
|
||||||
optional uint32 target_symbol = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DataDiff {
|
|
||||||
DiffKind kind = 1;
|
DiffKind kind = 1;
|
||||||
bytes data = 2;
|
bytes data = 2;
|
||||||
// May be larger than data
|
// Size may be larger than data length for BSS
|
||||||
uint64 size = 3;
|
uint64 size = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SectionDiff {
|
// Data relocation diff
|
||||||
string name = 1;
|
message DiffDataRelocation {
|
||||||
SectionKind kind = 2;
|
DiffRelocation relocation = 1;
|
||||||
uint64 size = 3;
|
DiffKind kind = 2;
|
||||||
uint64 address = 4;
|
// Address range this relocation covers
|
||||||
reserved 5;
|
uint64 start = 3;
|
||||||
repeated DataDiff data = 6;
|
uint64 end = 4;
|
||||||
optional float match_percent = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum SectionKind {
|
|
||||||
SECTION_UNKNOWN = 0;
|
|
||||||
SECTION_TEXT = 1;
|
|
||||||
SECTION_DATA = 2;
|
|
||||||
SECTION_BSS = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ObjectDiff {
|
|
||||||
repeated SectionDiff sections = 1;
|
|
||||||
repeated SymbolDiff symbols = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DiffResult {
|
|
||||||
optional ObjectDiff left = 1;
|
|
||||||
optional ObjectDiff right = 2;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user