mirror of
https://github.com/encounter/objdiff.git
synced 2025-12-15 08:06:25 +00:00
Compare commits
161 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
|
|
9b557e4c8e | ||
|
|
b9ba5796ed | ||
|
|
e101610416 | ||
|
|
196c003a92 | ||
| 7b00a9e9f2 | |||
| 311de887ec | |||
| 485b259c32 | |||
| d8fdfaa2c0 | |||
| 2612cda1fb | |||
| bc46e17824 | |||
| e735adbd3d | |||
| 6768df9d80 | |||
| eaba2391e0 | |||
| bd8f5ede23 | |||
| acf46c6b54 | |||
| 9358d8ec60 | |||
| 809e2ffddc | |||
| 87fa29e8b0 | |||
| 42d4c38079 | |||
| fa26200ed7 | |||
| a0e7f9bc37 | |||
| 5898d7aebf | |||
| ffb38d1bb0 | |||
| d56dda72f0 | |||
| c6971f3f2d | |||
| 3965a035fa | |||
| f1fc29f77e | |||
| 7c4f1c5d13 | |||
| fa4a6cadbb | |||
| 799971d54e | |||
| 8eef37e8df | |||
| 5f36916087 | |||
| ee667a2dde | |||
|
|
cf5fc54cfa | ||
| 1cdfa1e857 | |||
| 3f157f33a5 | |||
|
|
a1ea2919f8 | ||
| 9c31c82a37 | |||
| 4f34dfa194 | |||
| ae6d37a10b | |||
| 06e54f3456 | |||
| 6ed07bfaf1 | |||
| 80e939653a | |||
| 48305e0380 | |||
| 2eafbb218b | |||
| a1f2a535e5 | |||
| 8461b35cd7 | |||
| 95868f1d19 | |||
| 506c251d68 | |||
| f3c157ff06 | |||
|
|
6d3c63ccd8 | ||
| bbd8d9714f | |||
| 3c66ac3d54 | |||
| 561a9107e2 | |||
| e8de35b78e | |||
| d938988d43 | |||
|
|
3e6efb7736 | ||
|
|
674c942d7d | ||
|
|
6b7dcabbed | ||
| e202c3ef95 | |||
|
|
b7730b3d00 | ||
|
|
a4fdb61f04 | ||
|
|
2876be37a3 | ||
| 11171763eb | |||
| 6037a79ba2 | |||
| f7efe5fdff | |||
| 0692deac59 | |||
| c3e3d175c5 | |||
| c45f4bbc99 | |||
| b0c5431ac5 | |||
|
|
9ab246367b | ||
|
|
dcafe51eda | ||
| c65e87c382 | |||
| 1756b9f6c5 | |||
| 303f2938a2 | |||
| 526e031251 | |||
|
|
10b2a9c129 | ||
|
|
abe68ef2f2 | ||
|
|
304df96411 |
@@ -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"
|
|
||||||
|
|||||||
2
.editorconfig
Normal file
2
.editorconfig
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
152
.github/workflows/build.yaml
vendored
152
.github/workflows/build.yaml
vendored
@@ -4,13 +4,16 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
push:
|
push:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '*.md'
|
- "*.md"
|
||||||
- '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_TARGET_DIR: target
|
|
||||||
CARGO_INCREMENTAL: 0
|
CARGO_INCREMENTAL: 0
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -33,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-features --all-targets
|
run: cargo check --all-targets --all-features
|
||||||
- name: Cargo clippy
|
- name: Cargo clippy
|
||||||
run: cargo clippy --all-features --all-targets
|
run: cargo clippy --all-targets --all-features
|
||||||
|
|
||||||
fmt:
|
fmt:
|
||||||
name: Format
|
name: Format
|
||||||
@@ -65,13 +68,12 @@ jobs:
|
|||||||
continue-on-error: ${{ matrix.checks == 'advisories' }}
|
continue-on-error: ${{ matrix.checks == 'advisories' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: EmbarkStudios/cargo-deny-action@v1
|
- uses: EmbarkStudios/cargo-deny-action@v2
|
||||||
with:
|
with:
|
||||||
command: check ${{ matrix.checks }}
|
command: check ${{ matrix.checks }}
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: Test
|
name: Test
|
||||||
if: 'false' # No tests yet
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
platform: [ ubuntu-latest, windows-latest, macos-latest ]
|
platform: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||||
@@ -90,7 +92,7 @@ jobs:
|
|||||||
- 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 --all-features
|
run: cargo test --release --features all
|
||||||
|
|
||||||
build-cli:
|
build-cli:
|
||||||
name: Build objdiff-cli
|
name: Build objdiff-cli
|
||||||
@@ -150,7 +152,7 @@ jobs:
|
|||||||
python3 -m venv .venv
|
python3 -m venv .venv
|
||||||
. .venv/bin/activate
|
. .venv/bin/activate
|
||||||
echo PATH=$PATH >> $GITHUB_ENV
|
echo PATH=$PATH >> $GITHUB_ENV
|
||||||
pip install ziglang==0.13.0 cargo-zigbuild==0.19.1
|
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:
|
||||||
@@ -161,15 +163,15 @@ jobs:
|
|||||||
key: ${{ matrix.target }}
|
key: ${{ matrix.target }}
|
||||||
- name: Cargo build
|
- name: Cargo build
|
||||||
run: >
|
run: >
|
||||||
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
|
||||||
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
|
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
|
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
|
||||||
path: |
|
path: |
|
||||||
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
|
target/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
|
||||||
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
|
target/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
build-gui:
|
build-gui:
|
||||||
@@ -180,21 +182,26 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- platform: ubuntu-latest
|
- platform: ubuntu-latest
|
||||||
target: x86_64-unknown-linux-gnu
|
target: x86_64-unknown-linux-gnu.2.31
|
||||||
|
target_base: x86_64-unknown-linux-gnu
|
||||||
name: linux-x86_64
|
name: linux-x86_64
|
||||||
packages: libgtk-3-dev
|
packages: libgtk-3-dev
|
||||||
|
build: zigbuild
|
||||||
features: default
|
features: default
|
||||||
- platform: windows-latest
|
- platform: windows-latest
|
||||||
target: x86_64-pc-windows-msvc
|
target: x86_64-pc-windows-msvc
|
||||||
name: windows-x86_64
|
name: windows-x86_64
|
||||||
|
build: build
|
||||||
features: default
|
features: default
|
||||||
- platform: macos-latest
|
- platform: macos-latest
|
||||||
target: x86_64-apple-darwin
|
target: x86_64-apple-darwin
|
||||||
name: macos-x86_64
|
name: macos-x86_64
|
||||||
|
build: build
|
||||||
features: default
|
features: default
|
||||||
- platform: macos-latest
|
- platform: macos-latest
|
||||||
target: aarch64-apple-darwin
|
target: aarch64-apple-darwin
|
||||||
name: macos-arm64
|
name: macos-arm64
|
||||||
|
build: build
|
||||||
features: default
|
features: default
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
@@ -206,38 +213,70 @@ jobs:
|
|||||||
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@v4
|
||||||
|
- name: Install cargo-zigbuild
|
||||||
|
if: matrix.build == 'zigbuild'
|
||||||
|
run: |
|
||||||
|
python3 -m venv .venv
|
||||||
|
. .venv/bin/activate
|
||||||
|
echo PATH=$PATH >> $GITHUB_ENV
|
||||||
|
pip install ziglang==0.13.0.post1 cargo-zigbuild==0.19.8
|
||||||
- name: Setup Rust toolchain
|
- name: Setup Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
targets: ${{ matrix.target }}
|
targets: ${{ matrix.target_base || matrix.target }}
|
||||||
- name: Cache Rust workspace
|
- name: Cache Rust workspace
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
key: ${{ matrix.target }}
|
key: ${{ matrix.target }}
|
||||||
- name: Cargo build
|
- name: Cargo build
|
||||||
run: >
|
run: >
|
||||||
cargo 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@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
|
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
|
||||||
path: |
|
path: |
|
||||||
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
|
target/${{ matrix.target_base || matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
|
||||||
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
|
target/${{ matrix.target_base || matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
|
|
||||||
release:
|
build-wasm:
|
||||||
name: Release
|
name: Build objdiff-wasm
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [ build-cli, build-gui ]
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Check git tag against Cargo version
|
- name: Setup Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@nightly
|
||||||
|
with:
|
||||||
|
components: rust-src
|
||||||
|
- name: Cache Rust workspace
|
||||||
|
uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: lts/*
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm -C objdiff-wasm ci
|
||||||
|
- name: Build
|
||||||
|
run: npm -C objdiff-wasm run build
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: wasm
|
||||||
|
path: objdiff-wasm/dist/
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
check-version:
|
||||||
|
name: Check package versions
|
||||||
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [ build-cli, build-gui, build-wasm ]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Check git tag against package versions
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -eou pipefail
|
set -eou pipefail
|
||||||
@@ -249,9 +288,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
|
||||||
@@ -281,3 +335,53 @@ jobs:
|
|||||||
files: out/*
|
files: out/*
|
||||||
draft: true
|
draft: true
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
|
|
||||||
|
release-cargo:
|
||||||
|
name: Release (Cargo)
|
||||||
|
if: 'false' # TODO re-enable when all dependencies are published
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [ check-version ]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- 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@v4
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
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"
|
||||||
|
|||||||
36
.pre-commit-config.yaml
Normal file
36
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# See https://pre-commit.com for more information
|
||||||
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v5.0.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
args: [--markdown-linebreak-ext=md]
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: fix-byte-order-marker
|
||||||
|
- id: check-yaml
|
||||||
|
- id: check-added-large-files
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: cargo-fmt
|
||||||
|
name: cargo fmt
|
||||||
|
description: Run cargo fmt on all project files.
|
||||||
|
language: system
|
||||||
|
entry: cargo
|
||||||
|
args: ["+nightly", "fmt", "--all"]
|
||||||
|
pass_filenames: false
|
||||||
|
- id: cargo clippy
|
||||||
|
name: cargo clippy
|
||||||
|
description: Run cargo clippy on all project files.
|
||||||
|
language: system
|
||||||
|
entry: cargo
|
||||||
|
args: ["+nightly", "clippy", "--all-targets", "--all-features"]
|
||||||
|
pass_filenames: false
|
||||||
|
- id: cargo-deny
|
||||||
|
name: cargo deny
|
||||||
|
description: Run cargo deny on all project files.
|
||||||
|
language: system
|
||||||
|
entry: cargo
|
||||||
|
args: ["deny", "check"]
|
||||||
|
pass_filenames: false
|
||||||
|
always_run: true
|
||||||
3735
Cargo.lock
generated
3735
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -3,8 +3,9 @@ members = [
|
|||||||
"objdiff-cli",
|
"objdiff-cli",
|
||||||
"objdiff-core",
|
"objdiff-core",
|
||||||
"objdiff-gui",
|
"objdiff-gui",
|
||||||
|
"objdiff-wasm",
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "3"
|
||||||
|
|
||||||
[profile.release-lto]
|
[profile.release-lto]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
@@ -13,9 +14,9 @@ strip = "debuginfo"
|
|||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "2.4.0"
|
version = "3.0.0"
|
||||||
authors = ["Luke Street <luke@street.dev>"]
|
authors = ["Luke Street <luke@street.dev>"]
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/encounter/objdiff"
|
repository = "https://github.com/encounter/objdiff"
|
||||||
rust-version = "1.74"
|
rust-version = "1.88"
|
||||||
|
|||||||
39
README.md
39
README.md
@@ -16,11 +16,12 @@ Features:
|
|||||||
|
|
||||||
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.
|
||||||
|
|
||||||
@@ -105,6 +106,9 @@ file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it
|
|||||||
"*.txt",
|
"*.txt",
|
||||||
"*.json"
|
"*.json"
|
||||||
],
|
],
|
||||||
|
"ignore_patterns": [
|
||||||
|
"build/**/*"
|
||||||
|
],
|
||||||
"units": [
|
"units": [
|
||||||
{
|
{
|
||||||
"name": "main/MetroTRK/mslsupp",
|
"name": "main/MetroTRK/mslsupp",
|
||||||
@@ -140,18 +144,22 @@ It's unlikely you'll want to disable this, unless you're using an external tool
|
|||||||
If any of these files change, objdiff will automatically rebuild the objects and re-compare them.
|
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.
|
If not specified, objdiff will use the default patterns listed above.
|
||||||
|
|
||||||
|
`ignore_patterns` _(optional)_: A list of glob patterns to explicitly ignore when watching for changes.
|
||||||
|
([Supported syntax](https://docs.rs/globset/latest/globset/#syntax))
|
||||||
|
If not specified, objdiff will use the default patterns listed above.
|
||||||
|
|
||||||
`units` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
|
`units` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
|
||||||
|
|
||||||
> `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.
|
> `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.
|
||||||
>
|
>
|
||||||
> `target_path`: Path to the "target" or "expected" object from the project root.
|
> `target_path`: Path to the "target" or "expected" object from the project root.
|
||||||
> This object is the **intended result** of the match.
|
> This object is the **intended result** of the match.
|
||||||
>
|
>
|
||||||
> `base_path`: Path to the "base" or "actual" object from the project root.
|
> `base_path`: Path to the "base" or "actual" object from the project root.
|
||||||
> This object is built from the **current source code**.
|
> This object is built from the **current source code**.
|
||||||
>
|
>
|
||||||
> `metadata.auto_generated` _(optional)_: Hides the object from the object list, but still includes it in reports.
|
> `metadata.auto_generated` _(optional)_: Hides the object from the object list, but still includes it in reports.
|
||||||
>
|
>
|
||||||
> `metadata.complete` _(optional)_: Marks the object as "complete" (or "linked") in the object list.
|
> `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".
|
> This is useful for marking objects that are fully decompiled. A value of `false` will mark the object as "incomplete".
|
||||||
|
|
||||||
@@ -173,6 +181,21 @@ $ cargo install --locked --git https://github.com/encounter/objdiff.git objdiff-
|
|||||||
|
|
||||||
The binaries will be installed to `~/.cargo/bin` as `objdiff` and `objdiff-cli`.
|
The binaries will be installed to `~/.cargo/bin` as `objdiff` and `objdiff-cli`.
|
||||||
|
|
||||||
|
## Installing `pre-commit`
|
||||||
|
|
||||||
|
When contributing, it's recommended to install `pre-commit` to automatically run the linter and formatter before a commit.
|
||||||
|
|
||||||
|
[`uv`](https://github.com/astral-sh/uv#installation) is recommended to manage Python version and tools.
|
||||||
|
|
||||||
|
Rust nightly is required for `cargo +nightly fmt` and `cargo +nightly clippy`.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cargo install --locked cargo-deny
|
||||||
|
$ rustup toolchain install nightly
|
||||||
|
$ uv tool install pre-commit
|
||||||
|
$ pre-commit install
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Licensed under either of
|
Licensed under either of
|
||||||
|
|||||||
@@ -74,6 +74,16 @@
|
|||||||
"*.json"
|
"*.json"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"ignore_patterns": {
|
||||||
|
"type": "array",
|
||||||
|
"description": "List of glob patterns to explicitly ignore when watching for changes.\nFiles matching these patterns will not trigger a rebuild.\nSupported syntax: https://docs.rs/globset/latest/globset/#syntax",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"default": [
|
||||||
|
"build/**/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
"objects": {
|
"objects": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "Use units instead.",
|
"description": "Use units instead.",
|
||||||
@@ -175,6 +185,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": [
|
||||||
|
|||||||
11
deny.toml
11
deny.toml
@@ -73,7 +73,7 @@ ignore = [
|
|||||||
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
||||||
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
#"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-0384", reason = "Unmaintained indirect dependency" },
|
{ id = "RUSTSEC-2024-0436", reason = "Unmaintained paste crate is an indirect dependency" },
|
||||||
]
|
]
|
||||||
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
# If this is 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.
|
||||||
@@ -102,8 +102,8 @@ allow = [
|
|||||||
"Zlib",
|
"Zlib",
|
||||||
"0BSD",
|
"0BSD",
|
||||||
"OFL-1.1",
|
"OFL-1.1",
|
||||||
"LicenseRef-UFL-1.0",
|
"Ubuntu-font-1.0",
|
||||||
"OpenSSL",
|
"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,7 +240,10 @@ 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 = ["notify-rs"]
|
github = [
|
||||||
|
"enarx", # flagset
|
||||||
|
"encounter",
|
||||||
|
]
|
||||||
# gitlab.com organizations to allow git sources for
|
# gitlab.com organizations to allow git sources for
|
||||||
gitlab = []
|
gitlab = []
|
||||||
# bitbucket.org organizations to allow git sources for
|
# bitbucket.org organizations to allow git sources for
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ publish = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
argp = "0.3"
|
argp = "0.4"
|
||||||
crossterm = "0.28"
|
crossterm = "0.29"
|
||||||
enable-ansi-support = "0.2"
|
enable-ansi-support = "0.2"
|
||||||
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.29"
|
||||||
rayon = "1.10"
|
rayon = "1.10"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
@@ -28,6 +28,7 @@ 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.11"
|
||||||
|
|
||||||
[target.'cfg(target_env = "musl")'.dependencies]
|
[target.'cfg(target_env = "musl")'.dependencies]
|
||||||
mimalloc = "0.1"
|
mimalloc = "0.1"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
//! For now, this only adds a --version/-V option which causes early-exit.
|
//! For now, this only adds a --version/-V option which causes early-exit.
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
|
|
||||||
use argp::{parser::ParseGlobalOptions, EarlyExit, FromArgs, TopLevelCommand};
|
use argp::{EarlyExit, FromArgs, TopLevelCommand, parser::ParseGlobalOptions};
|
||||||
|
|
||||||
struct ArgsOrVersion<T>(T)
|
struct ArgsOrVersion<T>(T)
|
||||||
where T: FromArgs;
|
where T: FromArgs;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,28 +1,25 @@
|
|||||||
use std::{
|
use std::{collections::HashSet, fs::File, io::Read, time::Instant};
|
||||||
collections::HashSet,
|
|
||||||
fs::File,
|
|
||||||
io::Read,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result, bail};
|
||||||
use argp::FromArgs;
|
use argp::FromArgs;
|
||||||
use objdiff_core::{
|
use objdiff_core::{
|
||||||
bindings::report::{
|
bindings::report::{
|
||||||
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, Report,
|
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, REPORT_VERSION,
|
||||||
ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
|
Report, ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
|
||||||
REPORT_VERSION,
|
|
||||||
},
|
},
|
||||||
config::ProjectObject,
|
config::path::platform_path,
|
||||||
diff, obj,
|
diff, obj,
|
||||||
obj::{ObjSectionKind, ObjSymbolFlags},
|
obj::{SectionKind, SymbolFlag},
|
||||||
};
|
};
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
|
||||||
|
|
||||||
use crate::util::output::{write_output, OutputFormat};
|
use crate::{
|
||||||
|
cmd::{apply_config_args, diff::ObjectConfig},
|
||||||
|
util::output::{OutputFormat, write_output},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(FromArgs, PartialEq, Debug)]
|
#[derive(FromArgs, PartialEq, Debug)]
|
||||||
/// Generate a progress report for a project.
|
/// Generate a progress report for a project.
|
||||||
@@ -43,33 +40,36 @@ pub enum SubCommand {
|
|||||||
/// Generate a progress report for a project.
|
/// Generate a progress report for a project.
|
||||||
#[argp(subcommand, name = "generate")]
|
#[argp(subcommand, name = "generate")]
|
||||||
pub struct GenerateArgs {
|
pub struct GenerateArgs {
|
||||||
#[argp(option, short = 'p')]
|
#[argp(option, short = 'p', from_str_fn(platform_path))]
|
||||||
/// Project directory
|
/// Project directory
|
||||||
project: Option<PathBuf>,
|
project: Option<Utf8PlatformPathBuf>,
|
||||||
#[argp(option, short = 'o')]
|
#[argp(option, short = 'o', from_str_fn(platform_path))]
|
||||||
/// Output file
|
/// Output file
|
||||||
output: Option<PathBuf>,
|
output: Option<Utf8PlatformPathBuf>,
|
||||||
#[argp(switch, short = 'd')]
|
#[argp(switch, short = 'd')]
|
||||||
/// Deduplicate global and weak symbols (runs single-threaded)
|
/// Deduplicate global and weak symbols (runs single-threaded)
|
||||||
deduplicate: bool,
|
deduplicate: bool,
|
||||||
#[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)]
|
||||||
/// List any changes from a previous report.
|
/// List any changes from a previous report.
|
||||||
#[argp(subcommand, name = "changes")]
|
#[argp(subcommand, name = "changes")]
|
||||||
pub struct ChangesArgs {
|
pub struct ChangesArgs {
|
||||||
#[argp(positional)]
|
#[argp(positional, from_str_fn(platform_path))]
|
||||||
/// Previous report file
|
/// Previous report file
|
||||||
previous: PathBuf,
|
previous: Utf8PlatformPathBuf,
|
||||||
#[argp(positional)]
|
#[argp(positional, from_str_fn(platform_path))]
|
||||||
/// Current report file
|
/// Current report file
|
||||||
current: PathBuf,
|
current: Utf8PlatformPathBuf,
|
||||||
#[argp(option, short = 'o')]
|
#[argp(option, short = 'o', from_str_fn(platform_path))]
|
||||||
/// Output file
|
/// Output file
|
||||||
output: Option<PathBuf>,
|
output: Option<Utf8PlatformPathBuf>,
|
||||||
#[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>,
|
||||||
@@ -83,11 +83,20 @@ pub fn run(args: Args) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn generate(args: GenerateArgs) -> Result<()> {
|
fn generate(args: GenerateArgs) -> Result<()> {
|
||||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
let mut diff_config = diff::DiffObjConfig {
|
||||||
let project_dir = args.project.as_deref().unwrap_or_else(|| Path::new("."));
|
function_reloc_diffs: diff::FunctionRelocDiffs::None,
|
||||||
info!("Loading project {}", project_dir.display());
|
combine_data_sections: true,
|
||||||
|
combine_text_sections: true,
|
||||||
|
ppc_calculate_pool_relocations: false,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
apply_config_args(&mut diff_config, &args.config)?;
|
||||||
|
|
||||||
let mut project = match objdiff_core::config::try_project_config(project_dir) {
|
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||||
|
let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new("."));
|
||||||
|
info!("Loading project {}", project_dir);
|
||||||
|
|
||||||
|
let project = match objdiff_core::config::try_project_config(project_dir.as_ref()) {
|
||||||
Some((Ok(config), _)) => config,
|
Some((Ok(config), _)) => config,
|
||||||
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"),
|
||||||
@@ -98,37 +107,34 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
if args.deduplicate { 1 } else { rayon::current_num_threads() }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let target_obj_dir =
|
||||||
|
project.target_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
||||||
|
let base_obj_dir =
|
||||||
|
project.base_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
|
||||||
|
let objects = project
|
||||||
|
.units
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|o| {
|
||||||
|
ObjectConfig::new(o, project_dir, target_obj_dir.as_deref(), base_obj_dir.as_deref())
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
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 project.units.as_deref_mut().unwrap_or_default() {
|
for object in &objects {
|
||||||
if let Some(unit) = report_object(
|
if let Some(unit) = report_object(object, &diff_config, Some(&mut existing_functions))?
|
||||||
object,
|
{
|
||||||
project_dir,
|
|
||||||
project.target_dir.as_deref(),
|
|
||||||
project.base_dir.as_deref(),
|
|
||||||
Some(&mut existing_functions),
|
|
||||||
)? {
|
|
||||||
units.push(unit);
|
units.push(unit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let vec = project
|
let vec = objects
|
||||||
.units
|
.par_iter()
|
||||||
.as_deref_mut()
|
.map(|object| report_object(object, &diff_config, None))
|
||||||
.unwrap_or_default()
|
|
||||||
.par_iter_mut()
|
|
||||||
.map(|object| {
|
|
||||||
report_object(
|
|
||||||
object,
|
|
||||||
project_dir,
|
|
||||||
project.target_dir.as_deref(),
|
|
||||||
project.base_dir.as_deref(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<Option<ReportUnit>>>>()?;
|
.collect::<Result<Vec<Option<ReportUnit>>>>()?;
|
||||||
units = vec.into_iter().flatten().collect();
|
units = vec.into_iter().flatten().collect();
|
||||||
}
|
}
|
||||||
@@ -151,55 +157,49 @@ fn generate(args: GenerateArgs) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn report_object(
|
fn report_object(
|
||||||
object: &mut ProjectObject,
|
object: &ObjectConfig,
|
||||||
project_dir: &Path,
|
diff_config: &diff::DiffObjConfig,
|
||||||
target_dir: Option<&Path>,
|
|
||||||
base_dir: Option<&Path>,
|
|
||||||
mut existing_functions: Option<&mut HashSet<String>>,
|
mut existing_functions: Option<&mut HashSet<String>>,
|
||||||
) -> Result<Option<ReportUnit>> {
|
) -> Result<Option<ReportUnit>> {
|
||||||
object.resolve_paths(project_dir, target_dir, base_dir);
|
|
||||||
match (&object.target_path, &object.base_path) {
|
match (&object.target_path, &object.base_path) {
|
||||||
(None, Some(_)) if !object.complete().unwrap_or(false) => {
|
(None, Some(_)) if !object.complete.unwrap_or(false) => {
|
||||||
warn!("Skipping object without target: {}", object.name());
|
warn!("Skipping object without target: {}", object.name);
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
(None, None) => {
|
(None, None) => {
|
||||||
warn!("Skipping object without target or base: {}", object.name());
|
warn!("Skipping object without target or base: {}", object.name);
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
let config = diff::DiffObjConfig { relax_reloc_diffs: true, ..Default::default() };
|
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, &config).with_context(|| format!("Failed to open {}", p.display()))
|
obj::read::read(p.as_ref(), diff_config).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, &config).with_context(|| format!("Failed to open {}", p.display()))
|
obj::read::read(p.as_ref(), diff_config).with_context(|| format!("Failed to open {p}"))
|
||||||
})
|
})
|
||||||
.transpose()?;
|
.transpose()?;
|
||||||
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), None)?;
|
let result =
|
||||||
|
diff::diff_objs(target.as_ref(), base.as_ref(), None, diff_config, &mapping_config)?;
|
||||||
|
|
||||||
let metadata = ReportUnitMetadata {
|
let metadata = ReportUnitMetadata {
|
||||||
complete: object.complete(),
|
complete: object.metadata.complete,
|
||||||
module_name: target
|
module_name: target
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|o| o.split_meta.as_ref())
|
.and_then(|o| o.split_meta.as_ref())
|
||||||
.and_then(|m| m.module_name.clone()),
|
.and_then(|m| m.module_name.clone()),
|
||||||
module_id: target.as_ref().and_then(|o| o.split_meta.as_ref()).and_then(|m| m.module_id),
|
module_id: target.as_ref().and_then(|o| o.split_meta.as_ref()).and_then(|m| m.module_id),
|
||||||
source_path: object.metadata.as_ref().and_then(|m| m.source_path.clone()),
|
source_path: object.metadata.source_path.as_ref().map(|p| p.to_string()),
|
||||||
progress_categories: object
|
progress_categories: object.metadata.progress_categories.clone().unwrap_or_default(),
|
||||||
.metadata
|
auto_generated: object.metadata.auto_generated,
|
||||||
.as_ref()
|
|
||||||
.and_then(|m| m.progress_categories.clone())
|
|
||||||
.unwrap_or_default(),
|
|
||||||
auto_generated: object.metadata.as_ref().and_then(|m| m.auto_generated),
|
|
||||||
};
|
};
|
||||||
let mut measures = Measures { total_units: 1, ..Default::default() };
|
let mut measures = Measures { total_units: 1, ..Default::default() };
|
||||||
let mut sections = vec![];
|
let mut sections = vec![];
|
||||||
@@ -207,15 +207,16 @@ fn report_object(
|
|||||||
|
|
||||||
let obj = target.as_ref().or(base.as_ref()).unwrap();
|
let obj = target.as_ref().or(base.as_ref()).unwrap();
|
||||||
let obj_diff = result.left.as_ref().or(result.right.as_ref()).unwrap();
|
let obj_diff = result.left.as_ref().or(result.right.as_ref()).unwrap();
|
||||||
for (section, section_diff) in obj.sections.iter().zip(&obj_diff.sections) {
|
for ((section_idx, section), section_diff) in
|
||||||
|
obj.sections.iter().enumerate().zip(&obj_diff.sections)
|
||||||
|
{
|
||||||
|
if section.kind == SectionKind::Unknown {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let section_match_percent = section_diff.match_percent.unwrap_or_else(|| {
|
let section_match_percent = section_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,
|
||||||
// assume complete means 100% match
|
// assume complete means 100% match
|
||||||
if object.complete().unwrap_or(false) {
|
if object.complete.unwrap_or(false) { 100.0 } else { 0.0 }
|
||||||
100.0
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
sections.push(ReportItem {
|
sections.push(ReportItem {
|
||||||
name: section.name.clone(),
|
name: section.name.clone(),
|
||||||
@@ -225,39 +226,39 @@ 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 {
|
||||||
ObjSectionKind::Data | ObjSectionKind::Bss => {
|
SectionKind::Data | SectionKind::Bss => {
|
||||||
measures.total_data += section.size;
|
measures.total_data += section.size;
|
||||||
if section_match_percent == 100.0 {
|
if section_match_percent == 100.0 {
|
||||||
measures.matched_data += section.size;
|
measures.matched_data += section.size;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ObjSectionKind::Code => (),
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (symbol, symbol_diff) in section.symbols.iter().zip(§ion_diff.symbols) {
|
for (symbol, symbol_diff) in obj.symbols.iter().zip(&obj_diff.symbols) {
|
||||||
if symbol.size == 0 || symbol.flags.0.contains(ObjSymbolFlags::Hidden) {
|
if symbol.section != Some(section_idx)
|
||||||
|
|| symbol.size == 0
|
||||||
|
|| symbol.flags.contains(SymbolFlag::Hidden)
|
||||||
|
|| symbol.flags.contains(SymbolFlag::Ignored)
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(existing_functions) = &mut existing_functions {
|
if let Some(existing_functions) = &mut existing_functions
|
||||||
if (symbol.flags.0.contains(ObjSymbolFlags::Global)
|
&& (symbol.flags.contains(SymbolFlag::Global)
|
||||||
|| symbol.flags.0.contains(ObjSymbolFlags::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,
|
||||||
// assume complete means 100% match
|
// assume complete means 100% match
|
||||||
if object.complete().unwrap_or(false) {
|
if object.complete.unwrap_or(false) { 100.0 } else { 0.0 }
|
||||||
100.0
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
measures.fuzzy_match_percent += match_percent * symbol.size as f32;
|
measures.fuzzy_match_percent += match_percent * symbol.size as f32;
|
||||||
measures.total_code += symbol.size;
|
measures.total_code += symbol.size;
|
||||||
@@ -272,6 +273,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;
|
||||||
@@ -279,6 +281,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;
|
||||||
@@ -287,7 +299,7 @@ fn report_object(
|
|||||||
measures.calc_fuzzy_match_percent();
|
measures.calc_fuzzy_match_percent();
|
||||||
measures.calc_matched_percent();
|
measures.calc_matched_percent();
|
||||||
Ok(Some(ReportUnit {
|
Ok(Some(ReportUnit {
|
||||||
name: object.name().to_string(),
|
name: object.name.clone(),
|
||||||
measures: Some(measures),
|
measures: Some(measures),
|
||||||
sections,
|
sections,
|
||||||
functions,
|
functions,
|
||||||
@@ -297,7 +309,7 @@ fn report_object(
|
|||||||
|
|
||||||
fn changes(args: ChangesArgs) -> Result<()> {
|
fn changes(args: ChangesArgs) -> Result<()> {
|
||||||
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
let output_format = OutputFormat::from_option(args.format.as_deref())?;
|
||||||
let (previous, current) = if args.previous == Path::new("-") && args.current == Path::new("-") {
|
let (previous, current) = if args.previous == "-" && args.current == "-" {
|
||||||
// Special case for comparing two reports from stdin
|
// Special case for comparing two reports from stdin
|
||||||
let mut data = vec![];
|
let mut data = vec![];
|
||||||
std::io::stdin().read_to_end(&mut data)?;
|
std::io::stdin().read_to_end(&mut data)?;
|
||||||
@@ -412,15 +424,14 @@ fn process_new_items(items: &[ReportItem]) -> Vec<ChangeItem> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_report(path: &Path) -> Result<Report> {
|
fn read_report(path: &Utf8PlatformPath) -> Result<Report> {
|
||||||
if path == Path::new("-") {
|
if path == Utf8PlatformPath::new("-") {
|
||||||
let mut data = vec![];
|
let mut data = vec![];
|
||||||
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.display()))?;
|
let file = File::open(path).with_context(|| format!("Failed to open {path}"))?;
|
||||||
let mmap = unsafe { memmap2::Mmap::map(&file) }
|
let mmap =
|
||||||
.with_context(|| format!("Failed to map {}", path.display()))?;
|
unsafe { memmap2::Mmap::map(&file) }.with_context(|| format!("Failed to map {path}"))?;
|
||||||
Report::parse(mmap.as_ref())
|
Report::parse(mmap.as_ref()).with_context(|| format!("Failed to load report {path}"))
|
||||||
.with_context(|| format!("Failed to load report {}", path.display()))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
#![allow(clippy::too_many_arguments)]
|
||||||
|
|
||||||
mod argp_version;
|
mod argp_version;
|
||||||
mod cmd;
|
mod cmd;
|
||||||
mod util;
|
mod util;
|
||||||
|
mod views;
|
||||||
|
|
||||||
// musl's allocator is very slow, so use mimalloc when targeting musl.
|
// musl's allocator is very slow, so use mimalloc when targeting musl.
|
||||||
// Otherwise, use the system allocator to avoid extra code size.
|
// Otherwise, use the system allocator to avoid extra code size.
|
||||||
@@ -14,7 +17,7 @@ use anyhow::{Error, Result};
|
|||||||
use argp::{FromArgValue, FromArgs};
|
use argp::{FromArgValue, FromArgs};
|
||||||
use enable_ansi_support::enable_ansi_support;
|
use enable_ansi_support::enable_ansi_support;
|
||||||
use supports_color::Stream;
|
use supports_color::Stream;
|
||||||
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
|
use tracing_subscriber::{EnvFilter, filter::LevelFilter};
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||||
enum LogLevel {
|
enum LogLevel {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use std::{
|
|||||||
path::Path,
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{Context, Result, bail};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
|
||||||
@@ -34,9 +34,12 @@ impl OutputFormat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_output<T>(input: &T, output: Option<&Path>, format: OutputFormat) -> Result<()>
|
pub fn write_output<T, P>(input: &T, output: Option<P>, format: OutputFormat) -> Result<()>
|
||||||
where T: serde::Serialize + prost::Message {
|
where
|
||||||
match output {
|
T: serde::Serialize + prost::Message,
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
match output.as_ref().map(|p| p.as_ref()) {
|
||||||
Some(output) if output != Path::new("-") => {
|
Some(output) if output != Path::new("-") => {
|
||||||
info!("Writing to {}", output.display());
|
info!("Writing to {}", output.display());
|
||||||
let file = File::options()
|
let file = File::options()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::{io::stdout, panic};
|
|||||||
use crossterm::{
|
use crossterm::{
|
||||||
cursor::Show,
|
cursor::Show,
|
||||||
event::DisableMouseCapture,
|
event::DisableMouseCapture,
|
||||||
terminal::{disable_raw_mode, LeaveAlternateScreen},
|
terminal::{LeaveAlternateScreen, disable_raw_mode},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn crossterm_panic_handler() {
|
pub fn crossterm_panic_handler() {
|
||||||
|
|||||||
654
objdiff-cli/src/views/function_diff.rs
Normal file
654
objdiff-cli/src/views/function_diff.rs
Normal file
@@ -0,0 +1,654 @@
|
|||||||
|
use core::cmp::Ordering;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers, MouseButton, MouseEventKind};
|
||||||
|
use objdiff_core::{
|
||||||
|
build::BuildStatus,
|
||||||
|
diff::{
|
||||||
|
DiffObjConfig, FunctionRelocDiffs, InstructionDiffKind, ObjectDiff, SymbolDiff,
|
||||||
|
display::{DiffText, DiffTextColor, HighlightKind, display_row},
|
||||||
|
},
|
||||||
|
obj::Object,
|
||||||
|
};
|
||||||
|
use ratatui::{
|
||||||
|
Frame,
|
||||||
|
prelude::*,
|
||||||
|
widgets::{Block, Borders, Clear, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{EventControlFlow, EventResult, UiView};
|
||||||
|
use crate::cmd::diff::AppState;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct FunctionDiffUi {
|
||||||
|
pub symbol_name: String,
|
||||||
|
pub left_highlight: HighlightKind,
|
||||||
|
pub right_highlight: HighlightKind,
|
||||||
|
pub scroll_x: usize,
|
||||||
|
pub scroll_state_x: ScrollbarState,
|
||||||
|
pub scroll_y: usize,
|
||||||
|
pub scroll_state_y: ScrollbarState,
|
||||||
|
pub per_page: usize,
|
||||||
|
pub num_rows: usize,
|
||||||
|
pub left_sym: Option<usize>,
|
||||||
|
pub right_sym: Option<usize>,
|
||||||
|
pub prev_sym: Option<usize>,
|
||||||
|
pub open_options: bool,
|
||||||
|
pub three_way: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiView for FunctionDiffUi {
|
||||||
|
fn draw(&mut self, state: &AppState, f: &mut Frame, result: &mut EventResult) {
|
||||||
|
let chunks = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]).split(f.area());
|
||||||
|
let header_chunks = Layout::horizontal([
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(2),
|
||||||
|
])
|
||||||
|
.split(chunks[0]);
|
||||||
|
let content_chunks = if self.three_way {
|
||||||
|
Layout::horizontal([
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(2),
|
||||||
|
])
|
||||||
|
.split(chunks[1])
|
||||||
|
} else {
|
||||||
|
Layout::horizontal([
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(3),
|
||||||
|
Constraint::Fill(1),
|
||||||
|
Constraint::Length(2),
|
||||||
|
])
|
||||||
|
.split(chunks[1])
|
||||||
|
};
|
||||||
|
|
||||||
|
self.per_page = chunks[1].height.saturating_sub(2) as usize;
|
||||||
|
let max_scroll_y = self.num_rows.saturating_sub(self.per_page);
|
||||||
|
if self.scroll_y > max_scroll_y {
|
||||||
|
self.scroll_y = max_scroll_y;
|
||||||
|
}
|
||||||
|
self.scroll_state_y =
|
||||||
|
self.scroll_state_y.content_length(max_scroll_y).position(self.scroll_y);
|
||||||
|
|
||||||
|
let mut line_l = Line::default();
|
||||||
|
line_l
|
||||||
|
.spans
|
||||||
|
.push(Span::styled(self.symbol_name.clone(), Style::new().fg(Color::White).bold()));
|
||||||
|
f.render_widget(line_l, header_chunks[0]);
|
||||||
|
|
||||||
|
let mut line_r = Line::default();
|
||||||
|
if let Some(percent) = get_symbol(state.right_obj.as_ref(), self.right_sym)
|
||||||
|
.and_then(|(_, _, d)| d.match_percent)
|
||||||
|
{
|
||||||
|
line_r.spans.push(Span::styled(
|
||||||
|
format!("{percent:.2}% "),
|
||||||
|
Style::new().fg(match_percent_color(percent)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let reload_time = state
|
||||||
|
.reload_time
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|t| t.format(&state.time_format).ok())
|
||||||
|
.unwrap_or_else(|| "N/A".to_string());
|
||||||
|
line_r.spans.push(Span::styled(
|
||||||
|
format!("Last reload: {reload_time}"),
|
||||||
|
Style::new().fg(Color::White),
|
||||||
|
));
|
||||||
|
line_r.spans.push(Span::styled(
|
||||||
|
format!(" ({} jobs)", state.jobs.jobs.len()),
|
||||||
|
Style::new().fg(Color::LightYellow),
|
||||||
|
));
|
||||||
|
f.render_widget(line_r, header_chunks[2]);
|
||||||
|
|
||||||
|
let mut left_text = None;
|
||||||
|
let mut left_highlight = None;
|
||||||
|
let mut max_width = 0;
|
||||||
|
if let Some((obj, symbol_idx, symbol_diff)) =
|
||||||
|
get_symbol(state.left_obj.as_ref(), self.left_sym)
|
||||||
|
{
|
||||||
|
let mut text = Text::default();
|
||||||
|
let rect = content_chunks[0].inner(Margin::new(0, 1));
|
||||||
|
left_highlight = self.print_sym(
|
||||||
|
&mut text,
|
||||||
|
obj,
|
||||||
|
symbol_idx,
|
||||||
|
symbol_diff,
|
||||||
|
&state.diff_obj_config,
|
||||||
|
rect,
|
||||||
|
&self.left_highlight,
|
||||||
|
result,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
max_width = max_width.max(text.width());
|
||||||
|
left_text = Some(text);
|
||||||
|
} else if let Some(status) = &state.left_status {
|
||||||
|
let mut text = Text::default();
|
||||||
|
self.print_build_status(&mut text, status);
|
||||||
|
max_width = max_width.max(text.width());
|
||||||
|
left_text = Some(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut right_text = None;
|
||||||
|
let mut right_highlight = None;
|
||||||
|
let mut margin_text = None;
|
||||||
|
if let Some((obj, symbol_idx, symbol_diff)) =
|
||||||
|
get_symbol(state.right_obj.as_ref(), self.right_sym)
|
||||||
|
{
|
||||||
|
let mut text = Text::default();
|
||||||
|
let rect = content_chunks[2].inner(Margin::new(0, 1));
|
||||||
|
right_highlight = self.print_sym(
|
||||||
|
&mut text,
|
||||||
|
obj,
|
||||||
|
symbol_idx,
|
||||||
|
symbol_diff,
|
||||||
|
&state.diff_obj_config,
|
||||||
|
rect,
|
||||||
|
&self.right_highlight,
|
||||||
|
result,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
max_width = max_width.max(text.width());
|
||||||
|
right_text = Some(text);
|
||||||
|
|
||||||
|
// Render margin
|
||||||
|
let mut text = Text::default();
|
||||||
|
let rect = content_chunks[1].inner(Margin::new(1, 1));
|
||||||
|
self.print_margin(&mut text, symbol_diff, rect);
|
||||||
|
margin_text = Some(text);
|
||||||
|
} else if let Some(status) = &state.right_status {
|
||||||
|
let mut text = Text::default();
|
||||||
|
self.print_build_status(&mut text, status);
|
||||||
|
max_width = max_width.max(text.width());
|
||||||
|
right_text = Some(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut prev_text = None;
|
||||||
|
let mut prev_margin_text = None;
|
||||||
|
if self.three_way
|
||||||
|
&& let Some((obj, symbol_idx, symbol_diff)) =
|
||||||
|
get_symbol(state.prev_obj.as_ref(), self.prev_sym)
|
||||||
|
{
|
||||||
|
let mut text = Text::default();
|
||||||
|
let rect = content_chunks[4].inner(Margin::new(0, 1));
|
||||||
|
self.print_sym(
|
||||||
|
&mut text,
|
||||||
|
obj,
|
||||||
|
symbol_idx,
|
||||||
|
symbol_diff,
|
||||||
|
&state.diff_obj_config,
|
||||||
|
rect,
|
||||||
|
&self.right_highlight,
|
||||||
|
result,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
max_width = max_width.max(text.width());
|
||||||
|
prev_text = Some(text);
|
||||||
|
|
||||||
|
// Render margin
|
||||||
|
let mut text = Text::default();
|
||||||
|
let rect = content_chunks[3].inner(Margin::new(1, 1));
|
||||||
|
self.print_margin(&mut text, symbol_diff, rect);
|
||||||
|
prev_margin_text = Some(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
let max_scroll_x =
|
||||||
|
max_width.saturating_sub(content_chunks[0].width.min(content_chunks[2].width) as usize);
|
||||||
|
if self.scroll_x > max_scroll_x {
|
||||||
|
self.scroll_x = max_scroll_x;
|
||||||
|
}
|
||||||
|
self.scroll_state_x =
|
||||||
|
self.scroll_state_x.content_length(max_scroll_x).position(self.scroll_x);
|
||||||
|
|
||||||
|
if let Some(text) = left_text {
|
||||||
|
// Render left column
|
||||||
|
f.render_widget(
|
||||||
|
Paragraph::new(text)
|
||||||
|
.block(
|
||||||
|
Block::new()
|
||||||
|
.borders(Borders::TOP)
|
||||||
|
.border_style(Style::new().fg(Color::Gray))
|
||||||
|
.title_style(Style::new().bold())
|
||||||
|
.title("TARGET"),
|
||||||
|
)
|
||||||
|
.scroll((0, self.scroll_x as u16)),
|
||||||
|
content_chunks[0],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(text) = margin_text {
|
||||||
|
f.render_widget(text, content_chunks[1].inner(Margin::new(1, 1)));
|
||||||
|
}
|
||||||
|
if let Some(text) = right_text {
|
||||||
|
f.render_widget(
|
||||||
|
Paragraph::new(text)
|
||||||
|
.block(
|
||||||
|
Block::new()
|
||||||
|
.borders(Borders::TOP)
|
||||||
|
.border_style(Style::new().fg(Color::Gray))
|
||||||
|
.title_style(Style::new().bold())
|
||||||
|
.title("CURRENT"),
|
||||||
|
)
|
||||||
|
.scroll((0, self.scroll_x as u16)),
|
||||||
|
content_chunks[2],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.three_way {
|
||||||
|
if let Some(text) = prev_margin_text {
|
||||||
|
f.render_widget(text, content_chunks[3].inner(Margin::new(1, 1)));
|
||||||
|
}
|
||||||
|
let block = Block::new()
|
||||||
|
.borders(Borders::TOP)
|
||||||
|
.border_style(Style::new().fg(Color::Gray))
|
||||||
|
.title_style(Style::new().bold())
|
||||||
|
.title("SAVED");
|
||||||
|
if let Some(text) = prev_text {
|
||||||
|
f.render_widget(
|
||||||
|
Paragraph::new(text).block(block.clone()).scroll((0, self.scroll_x as u16)),
|
||||||
|
content_chunks[4],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
f.render_widget(block, content_chunks[4]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render scrollbars
|
||||||
|
f.render_stateful_widget(
|
||||||
|
Scrollbar::new(ScrollbarOrientation::VerticalRight).begin_symbol(None).end_symbol(None),
|
||||||
|
chunks[1].inner(Margin::new(0, 1)),
|
||||||
|
&mut self.scroll_state_y,
|
||||||
|
);
|
||||||
|
f.render_stateful_widget(
|
||||||
|
Scrollbar::new(ScrollbarOrientation::HorizontalBottom).thumb_symbol("■"),
|
||||||
|
content_chunks[0],
|
||||||
|
&mut self.scroll_state_x,
|
||||||
|
);
|
||||||
|
f.render_stateful_widget(
|
||||||
|
Scrollbar::new(ScrollbarOrientation::HorizontalBottom).thumb_symbol("■"),
|
||||||
|
content_chunks[2],
|
||||||
|
&mut self.scroll_state_x,
|
||||||
|
);
|
||||||
|
if self.three_way {
|
||||||
|
f.render_stateful_widget(
|
||||||
|
Scrollbar::new(ScrollbarOrientation::HorizontalBottom).thumb_symbol("■"),
|
||||||
|
content_chunks[4],
|
||||||
|
&mut self.scroll_state_x,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(new_highlight) = left_highlight {
|
||||||
|
if new_highlight == self.left_highlight {
|
||||||
|
if self.left_highlight != self.right_highlight {
|
||||||
|
self.right_highlight = self.left_highlight.clone();
|
||||||
|
} else {
|
||||||
|
self.left_highlight = HighlightKind::None;
|
||||||
|
self.right_highlight = HighlightKind::None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.left_highlight = new_highlight;
|
||||||
|
}
|
||||||
|
result.redraw = true;
|
||||||
|
} else if let Some(new_highlight) = right_highlight {
|
||||||
|
if new_highlight == self.right_highlight {
|
||||||
|
if self.left_highlight != self.right_highlight {
|
||||||
|
self.left_highlight = self.right_highlight.clone();
|
||||||
|
} else {
|
||||||
|
self.left_highlight = HighlightKind::None;
|
||||||
|
self.right_highlight = HighlightKind::None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.right_highlight = new_highlight;
|
||||||
|
}
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.open_options {
|
||||||
|
self.draw_options(f, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_event(&mut self, state: &mut AppState, event: Event) -> EventControlFlow {
|
||||||
|
let mut result = EventResult::default();
|
||||||
|
match event {
|
||||||
|
Event::Key(event)
|
||||||
|
if matches!(event.kind, KeyEventKind::Press | KeyEventKind::Repeat) =>
|
||||||
|
{
|
||||||
|
match event.code {
|
||||||
|
// Quit
|
||||||
|
KeyCode::Esc | KeyCode::Char('q') => return EventControlFlow::Break,
|
||||||
|
// Page up
|
||||||
|
KeyCode::PageUp => {
|
||||||
|
self.page_up(false);
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
// Page up (shift + space)
|
||||||
|
KeyCode::Char(' ') if event.modifiers.contains(KeyModifiers::SHIFT) => {
|
||||||
|
self.page_up(false);
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
// Page down
|
||||||
|
KeyCode::Char(' ') | KeyCode::PageDown => {
|
||||||
|
self.page_down(false);
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
// Page down (ctrl + f)
|
||||||
|
KeyCode::Char('f') if event.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
|
self.page_down(false);
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
// Page up (ctrl + b)
|
||||||
|
KeyCode::Char('b') if event.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
|
self.page_up(false);
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
// Half page down (ctrl + d)
|
||||||
|
KeyCode::Char('d') if event.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
|
self.page_down(true);
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
// Half page up (ctrl + u)
|
||||||
|
KeyCode::Char('u') if event.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
|
self.page_up(true);
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
// Scroll down
|
||||||
|
KeyCode::Down | KeyCode::Char('j') => {
|
||||||
|
self.scroll_y += 1;
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
// Scroll up
|
||||||
|
KeyCode::Up | KeyCode::Char('k') => {
|
||||||
|
self.scroll_y = self.scroll_y.saturating_sub(1);
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
// Scroll to start
|
||||||
|
KeyCode::Char('g') => {
|
||||||
|
self.scroll_y = 0;
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
// Scroll to end
|
||||||
|
KeyCode::Char('G') => {
|
||||||
|
self.scroll_y = self.num_rows;
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
// Reload
|
||||||
|
KeyCode::Char('r') => {
|
||||||
|
result.redraw = true;
|
||||||
|
return EventControlFlow::Reload;
|
||||||
|
}
|
||||||
|
// Scroll right
|
||||||
|
KeyCode::Right | KeyCode::Char('l') => {
|
||||||
|
self.scroll_x += 1;
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
// Scroll left
|
||||||
|
KeyCode::Left | KeyCode::Char('h') => {
|
||||||
|
self.scroll_x = self.scroll_x.saturating_sub(1);
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
// Cycle through function relocation diff mode
|
||||||
|
KeyCode::Char('x') => {
|
||||||
|
state.diff_obj_config.function_reloc_diffs =
|
||||||
|
match state.diff_obj_config.function_reloc_diffs {
|
||||||
|
FunctionRelocDiffs::None => FunctionRelocDiffs::NameAddress,
|
||||||
|
FunctionRelocDiffs::NameAddress => FunctionRelocDiffs::DataValue,
|
||||||
|
FunctionRelocDiffs::DataValue => FunctionRelocDiffs::All,
|
||||||
|
FunctionRelocDiffs::All => FunctionRelocDiffs::None,
|
||||||
|
};
|
||||||
|
result.redraw = true;
|
||||||
|
return EventControlFlow::Reload;
|
||||||
|
}
|
||||||
|
// Toggle three-way diff
|
||||||
|
KeyCode::Char('3') => {
|
||||||
|
self.three_way = !self.three_way;
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
// Toggle options
|
||||||
|
KeyCode::Char('o') => {
|
||||||
|
self.open_options = !self.open_options;
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::Mouse(event) => match event.kind {
|
||||||
|
MouseEventKind::ScrollDown => {
|
||||||
|
self.scroll_y += 3;
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
MouseEventKind::ScrollUp => {
|
||||||
|
self.scroll_y = self.scroll_y.saturating_sub(3);
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
MouseEventKind::ScrollRight => {
|
||||||
|
self.scroll_x += 3;
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
MouseEventKind::ScrollLeft => {
|
||||||
|
self.scroll_x = self.scroll_x.saturating_sub(3);
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
MouseEventKind::Down(MouseButton::Left) => {
|
||||||
|
result.click_xy = Some((event.column, event.row));
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
Event::Resize(_, _) => {
|
||||||
|
result.redraw = true;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
EventControlFlow::Continue(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reload(&mut self, state: &AppState) -> Result<()> {
|
||||||
|
let left_sym =
|
||||||
|
state.left_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
|
||||||
|
let right_sym =
|
||||||
|
state.right_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
|
||||||
|
let prev_sym =
|
||||||
|
state.prev_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
|
||||||
|
self.num_rows = match (
|
||||||
|
get_symbol(state.left_obj.as_ref(), left_sym),
|
||||||
|
get_symbol(state.right_obj.as_ref(), right_sym),
|
||||||
|
) {
|
||||||
|
(Some((_l, _ls, ld)), Some((_r, _rs, rd))) => {
|
||||||
|
ld.instruction_rows.len().max(rd.instruction_rows.len())
|
||||||
|
}
|
||||||
|
(Some((_l, _ls, ld)), None) => ld.instruction_rows.len(),
|
||||||
|
(None, Some((_r, _rs, rd))) => rd.instruction_rows.len(),
|
||||||
|
(None, None) => 0,
|
||||||
|
};
|
||||||
|
self.left_sym = left_sym;
|
||||||
|
self.right_sym = right_sym;
|
||||||
|
self.prev_sym = prev_sym;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FunctionDiffUi {
|
||||||
|
pub fn draw_options(&mut self, f: &mut Frame, _result: &mut EventResult) {
|
||||||
|
let percent_x = 50;
|
||||||
|
let percent_y = 50;
|
||||||
|
let popup_rect = Layout::vertical([
|
||||||
|
Constraint::Percentage((100 - percent_y) / 2),
|
||||||
|
Constraint::Percentage(percent_y),
|
||||||
|
Constraint::Percentage((100 - percent_y) / 2),
|
||||||
|
])
|
||||||
|
.split(f.area())[1];
|
||||||
|
let popup_rect = Layout::horizontal([
|
||||||
|
Constraint::Percentage((100 - percent_x) / 2),
|
||||||
|
Constraint::Percentage(percent_x),
|
||||||
|
Constraint::Percentage((100 - percent_x) / 2),
|
||||||
|
])
|
||||||
|
.split(popup_rect)[1];
|
||||||
|
|
||||||
|
let popup = Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title("Options")
|
||||||
|
.title_style(Style::default().fg(Color::White).bg(Color::Black));
|
||||||
|
f.render_widget(Clear, popup_rect);
|
||||||
|
f.render_widget(popup, popup_rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_up(&mut self, half: bool) {
|
||||||
|
self.scroll_y = self.scroll_y.saturating_sub(self.per_page / if half { 2 } else { 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn page_down(&mut self, half: bool) {
|
||||||
|
self.scroll_y += self.per_page / if half { 2 } else { 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_sym(
|
||||||
|
&self,
|
||||||
|
out: &mut Text<'static>,
|
||||||
|
obj: &Object,
|
||||||
|
symbol_index: usize,
|
||||||
|
symbol_diff: &SymbolDiff,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
rect: Rect,
|
||||||
|
highlight: &HighlightKind,
|
||||||
|
result: &EventResult,
|
||||||
|
only_changed: bool,
|
||||||
|
) -> Option<HighlightKind> {
|
||||||
|
let mut new_highlight = None;
|
||||||
|
for (y, ins_row) in symbol_diff
|
||||||
|
.instruction_rows
|
||||||
|
.iter()
|
||||||
|
.skip(self.scroll_y)
|
||||||
|
.take(rect.height as usize)
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
if only_changed && ins_row.kind == InstructionDiffKind::None {
|
||||||
|
out.lines.push(Line::default());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut sx = rect.x;
|
||||||
|
let sy = rect.y + y as u16;
|
||||||
|
let mut line = Line::default();
|
||||||
|
display_row(obj, symbol_index, ins_row, diff_config, |segment| {
|
||||||
|
let highlight_kind = HighlightKind::from(&segment.text);
|
||||||
|
let label_text = match segment.text {
|
||||||
|
DiffText::Basic(text) => text.to_string(),
|
||||||
|
DiffText::Line(num) => format!("{num} "),
|
||||||
|
DiffText::Address(addr) => format!("{addr:x}:"),
|
||||||
|
DiffText::Opcode(mnemonic, _op) => format!("{mnemonic} "),
|
||||||
|
DiffText::Argument(arg) => arg.to_string(),
|
||||||
|
DiffText::BranchDest(addr) => format!("{addr:x}"),
|
||||||
|
DiffText::Symbol(sym) => {
|
||||||
|
sym.demangled_name.as_ref().unwrap_or(&sym.name).clone()
|
||||||
|
}
|
||||||
|
DiffText::Addend(addend) => match addend.cmp(&0i64) {
|
||||||
|
Ordering::Greater => format!("+{addend:#x}"),
|
||||||
|
Ordering::Less => format!("-{:#x}", -addend),
|
||||||
|
_ => String::new(),
|
||||||
|
},
|
||||||
|
DiffText::Spacing(n) => {
|
||||||
|
line.spans.push(Span::raw(" ".repeat(n as usize)));
|
||||||
|
sx += n as u16;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
DiffText::Eol => return Ok(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let len = label_text.len();
|
||||||
|
let highlighted =
|
||||||
|
highlight_kind != HighlightKind::None && *highlight == highlight_kind;
|
||||||
|
if let Some((cx, cy)) = result.click_xy
|
||||||
|
&& cx >= sx
|
||||||
|
&& cx < sx + len as u16
|
||||||
|
&& cy == sy
|
||||||
|
{
|
||||||
|
new_highlight = Some(highlight_kind);
|
||||||
|
}
|
||||||
|
let mut style = Style::new().fg(match segment.color {
|
||||||
|
DiffTextColor::Normal => Color::Gray,
|
||||||
|
DiffTextColor::Dim => Color::DarkGray,
|
||||||
|
DiffTextColor::Bright => Color::White,
|
||||||
|
DiffTextColor::DataFlow => Color::LightCyan,
|
||||||
|
DiffTextColor::Replace => Color::Cyan,
|
||||||
|
DiffTextColor::Delete => Color::Red,
|
||||||
|
DiffTextColor::Insert => Color::Green,
|
||||||
|
DiffTextColor::Rotating(i) => COLOR_ROTATION[i as usize % COLOR_ROTATION.len()],
|
||||||
|
});
|
||||||
|
if highlighted {
|
||||||
|
style = style.bg(Color::DarkGray);
|
||||||
|
}
|
||||||
|
line.spans.push(Span::styled(label_text, style));
|
||||||
|
sx += len as u16;
|
||||||
|
if segment.pad_to as usize > len {
|
||||||
|
let pad = (segment.pad_to as usize - len) as u16;
|
||||||
|
line.spans.push(Span::raw(" ".repeat(pad as usize)));
|
||||||
|
sx += pad;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
out.lines.push(line);
|
||||||
|
}
|
||||||
|
new_highlight
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_margin(&self, out: &mut Text, symbol: &SymbolDiff, rect: Rect) {
|
||||||
|
for ins_row in symbol.instruction_rows.iter().skip(self.scroll_y).take(rect.height as usize)
|
||||||
|
{
|
||||||
|
if ins_row.kind != InstructionDiffKind::None {
|
||||||
|
out.lines.push(Line::raw(match ins_row.kind {
|
||||||
|
InstructionDiffKind::Delete => "<",
|
||||||
|
InstructionDiffKind::Insert => ">",
|
||||||
|
_ => "|",
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
out.lines.push(Line::raw(" "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_build_status<'a>(&self, out: &mut Text<'a>, status: &'a BuildStatus) {
|
||||||
|
if !status.cmdline.is_empty() {
|
||||||
|
out.lines.push(Line::styled(status.cmdline.clone(), Style::new().fg(Color::LightBlue)));
|
||||||
|
}
|
||||||
|
for line in status.stdout.lines() {
|
||||||
|
out.lines.push(Line::styled(line, Style::new().fg(Color::White)));
|
||||||
|
}
|
||||||
|
for line in status.stderr.lines() {
|
||||||
|
out.lines.push(Line::styled(line, Style::new().fg(Color::Red)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const COLOR_ROTATION: [Color; 7] = [
|
||||||
|
Color::Magenta,
|
||||||
|
Color::Cyan,
|
||||||
|
Color::Green,
|
||||||
|
Color::Red,
|
||||||
|
Color::Yellow,
|
||||||
|
Color::Blue,
|
||||||
|
Color::Green,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn match_percent_color(match_percent: f32) -> Color {
|
||||||
|
if match_percent == 100.0 {
|
||||||
|
Color::Green
|
||||||
|
} else if match_percent >= 50.0 {
|
||||||
|
Color::LightBlue
|
||||||
|
} else {
|
||||||
|
Color::LightRed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn get_symbol(
|
||||||
|
obj: Option<&(Object, ObjectDiff)>,
|
||||||
|
sym: Option<usize>,
|
||||||
|
) -> Option<(&Object, usize, &SymbolDiff)> {
|
||||||
|
let (obj, diff) = obj?;
|
||||||
|
let sym = sym?;
|
||||||
|
Some((obj, sym, &diff.symbols[sym]))
|
||||||
|
}
|
||||||
25
objdiff-cli/src/views/mod.rs
Normal file
25
objdiff-cli/src/views/mod.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use anyhow::Result;
|
||||||
|
use crossterm::event::Event;
|
||||||
|
use ratatui::Frame;
|
||||||
|
|
||||||
|
use crate::cmd::diff::AppState;
|
||||||
|
|
||||||
|
pub mod function_diff;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct EventResult {
|
||||||
|
pub redraw: bool,
|
||||||
|
pub click_xy: Option<(u16, u16)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum EventControlFlow {
|
||||||
|
Break,
|
||||||
|
Continue(EventResult),
|
||||||
|
Reload,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait UiView {
|
||||||
|
fn draw(&mut self, state: &AppState, f: &mut Frame, result: &mut EventResult);
|
||||||
|
fn handle_event(&mut self, state: &mut AppState, event: Event) -> EventControlFlow;
|
||||||
|
fn reload(&mut self, state: &AppState) -> Result<()>;
|
||||||
|
}
|
||||||
@@ -12,75 +12,198 @@ A local diffing tool for decompilation projects.
|
|||||||
"""
|
"""
|
||||||
documentation = "https://docs.rs/objdiff-core"
|
documentation = "https://docs.rs/objdiff-core"
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["cdylib", "rlib"]
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "arm64", "bindings"]
|
default = ["std"]
|
||||||
any-arch = ["config", "dep:bimap", "dep:strum", "dep:similar", "dep:flagset", "dep:log", "dep:memmap2", "dep:byteorder", "dep:num-traits"] # Implicit, used to check if any arch is enabled
|
all = [
|
||||||
config = ["dep:bimap", "dep:globset", "dep:semver", "dep:serde_json", "dep:serde_yaml", "dep:serde", "dep:filetime"]
|
# Features
|
||||||
dwarf = ["dep:gimli"]
|
"bindings",
|
||||||
mips = ["any-arch", "dep:rabbitizer"]
|
"build",
|
||||||
ppc = ["any-arch", "dep:cwdemangle", "dep:cwextab", "dep:ppc750cl"]
|
"config",
|
||||||
x86 = ["any-arch", "dep:cpp_demangle", "dep:iced-x86", "dep:msvc-demangler"]
|
"dwarf",
|
||||||
arm = ["any-arch", "dep:cpp_demangle", "dep:unarm", "dep:arm-attr"]
|
"serde",
|
||||||
arm64 = ["any-arch", "dep:cpp_demangle", "dep:yaxpeax-arch", "dep:yaxpeax-arm"]
|
# Architectures
|
||||||
bindings = ["dep:serde_json", "dep:prost", "dep:pbjson", "dep:serde", "dep:prost-build", "dep:pbjson-build"]
|
"arm",
|
||||||
wasm = ["bindings", "any-arch", "dep:console_error_panic_hook", "dep:console_log", "dep:wasm-bindgen", "dep:tsify-next", "dep:log"]
|
"arm64",
|
||||||
|
"mips",
|
||||||
|
"ppc",
|
||||||
|
"x86",
|
||||||
|
"superh"
|
||||||
|
]
|
||||||
|
# Implicit, used to check if any arch is enabled
|
||||||
|
any-arch = [
|
||||||
|
"dep:flagset",
|
||||||
|
"dep:heck",
|
||||||
|
"dep:log",
|
||||||
|
"dep:num-traits",
|
||||||
|
"dep:prettyplease",
|
||||||
|
"dep:proc-macro2",
|
||||||
|
"dep:quote",
|
||||||
|
"dep:regex",
|
||||||
|
"dep:similar",
|
||||||
|
"dep:syn",
|
||||||
|
"dep:encoding_rs"
|
||||||
|
]
|
||||||
|
bindings = [
|
||||||
|
"dep:prost",
|
||||||
|
"dep:prost-build",
|
||||||
|
]
|
||||||
|
build = [
|
||||||
|
"dep:notify",
|
||||||
|
"dep:notify-debouncer-full",
|
||||||
|
"dep:reqwest",
|
||||||
|
"dep:self_update",
|
||||||
|
"dep:shell-escape",
|
||||||
|
"dep:tempfile",
|
||||||
|
"dep:time",
|
||||||
|
"dep:winapi",
|
||||||
|
]
|
||||||
|
config = [
|
||||||
|
"dep:globset",
|
||||||
|
"dep:semver",
|
||||||
|
"dep:typed-path",
|
||||||
|
]
|
||||||
|
dwarf = [
|
||||||
|
"dep:gimli",
|
||||||
|
"dep:typed-arena",
|
||||||
|
]
|
||||||
|
serde = [
|
||||||
|
"dep:pbjson",
|
||||||
|
"dep:pbjson-build",
|
||||||
|
"dep:serde",
|
||||||
|
"dep:serde_json",
|
||||||
|
]
|
||||||
|
std = [
|
||||||
|
"anyhow/std",
|
||||||
|
"flagset?/std",
|
||||||
|
"log?/std",
|
||||||
|
"num-traits?/std",
|
||||||
|
"object/std",
|
||||||
|
"prost?/std",
|
||||||
|
"serde?/std",
|
||||||
|
"similar?/std",
|
||||||
|
"typed-arena?/std",
|
||||||
|
"typed-path?/std",
|
||||||
|
"dep:filetime",
|
||||||
|
"dep:memmap2",
|
||||||
|
]
|
||||||
|
mips = [
|
||||||
|
"any-arch",
|
||||||
|
"dep:cpp_demangle",
|
||||||
|
"dep:cwdemangle",
|
||||||
|
"dep:rabbitizer",
|
||||||
|
]
|
||||||
|
ppc = [
|
||||||
|
"any-arch",
|
||||||
|
"dep:cwdemangle",
|
||||||
|
"dep:cwextab",
|
||||||
|
"dep:powerpc",
|
||||||
|
"dep:rlwinmdec",
|
||||||
|
]
|
||||||
|
x86 = [
|
||||||
|
"any-arch",
|
||||||
|
"dep:cpp_demangle",
|
||||||
|
"dep:iced-x86",
|
||||||
|
"dep:msvc-demangler",
|
||||||
|
]
|
||||||
|
arm = [
|
||||||
|
"any-arch",
|
||||||
|
"dep:arm-attr",
|
||||||
|
"dep:cpp_demangle",
|
||||||
|
"dep:unarm",
|
||||||
|
]
|
||||||
|
arm64 = [
|
||||||
|
"any-arch",
|
||||||
|
"dep:cpp_demangle",
|
||||||
|
"dep:yaxpeax-arch",
|
||||||
|
"dep:yaxpeax-arm",
|
||||||
|
]
|
||||||
|
superh = [
|
||||||
|
"any-arch",
|
||||||
|
]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["all"]
|
features = ["all"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = { version = "1.0", default-features = false }
|
||||||
bimap = { version = "0.6", features = ["serde"], optional = true }
|
|
||||||
byteorder = { version = "1.5", optional = true }
|
|
||||||
filetime = { version = "0.2", optional = true }
|
filetime = { version = "0.2", optional = true }
|
||||||
flagset = { version = "0.4", optional = true }
|
flagset = { version = "0.4", default-features = false, optional = true }
|
||||||
log = { version = "0.4", optional = true }
|
itertools = { version = "0.14", default-features = false, features = ["use_alloc"] }
|
||||||
|
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", optional = true }
|
num-traits = { version = "0.2", default-features = false, optional = true }
|
||||||
object = { version = "0.36", features = ["read_core", "std", "elf", "pe"], default-features = false }
|
object = { version = "0.37", default-features = false, features = ["read_core", "elf", "coff"] }
|
||||||
pbjson = { version = "0.7", optional = true }
|
pbjson = { version = "0.8", default-features = false, optional = true }
|
||||||
prost = { version = "0.13", optional = true }
|
prost = { version = "0.14", default-features = false, features = ["derive"], optional = true }
|
||||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
regex = { version = "1.11", default-features = false, features = [], optional = true }
|
||||||
similar = { version = "2.6", default-features = false, optional = true }
|
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
|
||||||
strum = { version = "0.26", features = ["derive"], optional = true }
|
similar = { git = "https://github.com/encounter/similar.git", branch = "no_std", default-features = false, features = ["hashbrown"], optional = true }
|
||||||
wasm-bindgen = { version = "0.2", optional = true }
|
typed-path = { version = "0.11", default-features = false, optional = true }
|
||||||
tsify-next = { version = "0.5", default-features = false, features = ["js"], optional = true }
|
|
||||||
console_log = { version = "1.0", optional = true }
|
|
||||||
console_error_panic_hook = { version = "0.1", optional = true }
|
|
||||||
|
|
||||||
# config
|
# config
|
||||||
globset = { version = "0.4", features = ["serde1"], optional = true }
|
globset = { version = "0.4", default-features = false, optional = true }
|
||||||
semver = { version = "1.0", optional = true }
|
semver = { version = "1.0", default-features = false, optional = true }
|
||||||
serde_json = { version = "1.0", optional = true }
|
serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true }
|
||||||
serde_yaml = { version = "0.9", optional = true }
|
|
||||||
|
|
||||||
# dwarf
|
# dwarf
|
||||||
gimli = { version = "0.31", default-features = false, features = ["read-all"], optional = true }
|
gimli = { git = "https://github.com/gimli-rs/gimli", rev = "7335f00e7c39fd501511584fefb0ba974117c950", 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 }
|
cwdemangle = { version = "1.0", optional = true }
|
||||||
cwextab = { version = "1.0.2", optional = true }
|
cwextab = { version = "1.1", optional = true }
|
||||||
ppc750cl = { version = "0.3", optional = true }
|
powerpc = { version = "0.4", optional = true }
|
||||||
|
rlwinmdec = { version = "1.1", optional = true }
|
||||||
|
|
||||||
# mips
|
# mips
|
||||||
rabbitizer = { version = "1.12", optional = true }
|
rabbitizer = { version = "2.0.0-alpha.4", default-features = false, features = ["all_extensions"], optional = true }
|
||||||
|
|
||||||
# x86
|
# x86
|
||||||
cpp_demangle = { version = "0.4", optional = true }
|
cpp_demangle = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
|
||||||
iced-x86 = { version = "1.21", default-features = false, features = ["std", "decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums"], 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.10", optional = true }
|
msvc-demangler = { version = "0.11", optional = true }
|
||||||
|
|
||||||
# arm
|
# arm
|
||||||
unarm = { version = "1.6", optional = true }
|
unarm = { version = "1.9", optional = true }
|
||||||
arm-attr = { version = "0.1", optional = true }
|
arm-attr = { version = "0.2", optional = true }
|
||||||
|
|
||||||
# arm64
|
# arm64
|
||||||
yaxpeax-arch = { version = "0.3", default-features = false, features = ["std"], optional = true }
|
yaxpeax-arch = { version = "0.3", default-features = false, optional = true }
|
||||||
yaxpeax-arm = { version = "0.3", default-features = false, features = ["std"], optional = true }
|
yaxpeax-arm = { version = "0.3", default-features = false, optional = true }
|
||||||
|
|
||||||
|
# build
|
||||||
|
notify = { version = "8.1.0", optional = true }
|
||||||
|
notify-debouncer-full = { version = "0.5.0", optional = true }
|
||||||
|
shell-escape = { version = "0.1", optional = true }
|
||||||
|
tempfile = { version = "3.20", optional = true }
|
||||||
|
time = { version = "0.3", optional = true }
|
||||||
|
encoding_rs = { version = "0.8.35", optional = true }
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
winapi = { version = "0.3", optional = true, features = ["winbase"] }
|
||||||
|
|
||||||
|
# For Linux static binaries, use rustls
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "rustls-tls"], optional = true }
|
||||||
|
self_update = { version = "0.42", default-features = false, features = ["rustls"], optional = true }
|
||||||
|
|
||||||
|
# For all other platforms, use native TLS
|
||||||
|
[target.'cfg(not(target_os = "linux"))'.dependencies]
|
||||||
|
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "default-tls"], optional = true }
|
||||||
|
self_update = { version = "0.42", optional = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
prost-build = { version = "0.13", optional = true }
|
heck = { version = "0.5", optional = true }
|
||||||
pbjson-build = { version = "0.7", optional = true }
|
pbjson-build = { version = "0.8", optional = true }
|
||||||
|
prettyplease = { version = "0.2", optional = true }
|
||||||
|
proc-macro2 = { version = "1.0", optional = true }
|
||||||
|
prost-build = { version = "0.14", optional = true }
|
||||||
|
quote = { version = "1.0", optional = true }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = { version = "1.0" }
|
||||||
|
syn = { version = "2.0", optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
# Enable all features for tests
|
||||||
|
objdiff-core = { path = ".", features = ["all"] }
|
||||||
|
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).
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
fn main() {
|
#[cfg(feature = "any-arch")]
|
||||||
|
mod config_gen;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
#[cfg(feature = "bindings")]
|
#[cfg(feature = "bindings")]
|
||||||
compile_protos();
|
compile_protos();
|
||||||
|
#[cfg(feature = "any-arch")]
|
||||||
|
config_gen::generate_diff_config();
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bindings")]
|
#[cfg(feature = "bindings")]
|
||||||
@@ -13,7 +19,15 @@ fn compile_protos() {
|
|||||||
.map(|m| m.modified().unwrap())
|
.map(|m| m.modified().unwrap())
|
||||||
.unwrap_or(std::time::SystemTime::UNIX_EPOCH);
|
.unwrap_or(std::time::SystemTime::UNIX_EPOCH);
|
||||||
let mut run_protoc = false;
|
let mut run_protoc = false;
|
||||||
let proto_files = vec![root.join("diff.proto"), root.join("report.proto")];
|
let proto_files = root
|
||||||
|
.read_dir()
|
||||||
|
.unwrap()
|
||||||
|
.filter_map(|e| {
|
||||||
|
let e = e.unwrap();
|
||||||
|
let path = e.path();
|
||||||
|
(path.extension() == Some(std::ffi::OsStr::new("proto"))).then_some(path)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
for proto_file in &proto_files {
|
for proto_file in &proto_files {
|
||||||
println!("cargo:rerun-if-changed={}", proto_file.display());
|
println!("cargo:rerun-if-changed={}", proto_file.display());
|
||||||
let mtime = match std::fs::metadata(proto_file) {
|
let mtime = match std::fs::metadata(proto_file) {
|
||||||
@@ -49,11 +63,14 @@ fn compile_protos() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let descriptor_set = std::fs::read(descriptor_path).expect("Failed to read descriptor set");
|
#[cfg(feature = "serde")]
|
||||||
pbjson_build::Builder::new()
|
{
|
||||||
.register_descriptors(&descriptor_set)
|
let descriptor_set = std::fs::read(descriptor_path).expect("Failed to read descriptor set");
|
||||||
.expect("Failed to register descriptors")
|
pbjson_build::Builder::new()
|
||||||
.preserve_proto_field_names()
|
.register_descriptors(&descriptor_set)
|
||||||
.build(&[".objdiff"])
|
.expect("Failed to register descriptors")
|
||||||
.expect("Failed to build pbjson");
|
.preserve_proto_field_names()
|
||||||
|
.build(&[".objdiff"])
|
||||||
|
.expect("Failed to build pbjson");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
293
objdiff-core/config-schema.json
Normal file
293
objdiff-core/config-schema.json
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
{
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"id": "functionRelocDiffs",
|
||||||
|
"type": "choice",
|
||||||
|
"default": "name_address",
|
||||||
|
"name": "Function relocation diffs",
|
||||||
|
"description": "How relocation targets will be diffed in the function view.",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"value": "none",
|
||||||
|
"name": "None"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "name_address",
|
||||||
|
"name": "Name or address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "data_value",
|
||||||
|
"name": "Data value"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "all",
|
||||||
|
"name": "Name or address, data value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"name": "Space between args",
|
||||||
|
"description": "Adds a space between arguments in the diff output."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "combineDataSections",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"name": "Combine data sections",
|
||||||
|
"description": "Combines data sections with equal names."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "combineTextSections",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"name": "Combine text sections",
|
||||||
|
"description": "Combines all text sections into one."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arm.archVersion",
|
||||||
|
"type": "choice",
|
||||||
|
"default": "auto",
|
||||||
|
"name": "Architecture version",
|
||||||
|
"description": "ARM architecture version to use for disassembly.",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"value": "auto",
|
||||||
|
"name": "Auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "v4t",
|
||||||
|
"name": "ARMv4T (GBA)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "v5te",
|
||||||
|
"name": "ARMv5TE (DS)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "v6k",
|
||||||
|
"name": "ARMv6K (3DS)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arm.unifiedSyntax",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"name": "Unified syntax",
|
||||||
|
"description": "Disassemble as unified assembly language (UAL)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arm.avRegisters",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"name": "Use A/V registers",
|
||||||
|
"description": "Display R0-R3 as A1-A4 and R4-R11 as V1-V8."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arm.r9Usage",
|
||||||
|
"type": "choice",
|
||||||
|
"default": "generalPurpose",
|
||||||
|
"name": "Display R9 as",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"value": "generalPurpose",
|
||||||
|
"name": "R9 or V6",
|
||||||
|
"description": "Use R9 as a general-purpose register."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "sb",
|
||||||
|
"name": "SB (static base)",
|
||||||
|
"description": "Used for position-independent data (PID)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "tr",
|
||||||
|
"name": "TR (TLS register)",
|
||||||
|
"description": "Used for thread-local storage."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arm.slUsage",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"name": "Display R10 as SL",
|
||||||
|
"description": "Used for explicit stack limits."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arm.fpUsage",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"name": "Display R11 as FP",
|
||||||
|
"description": "Used for frame pointers."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arm.ipUsage",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"name": "Display R12 as IP",
|
||||||
|
"description": "Used for interworking and long branches."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mips.abi",
|
||||||
|
"type": "choice",
|
||||||
|
"default": "auto",
|
||||||
|
"name": "ABI",
|
||||||
|
"description": "MIPS ABI to use for disassembly.",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"value": "auto",
|
||||||
|
"name": "Auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "o32",
|
||||||
|
"name": "O32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "n32",
|
||||||
|
"name": "N32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "n64",
|
||||||
|
"name": "N64"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mips.instrCategory",
|
||||||
|
"type": "choice",
|
||||||
|
"default": "auto",
|
||||||
|
"name": "Instruction category",
|
||||||
|
"description": "MIPS instruction category to use for disassembly.",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"value": "auto",
|
||||||
|
"name": "Auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "cpu",
|
||||||
|
"name": "CPU"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "rsp",
|
||||||
|
"name": "RSP (N64)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "r3000gte",
|
||||||
|
"name": "R3000 GTE (PS1)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "r4000allegrex",
|
||||||
|
"name": "R4000 ALLEGREX (PSP)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "r5900",
|
||||||
|
"name": "R5900 EE (PS2)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mips.registerPrefix",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"name": "Register '$' prefix",
|
||||||
|
"description": "Display MIPS register names with a '$' prefix."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ppc.calculatePoolRelocations",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"name": "Calculate pooled data references",
|
||||||
|
"description": "Display pooled data references in functions as fake relocations."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "x86.formatter",
|
||||||
|
"type": "choice",
|
||||||
|
"default": "intel",
|
||||||
|
"name": "Format",
|
||||||
|
"description": "x86 disassembly syntax.",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"value": "intel",
|
||||||
|
"name": "Intel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "gas",
|
||||||
|
"name": "AT&T"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "nasm",
|
||||||
|
"name": "NASM"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "masm",
|
||||||
|
"name": "MASM"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"id": "general",
|
||||||
|
"name": "General",
|
||||||
|
"properties": [
|
||||||
|
"functionRelocDiffs",
|
||||||
|
"spaceBetweenArgs",
|
||||||
|
"combineDataSections",
|
||||||
|
"combineTextSections"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "arm",
|
||||||
|
"name": "ARM",
|
||||||
|
"properties": [
|
||||||
|
"arm.archVersion",
|
||||||
|
"arm.unifiedSyntax",
|
||||||
|
"arm.avRegisters",
|
||||||
|
"arm.r9Usage",
|
||||||
|
"arm.slUsage",
|
||||||
|
"arm.fpUsage",
|
||||||
|
"arm.ipUsage"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mips",
|
||||||
|
"name": "MIPS",
|
||||||
|
"properties": [
|
||||||
|
"mips.abi",
|
||||||
|
"mips.instrCategory",
|
||||||
|
"mips.registerPrefix"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ppc",
|
||||||
|
"name": "PowerPC",
|
||||||
|
"properties": [
|
||||||
|
"ppc.calculatePoolRelocations",
|
||||||
|
"analyzeDataFlow"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "x86",
|
||||||
|
"name": "x86",
|
||||||
|
"properties": [
|
||||||
|
"x86.formatter"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
500
objdiff-core/config_gen.rs
Normal file
500
objdiff-core/config_gen.rs
Normal file
@@ -0,0 +1,500 @@
|
|||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{format_ident, quote};
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub struct ConfigSchema {
|
||||||
|
pub properties: Vec<ConfigProperty>,
|
||||||
|
pub groups: Vec<ConfigGroup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum ConfigProperty {
|
||||||
|
#[serde(rename = "boolean")]
|
||||||
|
Boolean(ConfigPropertyBoolean),
|
||||||
|
#[serde(rename = "choice")]
|
||||||
|
Choice(ConfigPropertyChoice),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub struct ConfigPropertyBase {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub struct ConfigPropertyBoolean {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub base: ConfigPropertyBase,
|
||||||
|
pub default: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub struct ConfigPropertyChoice {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub base: ConfigPropertyBase,
|
||||||
|
pub default: String,
|
||||||
|
pub items: Vec<ConfigPropertyChoiceItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub struct ConfigPropertyChoiceItem {
|
||||||
|
pub value: String,
|
||||||
|
pub name: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub struct ConfigGroup {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub properties: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_doc(name: &str, description: Option<&str>) -> TokenStream {
|
||||||
|
let mut doc = format!(" {name}");
|
||||||
|
let mut out = quote! { #[doc = #doc] };
|
||||||
|
if let Some(description) = description {
|
||||||
|
doc = format!(" {description}");
|
||||||
|
out.extend(quote! { #[doc = ""] });
|
||||||
|
out.extend(quote! { #[doc = #doc] });
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_diff_config() {
|
||||||
|
let schema_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("config-schema.json");
|
||||||
|
println!("cargo:rerun-if-changed={}", schema_path.display());
|
||||||
|
let schema_file = File::open(schema_path).expect("Failed to open config schema file");
|
||||||
|
let schema: ConfigSchema =
|
||||||
|
serde_json::from_reader(schema_file).expect("Failed to parse config schema");
|
||||||
|
|
||||||
|
let mut enums = TokenStream::new();
|
||||||
|
for property in &schema.properties {
|
||||||
|
let ConfigProperty::Choice(choice) = property else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let enum_ident = format_ident!("{}", choice.base.id.to_upper_camel_case());
|
||||||
|
let mut variants = TokenStream::new();
|
||||||
|
let mut full_variants = TokenStream::new();
|
||||||
|
let mut variant_info = TokenStream::new();
|
||||||
|
let mut variant_to_str = TokenStream::new();
|
||||||
|
let mut variant_to_name = TokenStream::new();
|
||||||
|
let mut variant_to_description = TokenStream::new();
|
||||||
|
let mut variant_from_str = TokenStream::new();
|
||||||
|
for item in &choice.items {
|
||||||
|
let variant_name = item.value.to_upper_camel_case();
|
||||||
|
let variant_ident = format_ident!("{}", variant_name);
|
||||||
|
let is_default = item.value == choice.default;
|
||||||
|
variants.extend(build_doc(&item.name, item.description.as_deref()));
|
||||||
|
if is_default {
|
||||||
|
variants.extend(quote! { #[default] });
|
||||||
|
}
|
||||||
|
let value = &item.value;
|
||||||
|
variants.extend(quote! {
|
||||||
|
#[cfg_attr(feature = "serde", serde(rename = #value, alias = #variant_name))]
|
||||||
|
#variant_ident,
|
||||||
|
});
|
||||||
|
full_variants.extend(quote! { #enum_ident::#variant_ident, });
|
||||||
|
variant_to_str.extend(quote! { #enum_ident::#variant_ident => #value, });
|
||||||
|
let name = &item.name;
|
||||||
|
variant_to_name.extend(quote! { #enum_ident::#variant_ident => #name, });
|
||||||
|
if let Some(description) = &item.description {
|
||||||
|
variant_to_description.extend(quote! {
|
||||||
|
#enum_ident::#variant_ident => Some(#description),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
variant_to_description.extend(quote! {
|
||||||
|
#enum_ident::#variant_ident => None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let description = if let Some(description) = &item.description {
|
||||||
|
quote! { Some(#description) }
|
||||||
|
} else {
|
||||||
|
quote! { None }
|
||||||
|
};
|
||||||
|
variant_info.extend(quote! {
|
||||||
|
ConfigEnumVariantInfo {
|
||||||
|
value: #value,
|
||||||
|
name: #name,
|
||||||
|
description: #description,
|
||||||
|
is_default: #is_default,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
variant_from_str.extend(quote! {
|
||||||
|
if s.eq_ignore_ascii_case(#value) { return Ok(#enum_ident::#variant_ident); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
enums.extend(quote! {
|
||||||
|
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
pub enum #enum_ident {
|
||||||
|
#variants
|
||||||
|
}
|
||||||
|
impl ConfigEnum for #enum_ident {
|
||||||
|
#[inline]
|
||||||
|
fn variants() -> &'static [Self] {
|
||||||
|
static VARIANTS: &[#enum_ident] = &[#full_variants];
|
||||||
|
VARIANTS
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn variant_info() -> &'static [ConfigEnumVariantInfo] {
|
||||||
|
static VARIANT_INFO: &[ConfigEnumVariantInfo] = &[
|
||||||
|
#variant_info
|
||||||
|
];
|
||||||
|
VARIANT_INFO
|
||||||
|
}
|
||||||
|
fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
#variant_to_str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
#variant_to_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn description(&self) -> Option<&'static str> {
|
||||||
|
match self {
|
||||||
|
#variant_to_description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl core::str::FromStr for #enum_ident {
|
||||||
|
type Err = ();
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
#variant_from_str
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut groups = TokenStream::new();
|
||||||
|
let mut group_idents = Vec::new();
|
||||||
|
for group in &schema.groups {
|
||||||
|
let ident = format_ident!("CONFIG_GROUP_{}", group.id.to_shouty_snake_case());
|
||||||
|
let id = &group.id;
|
||||||
|
let name = &group.name;
|
||||||
|
let description = if let Some(description) = &group.description {
|
||||||
|
quote! { Some(#description) }
|
||||||
|
} else {
|
||||||
|
quote! { None }
|
||||||
|
};
|
||||||
|
let properties =
|
||||||
|
group.properties.iter().map(|p| format_ident!("{}", p.to_upper_camel_case()));
|
||||||
|
groups.extend(quote! {
|
||||||
|
ConfigPropertyGroup {
|
||||||
|
id: #id,
|
||||||
|
name: #name,
|
||||||
|
description: #description,
|
||||||
|
properties: &[#(ConfigPropertyId::#properties,)*],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
group_idents.push(ident);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut property_idents = Vec::new();
|
||||||
|
let mut property_variants = TokenStream::new();
|
||||||
|
let mut variant_info = TokenStream::new();
|
||||||
|
let mut config_property_id_to_str = TokenStream::new();
|
||||||
|
let mut config_property_id_to_name = TokenStream::new();
|
||||||
|
let mut config_property_id_to_description = TokenStream::new();
|
||||||
|
let mut config_property_id_to_kind = TokenStream::new();
|
||||||
|
let mut property_fields = TokenStream::new();
|
||||||
|
let mut default_fields = TokenStream::new();
|
||||||
|
let mut get_property_value_variants = TokenStream::new();
|
||||||
|
let mut set_property_value_variants = TokenStream::new();
|
||||||
|
let mut set_property_value_str_variants = TokenStream::new();
|
||||||
|
let mut config_property_id_from_str = TokenStream::new();
|
||||||
|
for property in &schema.properties {
|
||||||
|
let base = match property {
|
||||||
|
ConfigProperty::Boolean(b) => &b.base,
|
||||||
|
ConfigProperty::Choice(c) => &c.base,
|
||||||
|
};
|
||||||
|
let id = &base.id;
|
||||||
|
let enum_ident = format_ident!("{}", id.to_upper_camel_case());
|
||||||
|
property_idents.push(enum_ident.clone());
|
||||||
|
config_property_id_to_str.extend(quote! { Self::#enum_ident => #id, });
|
||||||
|
let name = &base.name;
|
||||||
|
config_property_id_to_name.extend(quote! { Self::#enum_ident => #name, });
|
||||||
|
if let Some(description) = &base.description {
|
||||||
|
config_property_id_to_description.extend(quote! {
|
||||||
|
Self::#enum_ident => Some(#description),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
config_property_id_to_description.extend(quote! {
|
||||||
|
Self::#enum_ident => None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let doc = build_doc(name, base.description.as_deref());
|
||||||
|
property_variants.extend(quote! { #doc #enum_ident, });
|
||||||
|
property_fields.extend(doc);
|
||||||
|
let field_ident = format_ident!("{}", id.to_snake_case());
|
||||||
|
match property {
|
||||||
|
ConfigProperty::Boolean(b) => {
|
||||||
|
let default = b.default;
|
||||||
|
if default {
|
||||||
|
property_fields.extend(quote! {
|
||||||
|
#[cfg_attr(feature = "serde", serde(default = "default_true"))]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
property_fields.extend(quote! {
|
||||||
|
pub #field_ident: bool,
|
||||||
|
});
|
||||||
|
default_fields.extend(quote! {
|
||||||
|
#field_ident: #default,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ConfigProperty::Choice(_) => {
|
||||||
|
property_fields.extend(quote! {
|
||||||
|
pub #field_ident: #enum_ident,
|
||||||
|
});
|
||||||
|
default_fields.extend(quote! {
|
||||||
|
#field_ident: #enum_ident::default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let property_value = match property {
|
||||||
|
ConfigProperty::Boolean(_) => {
|
||||||
|
quote! { ConfigPropertyValue::Boolean(self.#field_ident) }
|
||||||
|
}
|
||||||
|
ConfigProperty::Choice(_) => {
|
||||||
|
quote! { ConfigPropertyValue::Choice(self.#field_ident.as_str()) }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
get_property_value_variants.extend(quote! {
|
||||||
|
ConfigPropertyId::#enum_ident => #property_value,
|
||||||
|
});
|
||||||
|
match property {
|
||||||
|
ConfigProperty::Boolean(_) => {
|
||||||
|
set_property_value_variants.extend(quote! {
|
||||||
|
ConfigPropertyId::#enum_ident => {
|
||||||
|
if let ConfigPropertyValue::Boolean(value) = value {
|
||||||
|
self.#field_ident = value;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
set_property_value_str_variants.extend(quote! {
|
||||||
|
ConfigPropertyId::#enum_ident => {
|
||||||
|
if let Ok(value) = value.parse() {
|
||||||
|
self.#field_ident = value;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ConfigProperty::Choice(_) => {
|
||||||
|
set_property_value_variants.extend(quote! {
|
||||||
|
ConfigPropertyId::#enum_ident => {
|
||||||
|
if let ConfigPropertyValue::Choice(value) = value {
|
||||||
|
if let Ok(value) = value.parse() {
|
||||||
|
self.#field_ident = value;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
set_property_value_str_variants.extend(quote! {
|
||||||
|
ConfigPropertyId::#enum_ident => {
|
||||||
|
if let Ok(value) = value.parse() {
|
||||||
|
self.#field_ident = value;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let description = if let Some(description) = &base.description {
|
||||||
|
quote! { Some(#description) }
|
||||||
|
} else {
|
||||||
|
quote! { None }
|
||||||
|
};
|
||||||
|
variant_info.extend(quote! {
|
||||||
|
ConfigEnumVariantInfo {
|
||||||
|
value: #id,
|
||||||
|
name: #name,
|
||||||
|
description: #description,
|
||||||
|
is_default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
match property {
|
||||||
|
ConfigProperty::Boolean(_) => {
|
||||||
|
config_property_id_to_kind.extend(quote! {
|
||||||
|
Self::#enum_ident => ConfigPropertyKind::Boolean,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ConfigProperty::Choice(_) => {
|
||||||
|
config_property_id_to_kind.extend(quote! {
|
||||||
|
Self::#enum_ident => ConfigPropertyKind::Choice(#enum_ident::variant_info()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let snake_id = id.to_snake_case();
|
||||||
|
config_property_id_from_str.extend(quote! {
|
||||||
|
if s.eq_ignore_ascii_case(#id) || s.eq_ignore_ascii_case(#snake_id) {
|
||||||
|
return Ok(Self::#enum_ident);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let tokens = quote! {
|
||||||
|
pub trait ConfigEnum: Sized {
|
||||||
|
fn variants() -> &'static [Self];
|
||||||
|
fn variant_info() -> &'static [ConfigEnumVariantInfo];
|
||||||
|
fn as_str(&self) -> &'static str;
|
||||||
|
fn name(&self) -> &'static str;
|
||||||
|
fn description(&self) -> Option<&'static str>;
|
||||||
|
}
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ConfigEnumVariantInfo {
|
||||||
|
pub value: &'static str,
|
||||||
|
pub name: &'static str,
|
||||||
|
pub description: Option<&'static str>,
|
||||||
|
pub is_default: bool,
|
||||||
|
}
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
pub enum ConfigPropertyId {
|
||||||
|
#property_variants
|
||||||
|
}
|
||||||
|
impl ConfigEnum for ConfigPropertyId {
|
||||||
|
#[inline]
|
||||||
|
fn variants() -> &'static [Self] {
|
||||||
|
static VARIANTS: &[ConfigPropertyId] = &[#(ConfigPropertyId::#property_idents,)*];
|
||||||
|
VARIANTS
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn variant_info() -> &'static [ConfigEnumVariantInfo] {
|
||||||
|
static VARIANT_INFO: &[ConfigEnumVariantInfo] = &[
|
||||||
|
#variant_info
|
||||||
|
];
|
||||||
|
VARIANT_INFO
|
||||||
|
}
|
||||||
|
fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
#config_property_id_to_str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
#config_property_id_to_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn description(&self) -> Option<&'static str> {
|
||||||
|
match self {
|
||||||
|
#config_property_id_to_description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ConfigPropertyId {
|
||||||
|
pub fn kind(&self) -> ConfigPropertyKind {
|
||||||
|
match self {
|
||||||
|
#config_property_id_to_kind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl core::str::FromStr for ConfigPropertyId {
|
||||||
|
type Err = ();
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
#config_property_id_from_str
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ConfigPropertyGroup {
|
||||||
|
pub id: &'static str,
|
||||||
|
pub name: &'static str,
|
||||||
|
pub description: Option<&'static str>,
|
||||||
|
pub properties: &'static [ConfigPropertyId],
|
||||||
|
}
|
||||||
|
pub static CONFIG_GROUPS: &[ConfigPropertyGroup] = &[#groups];
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
pub enum ConfigPropertyValue {
|
||||||
|
Boolean(bool),
|
||||||
|
Choice(&'static str),
|
||||||
|
}
|
||||||
|
impl ConfigPropertyValue {
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
pub fn to_json(&self) -> serde_json::Value {
|
||||||
|
match self {
|
||||||
|
ConfigPropertyValue::Boolean(value) => serde_json::Value::Bool(*value),
|
||||||
|
ConfigPropertyValue::Choice(value) => serde_json::Value::String(value.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl core::fmt::Display for ConfigPropertyValue {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
match *self {
|
||||||
|
ConfigPropertyValue::Boolean(value) => write!(f, "{value}"),
|
||||||
|
ConfigPropertyValue::Choice(value) => f.write_str(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum ConfigPropertyKind {
|
||||||
|
Boolean,
|
||||||
|
Choice(&'static [ConfigEnumVariantInfo]),
|
||||||
|
}
|
||||||
|
#enums
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
#[inline(always)]
|
||||||
|
fn default_true() -> bool { true }
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(default))]
|
||||||
|
pub struct DiffObjConfig {
|
||||||
|
#property_fields
|
||||||
|
}
|
||||||
|
impl Default for DiffObjConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
#default_fields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl DiffObjConfig {
|
||||||
|
pub fn get_property_value(&self, id: ConfigPropertyId) -> ConfigPropertyValue {
|
||||||
|
match id {
|
||||||
|
#get_property_value_variants
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[allow(clippy::result_unit_err)]
|
||||||
|
pub fn set_property_value(&mut self, id: ConfigPropertyId, value: ConfigPropertyValue) -> Result<(), ()> {
|
||||||
|
match id {
|
||||||
|
#set_property_value_variants
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[allow(clippy::result_unit_err)]
|
||||||
|
pub fn set_property_value_str(&mut self, id: ConfigPropertyId, value: &str) -> Result<(), ()> {
|
||||||
|
match id {
|
||||||
|
#set_property_value_str_variants
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let file = syn::parse2(tokens).unwrap();
|
||||||
|
let formatted = prettyplease::unparse(&file);
|
||||||
|
std::fs::write(
|
||||||
|
PathBuf::from(std::env::var_os("OUT_DIR").unwrap()).join("config.gen.rs"),
|
||||||
|
formatted,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
59
objdiff-core/protos/changes.proto
Normal file
59
objdiff-core/protos/changes.proto
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
import "report.proto";
|
||||||
|
|
||||||
|
package objdiff.report;
|
||||||
|
|
||||||
|
// A pair of reports to compare and generate changes
|
||||||
|
message ChangesInput {
|
||||||
|
// The previous report
|
||||||
|
Report from = 1;
|
||||||
|
// The current report
|
||||||
|
Report to = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changes between two reports
|
||||||
|
message Changes {
|
||||||
|
// The progress info for the previous report
|
||||||
|
Measures from = 1;
|
||||||
|
// The progress info for the current report
|
||||||
|
Measures to = 2;
|
||||||
|
// Units that changed
|
||||||
|
repeated ChangeUnit units = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A changed unit
|
||||||
|
message ChangeUnit {
|
||||||
|
// The name of the unit
|
||||||
|
string name = 1;
|
||||||
|
// The previous progress info (omitted if new)
|
||||||
|
optional Measures from = 2;
|
||||||
|
// The current progress info (omitted if removed)
|
||||||
|
optional Measures to = 3;
|
||||||
|
// Sections that changed
|
||||||
|
repeated ChangeItem sections = 4;
|
||||||
|
// Functions that changed
|
||||||
|
repeated ChangeItem functions = 5;
|
||||||
|
// Extra metadata for this unit
|
||||||
|
optional ReportUnitMetadata metadata = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A changed section or function
|
||||||
|
message ChangeItem {
|
||||||
|
// The name of the item
|
||||||
|
string name = 1;
|
||||||
|
// The previous progress info (omitted if new)
|
||||||
|
optional ChangeItemInfo from = 2;
|
||||||
|
// The current progress info (omitted if removed)
|
||||||
|
optional ChangeItemInfo to = 3;
|
||||||
|
// Extra metadata for this item
|
||||||
|
optional ReportItemMetadata metadata = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Progress info for a section or function
|
||||||
|
message ChangeItemInfo {
|
||||||
|
// The overall match percent for this item
|
||||||
|
float fuzzy_match_percent = 1;
|
||||||
|
// The size of the item in bytes
|
||||||
|
uint64 size = 2;
|
||||||
|
}
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package objdiff.diff;
|
|
||||||
|
|
||||||
// A symbol
|
|
||||||
message Symbol {
|
|
||||||
// Name of the symbol
|
|
||||||
string name = 1;
|
|
||||||
// Demangled name of the symbol
|
|
||||||
optional string demangled_name = 2;
|
|
||||||
// Symbol address
|
|
||||||
uint64 address = 3;
|
|
||||||
// Symbol size
|
|
||||||
uint64 size = 4;
|
|
||||||
// Bitmask of SymbolFlag
|
|
||||||
uint32 flags = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Symbol visibility flags
|
|
||||||
enum SymbolFlag {
|
|
||||||
SYMBOL_NONE = 0;
|
|
||||||
SYMBOL_GLOBAL = 1;
|
|
||||||
SYMBOL_LOCAL = 2;
|
|
||||||
SYMBOL_WEAK = 3;
|
|
||||||
SYMBOL_COMMON = 4;
|
|
||||||
SYMBOL_HIDDEN = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A single parsed instruction
|
|
||||||
message Instruction {
|
|
||||||
// Instruction address
|
|
||||||
uint64 address = 1;
|
|
||||||
// Instruction size
|
|
||||||
uint32 size = 2;
|
|
||||||
// Instruction opcode
|
|
||||||
uint32 opcode = 3;
|
|
||||||
// Instruction mnemonic
|
|
||||||
string mnemonic = 4;
|
|
||||||
// Instruction formatted string
|
|
||||||
string formatted = 5;
|
|
||||||
// Original (unsimplified) instruction string
|
|
||||||
optional string original = 6;
|
|
||||||
// Instruction arguments
|
|
||||||
repeated Argument arguments = 7;
|
|
||||||
// Instruction relocation
|
|
||||||
optional Relocation relocation = 8;
|
|
||||||
// Instruction branch destination
|
|
||||||
optional uint64 branch_dest = 9;
|
|
||||||
// Instruction line number
|
|
||||||
optional uint32 line_number = 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
// An instruction argument
|
|
||||||
message Argument {
|
|
||||||
oneof value {
|
|
||||||
// Plain text
|
|
||||||
string plain_text = 1;
|
|
||||||
// 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;
|
|
||||||
// Opaque value
|
|
||||||
string opaque = 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marker type for relocation arguments
|
|
||||||
message ArgumentRelocation {
|
|
||||||
}
|
|
||||||
|
|
||||||
message Relocation {
|
|
||||||
uint32 type = 1;
|
|
||||||
string type_name = 2;
|
|
||||||
RelocationTarget target = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RelocationTarget {
|
|
||||||
Symbol symbol = 1;
|
|
||||||
int64 addend = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InstructionDiff {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum DiffKind {
|
|
||||||
DIFF_NONE = 0;
|
|
||||||
DIFF_REPLACE = 1;
|
|
||||||
DIFF_DELETE = 2;
|
|
||||||
DIFF_INSERT = 3;
|
|
||||||
DIFF_OP_MISMATCH = 4;
|
|
||||||
DIFF_ARG_MISMATCH = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InstructionBranchFrom {
|
|
||||||
repeated uint32 instruction_index = 1;
|
|
||||||
uint32 branch_index = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InstructionBranchTo {
|
|
||||||
uint32 instruction_index = 1;
|
|
||||||
uint32 branch_index = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FunctionDiff {
|
|
||||||
Symbol symbol = 1;
|
|
||||||
repeated InstructionDiff instructions = 2;
|
|
||||||
optional float match_percent = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DataDiff {
|
|
||||||
DiffKind kind = 1;
|
|
||||||
bytes data = 2;
|
|
||||||
// May be larger than data
|
|
||||||
uint64 size = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SectionDiff {
|
|
||||||
string name = 1;
|
|
||||||
SectionKind kind = 2;
|
|
||||||
uint64 size = 3;
|
|
||||||
uint64 address = 4;
|
|
||||||
repeated FunctionDiff functions = 5;
|
|
||||||
repeated DataDiff data = 6;
|
|
||||||
optional float match_percent = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum SectionKind {
|
|
||||||
SECTION_UNKNOWN = 0;
|
|
||||||
SECTION_TEXT = 1;
|
|
||||||
SECTION_DATA = 2;
|
|
||||||
SECTION_BSS = 3;
|
|
||||||
SECTION_COMMON = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ObjectDiff {
|
|
||||||
repeated SectionDiff sections = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DiffResult {
|
|
||||||
optional ObjectDiff left = 1;
|
|
||||||
optional ObjectDiff right = 2;
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -2,6 +2,18 @@ syntax = "proto3";
|
|||||||
|
|
||||||
package objdiff.report;
|
package objdiff.report;
|
||||||
|
|
||||||
|
// Project progress report
|
||||||
|
message Report {
|
||||||
|
// Overall progress info
|
||||||
|
Measures measures = 1;
|
||||||
|
// Units within this report
|
||||||
|
repeated ReportUnit units = 2;
|
||||||
|
// Report version
|
||||||
|
uint32 version = 3;
|
||||||
|
// Progress categories
|
||||||
|
repeated ReportCategory categories = 4;
|
||||||
|
}
|
||||||
|
|
||||||
// Progress info for a report or unit
|
// Progress info for a report or unit
|
||||||
message Measures {
|
message Measures {
|
||||||
// Overall match percent, including partially matched functions and data
|
// Overall match percent, including partially matched functions and data
|
||||||
@@ -38,18 +50,6 @@ message Measures {
|
|||||||
uint32 complete_units = 16;
|
uint32 complete_units = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Project progress report
|
|
||||||
message Report {
|
|
||||||
// Overall progress info
|
|
||||||
Measures measures = 1;
|
|
||||||
// Units within this report
|
|
||||||
repeated ReportUnit units = 2;
|
|
||||||
// Report version
|
|
||||||
uint32 version = 3;
|
|
||||||
// Progress categories
|
|
||||||
repeated ReportCategory categories = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ReportCategory {
|
message ReportCategory {
|
||||||
// The ID of the category
|
// The ID of the category
|
||||||
string id = 1;
|
string id = 1;
|
||||||
@@ -99,6 +99,8 @@ message ReportItem {
|
|||||||
float fuzzy_match_percent = 3;
|
float fuzzy_match_percent = 3;
|
||||||
// Extra metadata for this item
|
// Extra metadata for this item
|
||||||
optional ReportItemMetadata metadata = 4;
|
optional ReportItemMetadata metadata = 4;
|
||||||
|
// Address of the item (section-relative offset)
|
||||||
|
optional uint64 address = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extra metadata for an item
|
// Extra metadata for an item
|
||||||
@@ -108,57 +110,3 @@ message ReportItemMetadata {
|
|||||||
// The virtual address of the function or section
|
// The virtual address of the function or section
|
||||||
optional uint64 virtual_address = 2;
|
optional uint64 virtual_address = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A pair of reports to compare and generate changes
|
|
||||||
message ChangesInput {
|
|
||||||
// The previous report
|
|
||||||
Report from = 1;
|
|
||||||
// The current report
|
|
||||||
Report to = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Changes between two reports
|
|
||||||
message Changes {
|
|
||||||
// The progress info for the previous report
|
|
||||||
Measures from = 1;
|
|
||||||
// The progress info for the current report
|
|
||||||
Measures to = 2;
|
|
||||||
// Units that changed
|
|
||||||
repeated ChangeUnit units = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A changed unit
|
|
||||||
message ChangeUnit {
|
|
||||||
// The name of the unit
|
|
||||||
string name = 1;
|
|
||||||
// The previous progress info (omitted if new)
|
|
||||||
optional Measures from = 2;
|
|
||||||
// The current progress info (omitted if removed)
|
|
||||||
optional Measures to = 3;
|
|
||||||
// Sections that changed
|
|
||||||
repeated ChangeItem sections = 4;
|
|
||||||
// Functions that changed
|
|
||||||
repeated ChangeItem functions = 5;
|
|
||||||
// Extra metadata for this unit
|
|
||||||
optional ReportUnitMetadata metadata = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A changed section or function
|
|
||||||
message ChangeItem {
|
|
||||||
// The name of the item
|
|
||||||
string name = 1;
|
|
||||||
// The previous progress info (omitted if new)
|
|
||||||
optional ChangeItemInfo from = 2;
|
|
||||||
// The current progress info (omitted if removed)
|
|
||||||
optional ChangeItemInfo to = 3;
|
|
||||||
// Extra metadata for this item
|
|
||||||
optional ReportItemMetadata metadata = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Progress info for a section or function
|
|
||||||
message ChangeItemInfo {
|
|
||||||
// The overall match percent for this item
|
|
||||||
float fuzzy_match_percent = 1;
|
|
||||||
// The size of the item in bytes
|
|
||||||
uint64 size = 2;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,40 +1,39 @@
|
|||||||
use std::{
|
use alloc::{
|
||||||
borrow::Cow,
|
collections::BTreeMap,
|
||||||
collections::{BTreeMap, HashMap},
|
format,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{Result, bail};
|
||||||
use arm_attr::{enums::CpuArch, tag::Tag, BuildAttrs};
|
use arm_attr::{BuildAttrs, enums::CpuArch, tag::Tag};
|
||||||
use object::{
|
use object::{Endian as _, Object as _, ObjectSection as _, ObjectSymbol as _, elf};
|
||||||
elf::{self, SHT_ARM_ATTRIBUTES},
|
use unarm::{args, arm, thumb};
|
||||||
Endian, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, SectionIndex,
|
|
||||||
SectionKind, Symbol, SymbolKind,
|
|
||||||
};
|
|
||||||
use unarm::{
|
|
||||||
args::{Argument, OffsetImm, OffsetReg, Register},
|
|
||||||
parse::{ArmVersion, ParseMode, Parser},
|
|
||||||
DisplayOptions, ParseFlags, ParsedIns, RegNames,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
arch::{ObjArch, ProcessCodeResult},
|
arch::{Arch, RelocationOverride, RelocationOverrideTarget},
|
||||||
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig},
|
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig, display::InstructionPart},
|
||||||
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
|
obj::{
|
||||||
|
InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation,
|
||||||
|
Section, SectionKind, Symbol, SymbolFlag, SymbolFlagSet, SymbolKind,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ObjArchArm {
|
#[derive(Debug)]
|
||||||
|
pub struct ArchArm {
|
||||||
/// Maps section index, to list of disasm modes (arm, thumb or data) sorted by address
|
/// Maps section index, to list of disasm modes (arm, thumb or data) sorted by address
|
||||||
disasm_modes: HashMap<SectionIndex, Vec<DisasmMode>>,
|
disasm_modes: BTreeMap<usize, Vec<DisasmMode>>,
|
||||||
detected_version: Option<ArmVersion>,
|
detected_version: Option<unarm::ArmVersion>,
|
||||||
endianness: object::Endianness,
|
endianness: object::Endianness,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjArchArm {
|
impl ArchArm {
|
||||||
pub fn new(file: &File) -> Result<Self> {
|
pub fn new(file: &object::File) -> Result<Self> {
|
||||||
let endianness = file.endianness();
|
let endianness = file.endianness();
|
||||||
match file {
|
match file {
|
||||||
File::Elf32(_) => {
|
object::File::Elf32(_) => {
|
||||||
let disasm_modes = Self::elf_get_mapping_symbols(file);
|
// The disasm_modes mapping is populated later in the post_init step so that we have access to merged sections.
|
||||||
|
let disasm_modes = BTreeMap::new();
|
||||||
let detected_version = Self::elf_detect_arm_version(file)?;
|
let detected_version = Self::elf_detect_arm_version(file)?;
|
||||||
Ok(Self { disasm_modes, detected_version, endianness })
|
Ok(Self { disasm_modes, detected_version, endianness })
|
||||||
}
|
}
|
||||||
@@ -42,10 +41,11 @@ impl ObjArchArm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn elf_detect_arm_version(file: &File) -> Result<Option<ArmVersion>> {
|
fn elf_detect_arm_version(file: &object::File) -> Result<Option<unarm::ArmVersion>> {
|
||||||
// Check ARM attributes
|
// Check ARM attributes
|
||||||
if let Some(arm_attrs) = file.sections().find(|s| {
|
if let Some(arm_attrs) = file.sections().find(|s| {
|
||||||
s.kind() == SectionKind::Elf(SHT_ARM_ATTRIBUTES) && s.name() == Ok(".ARM.attributes")
|
s.kind() == object::SectionKind::Elf(elf::SHT_ARM_ATTRIBUTES)
|
||||||
|
&& s.name() == Ok(".ARM.attributes")
|
||||||
}) {
|
}) {
|
||||||
let attr_data = arm_attrs.uncompressed_data()?;
|
let attr_data = arm_attrs.uncompressed_data()?;
|
||||||
let build_attrs = BuildAttrs::new(&attr_data, match file.endianness() {
|
let build_attrs = BuildAttrs::new(&attr_data, match file.endianness() {
|
||||||
@@ -59,16 +59,12 @@ impl ObjArchArm {
|
|||||||
}
|
}
|
||||||
// Only checking first CpuArch tag. Others may exist, but that's very unlikely.
|
// Only checking first CpuArch tag. Others may exist, but that's very unlikely.
|
||||||
let cpu_arch = subsection.into_public_tag_iter()?.find_map(|(_, tag)| {
|
let cpu_arch = subsection.into_public_tag_iter()?.find_map(|(_, tag)| {
|
||||||
if let Tag::CpuArch(cpu_arch) = tag {
|
if let Tag::CpuArch(cpu_arch) = tag { Some(cpu_arch) } else { None }
|
||||||
Some(cpu_arch)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
match cpu_arch {
|
match cpu_arch {
|
||||||
Some(CpuArch::V4T) => return Ok(Some(ArmVersion::V4T)),
|
Some(CpuArch::V4T) => return Ok(Some(unarm::ArmVersion::V4T)),
|
||||||
Some(CpuArch::V5TE) => return Ok(Some(ArmVersion::V5Te)),
|
Some(CpuArch::V5TE) => return Ok(Some(unarm::ArmVersion::V5Te)),
|
||||||
Some(CpuArch::V6K) => return Ok(Some(ArmVersion::V6K)),
|
Some(CpuArch::V6K) => return Ok(Some(unarm::ArmVersion::V6K)),
|
||||||
Some(arch) => bail!("ARM arch {} not supported", arch),
|
Some(arch) => bail!("ARM arch {} not supported", arch),
|
||||||
None => {}
|
None => {}
|
||||||
};
|
};
|
||||||
@@ -78,193 +74,339 @@ impl ObjArchArm {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn elf_get_mapping_symbols(file: &File) -> HashMap<SectionIndex, Vec<DisasmMode>> {
|
fn get_mapping_symbols(
|
||||||
file.sections()
|
sections: &[Section],
|
||||||
.filter(|s| s.kind() == SectionKind::Text)
|
symbols: &[Symbol],
|
||||||
.map(|s| {
|
) -> BTreeMap<usize, Vec<DisasmMode>> {
|
||||||
let index = s.index();
|
sections
|
||||||
let mut mapping_symbols: Vec<_> = file
|
.iter()
|
||||||
.symbols()
|
.enumerate()
|
||||||
.filter(|s| s.section_index().map(|i| i == index).unwrap_or(false))
|
.filter(|(_, section)| section.kind == SectionKind::Code)
|
||||||
.filter_map(|s| DisasmMode::from_symbol(&s))
|
.map(|(index, _)| {
|
||||||
|
let mut mapping_symbols: Vec<_> = symbols
|
||||||
|
.iter()
|
||||||
|
.filter(|s| s.section.map(|i| i == index).unwrap_or(false))
|
||||||
|
.filter_map(DisasmMode::from_symbol)
|
||||||
.collect();
|
.collect();
|
||||||
mapping_symbols.sort_unstable_by_key(|x| x.address);
|
mapping_symbols.sort_unstable_by_key(|x| x.address);
|
||||||
(s.index(), mapping_symbols)
|
(index, mapping_symbols)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjArch for ObjArchArm {
|
fn parse_flags(&self, diff_config: &DiffObjConfig) -> unarm::ParseFlags {
|
||||||
fn symbol_address(&self, symbol: &Symbol) -> u64 {
|
unarm::ParseFlags {
|
||||||
let address = symbol.address();
|
ual: diff_config.arm_unified_syntax,
|
||||||
if symbol.kind() == SymbolKind::Text {
|
version: match diff_config.arm_arch_version {
|
||||||
address & !1
|
ArmArchVersion::Auto => self.detected_version.unwrap_or(unarm::ArmVersion::V5Te),
|
||||||
} else {
|
ArmArchVersion::V4t => unarm::ArmVersion::V4T,
|
||||||
address
|
ArmArchVersion::V5te => unarm::ArmVersion::V5Te,
|
||||||
|
ArmArchVersion::V6k => unarm::ArmVersion::V6K,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_code(
|
fn display_options(&self, diff_config: &DiffObjConfig) -> unarm::DisplayOptions {
|
||||||
&self,
|
unarm::DisplayOptions {
|
||||||
address: u64,
|
reg_names: unarm::RegNames {
|
||||||
code: &[u8],
|
av_registers: diff_config.arm_av_registers,
|
||||||
section_index: usize,
|
r9_use: match diff_config.arm_r9_usage {
|
||||||
relocations: &[ObjReloc],
|
|
||||||
line_info: &BTreeMap<u64, u32>,
|
|
||||||
config: &DiffObjConfig,
|
|
||||||
) -> Result<ProcessCodeResult> {
|
|
||||||
let start_addr = address as u32;
|
|
||||||
let end_addr = start_addr + code.len() as u32;
|
|
||||||
|
|
||||||
// Mapping symbols decide what kind of data comes after it. $a for ARM code, $t for Thumb code and $d for data.
|
|
||||||
let fallback_mappings = [DisasmMode { address: start_addr, mapping: ParseMode::Arm }];
|
|
||||||
let mapping_symbols = self
|
|
||||||
.disasm_modes
|
|
||||||
.get(&SectionIndex(section_index))
|
|
||||||
.map(|x| x.as_slice())
|
|
||||||
.unwrap_or(&fallback_mappings);
|
|
||||||
let first_mapping_idx = mapping_symbols
|
|
||||||
.binary_search_by_key(&start_addr, |x| x.address)
|
|
||||||
.unwrap_or_else(|idx| idx - 1);
|
|
||||||
let first_mapping = mapping_symbols[first_mapping_idx].mapping;
|
|
||||||
|
|
||||||
let mut mappings_iter =
|
|
||||||
mapping_symbols.iter().skip(first_mapping_idx + 1).take_while(|x| x.address < end_addr);
|
|
||||||
let mut next_mapping = mappings_iter.next();
|
|
||||||
|
|
||||||
let ins_count = code.len() / first_mapping.instruction_size(start_addr);
|
|
||||||
let mut ops = Vec::<u16>::with_capacity(ins_count);
|
|
||||||
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
|
|
||||||
|
|
||||||
let version = match config.arm_arch_version {
|
|
||||||
ArmArchVersion::Auto => self.detected_version.unwrap_or(ArmVersion::V5Te),
|
|
||||||
ArmArchVersion::V4T => ArmVersion::V4T,
|
|
||||||
ArmArchVersion::V5TE => ArmVersion::V5Te,
|
|
||||||
ArmArchVersion::V6K => ArmVersion::V6K,
|
|
||||||
};
|
|
||||||
let endian = match self.endianness {
|
|
||||||
object::Endianness::Little => unarm::Endian::Little,
|
|
||||||
object::Endianness::Big => unarm::Endian::Big,
|
|
||||||
};
|
|
||||||
|
|
||||||
let parse_flags = ParseFlags { ual: config.arm_unified_syntax, version };
|
|
||||||
|
|
||||||
let mut parser = Parser::new(first_mapping, start_addr, endian, parse_flags, code);
|
|
||||||
|
|
||||||
let display_options = DisplayOptions {
|
|
||||||
reg_names: RegNames {
|
|
||||||
av_registers: config.arm_av_registers,
|
|
||||||
r9_use: match config.arm_r9_usage {
|
|
||||||
ArmR9Usage::GeneralPurpose => unarm::R9Use::GeneralPurpose,
|
ArmR9Usage::GeneralPurpose => unarm::R9Use::GeneralPurpose,
|
||||||
ArmR9Usage::Sb => unarm::R9Use::Pid,
|
ArmR9Usage::Sb => unarm::R9Use::Pid,
|
||||||
ArmR9Usage::Tr => unarm::R9Use::Tls,
|
ArmR9Usage::Tr => unarm::R9Use::Tls,
|
||||||
},
|
},
|
||||||
explicit_stack_limit: config.arm_sl_usage,
|
explicit_stack_limit: diff_config.arm_sl_usage,
|
||||||
frame_pointer: config.arm_fp_usage,
|
frame_pointer: diff_config.arm_fp_usage,
|
||||||
ip: config.arm_ip_usage,
|
ip: diff_config.arm_ip_usage,
|
||||||
},
|
},
|
||||||
};
|
|
||||||
|
|
||||||
while let Some((address, ins, parsed_ins)) = parser.next() {
|
|
||||||
if let Some(next) = next_mapping {
|
|
||||||
let next_address = parser.address;
|
|
||||||
if next_address >= next.address {
|
|
||||||
// Change mapping
|
|
||||||
parser.mode = next.mapping;
|
|
||||||
next_mapping = mappings_iter.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let line = line_info.range(..=address as u64).last().map(|(_, &b)| b);
|
|
||||||
|
|
||||||
let reloc = relocations.iter().find(|r| (r.address as u32 & !1) == address).cloned();
|
|
||||||
|
|
||||||
let mut reloc_arg = None;
|
|
||||||
if let Some(reloc) = &reloc {
|
|
||||||
match reloc.flags {
|
|
||||||
// Calls
|
|
||||||
RelocationFlags::Elf { r_type: elf::R_ARM_THM_XPC22 }
|
|
||||||
| RelocationFlags::Elf { r_type: elf::R_ARM_THM_PC22 }
|
|
||||||
| RelocationFlags::Elf { r_type: elf::R_ARM_PC24 }
|
|
||||||
| RelocationFlags::Elf { r_type: elf::R_ARM_XPC25 }
|
|
||||||
| RelocationFlags::Elf { r_type: elf::R_ARM_CALL } => {
|
|
||||||
reloc_arg = parsed_ins
|
|
||||||
.args
|
|
||||||
.iter()
|
|
||||||
.rposition(|a| matches!(a, Argument::BranchDest(_)));
|
|
||||||
}
|
|
||||||
// Data
|
|
||||||
RelocationFlags::Elf { r_type: elf::R_ARM_ABS32 } => {
|
|
||||||
reloc_arg =
|
|
||||||
parsed_ins.args.iter().rposition(|a| matches!(a, Argument::UImm(_)));
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let (args, branch_dest) = if reloc.is_some() && parser.mode == ParseMode::Data {
|
|
||||||
(vec![ObjInsArg::Reloc], None)
|
|
||||||
} else {
|
|
||||||
push_args(&parsed_ins, config, reloc_arg, address, display_options)?
|
|
||||||
};
|
|
||||||
|
|
||||||
ops.push(ins.opcode_id());
|
|
||||||
insts.push(ObjIns {
|
|
||||||
address: address as u64,
|
|
||||||
size: (parser.address - address) as u8,
|
|
||||||
op: ins.opcode_id(),
|
|
||||||
mnemonic: Cow::Borrowed(parsed_ins.mnemonic),
|
|
||||||
args,
|
|
||||||
reloc,
|
|
||||||
branch_dest,
|
|
||||||
line,
|
|
||||||
formatted: parsed_ins.display(display_options).to_string(),
|
|
||||||
orig: None,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ProcessCodeResult { ops, insts })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn implcit_addend(
|
fn parse_ins_ref(
|
||||||
|
&self,
|
||||||
|
ins_ref: InstructionRef,
|
||||||
|
code: &[u8],
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
) -> Result<(unarm::Ins, unarm::ParsedIns)> {
|
||||||
|
if ins_ref.opcode == thumb::Opcode::BlH as u16 && ins_ref.size == 4 {
|
||||||
|
// Special case: combined thumb BL instruction
|
||||||
|
let parse_flags = self.parse_flags(diff_config);
|
||||||
|
let first_ins = thumb::Ins {
|
||||||
|
code: match self.endianness {
|
||||||
|
object::Endianness::Little => u16::from_le_bytes([code[0], code[1]]),
|
||||||
|
object::Endianness::Big => u16::from_be_bytes([code[0], code[1]]),
|
||||||
|
} as u32,
|
||||||
|
op: thumb::Opcode::BlH,
|
||||||
|
};
|
||||||
|
let second_ins = thumb::Ins::new(
|
||||||
|
match self.endianness {
|
||||||
|
object::Endianness::Little => u16::from_le_bytes([code[2], code[3]]),
|
||||||
|
object::Endianness::Big => u16::from_be_bytes([code[2], code[3]]),
|
||||||
|
} as u32,
|
||||||
|
&parse_flags,
|
||||||
|
);
|
||||||
|
let first_parsed = first_ins.parse(&parse_flags);
|
||||||
|
let second_parsed = second_ins.parse(&parse_flags);
|
||||||
|
return Ok((
|
||||||
|
unarm::Ins::Thumb(first_ins),
|
||||||
|
first_parsed.combine_thumb_bl(&second_parsed),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let code = match (self.endianness, ins_ref.size) {
|
||||||
|
(object::Endianness::Little, 2) => u16::from_le_bytes([code[0], code[1]]) as u32,
|
||||||
|
(object::Endianness::Little, 4) => {
|
||||||
|
u32::from_le_bytes([code[0], code[1], code[2], code[3]])
|
||||||
|
}
|
||||||
|
(object::Endianness::Big, 2) => u16::from_be_bytes([code[0], code[1]]) as u32,
|
||||||
|
(object::Endianness::Big, 4) => {
|
||||||
|
u32::from_be_bytes([code[0], code[1], code[2], code[3]])
|
||||||
|
}
|
||||||
|
_ => bail!("Invalid instruction size {}", ins_ref.size),
|
||||||
|
};
|
||||||
|
let (ins, parsed_ins) = if ins_ref.opcode == u16::MAX {
|
||||||
|
let mut args = args::Arguments::default();
|
||||||
|
args[0] = args::Argument::UImm(code);
|
||||||
|
let mnemonic = if ins_ref.size == 4 { ".word" } else { ".hword" };
|
||||||
|
(unarm::Ins::Data, unarm::ParsedIns { mnemonic, args })
|
||||||
|
} else if ins_ref.opcode & (1 << 15) != 0 {
|
||||||
|
let ins = arm::Ins { code, op: arm::Opcode::from(ins_ref.opcode as u8) };
|
||||||
|
let parsed = ins.parse(&self.parse_flags(diff_config));
|
||||||
|
(unarm::Ins::Arm(ins), parsed)
|
||||||
|
} else {
|
||||||
|
let ins = thumb::Ins { code, op: thumb::Opcode::from(ins_ref.opcode as u8) };
|
||||||
|
let parsed = ins.parse(&self.parse_flags(diff_config));
|
||||||
|
(unarm::Ins::Thumb(ins), parsed)
|
||||||
|
};
|
||||||
|
Ok((ins, parsed_ins))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arch for ArchArm {
|
||||||
|
fn post_init(&mut self, sections: &[Section], symbols: &[Symbol]) {
|
||||||
|
self.disasm_modes = Self::get_mapping_symbols(sections, symbols);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scan_instructions_internal(
|
||||||
&self,
|
&self,
|
||||||
_file: &File<'_>,
|
|
||||||
section: &ObjSection,
|
|
||||||
address: u64,
|
address: u64,
|
||||||
reloc: &Relocation,
|
code: &[u8],
|
||||||
) -> Result<i64> {
|
section_index: usize,
|
||||||
let address = address as usize;
|
_relocations: &[Relocation],
|
||||||
Ok(match reloc.flags() {
|
diff_config: &DiffObjConfig,
|
||||||
// ARM calls
|
) -> Result<Vec<InstructionRef>> {
|
||||||
RelocationFlags::Elf { r_type: elf::R_ARM_PC24 }
|
let start_addr = address as u32;
|
||||||
| RelocationFlags::Elf { r_type: elf::R_ARM_XPC25 }
|
let end_addr = start_addr + code.len() as u32;
|
||||||
| RelocationFlags::Elf { r_type: elf::R_ARM_CALL } => {
|
|
||||||
let data = section.data[address..address + 4].try_into()?;
|
// Mapping symbols decide what kind of data comes after it. $a for ARM code, $t for Thumb code and $d for data.
|
||||||
let addend = self.endianness.read_i32_bytes(data);
|
let fallback_mappings =
|
||||||
let imm24 = addend & 0xffffff;
|
[DisasmMode { address: start_addr, mapping: unarm::ParseMode::Arm }];
|
||||||
(imm24 << 2) << 8 >> 8
|
let mapping_symbols = self
|
||||||
|
.disasm_modes
|
||||||
|
.get(§ion_index)
|
||||||
|
.map(|x| x.as_slice())
|
||||||
|
.unwrap_or(&fallback_mappings);
|
||||||
|
let first_mapping_idx = mapping_symbols
|
||||||
|
.binary_search_by_key(&start_addr, |x| x.address)
|
||||||
|
.unwrap_or_else(|idx| idx.saturating_sub(1));
|
||||||
|
let mut mode = mapping_symbols[first_mapping_idx].mapping;
|
||||||
|
|
||||||
|
let mut mappings_iter = mapping_symbols
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.skip(first_mapping_idx + 1)
|
||||||
|
.take_while(|x| x.address < end_addr);
|
||||||
|
let mut next_mapping = mappings_iter.next();
|
||||||
|
|
||||||
|
let ins_count = code.len() / mode.instruction_size(start_addr);
|
||||||
|
let mut ops = Vec::<InstructionRef>::with_capacity(ins_count);
|
||||||
|
|
||||||
|
let parse_flags = self.parse_flags(diff_config);
|
||||||
|
|
||||||
|
let mut address = start_addr;
|
||||||
|
while address < end_addr {
|
||||||
|
while let Some(next) = next_mapping.filter(|x| address >= x.address) {
|
||||||
|
// Change mapping
|
||||||
|
mode = next.mapping;
|
||||||
|
next_mapping = mappings_iter.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Thumb calls
|
let mut ins_size = mode.instruction_size(address);
|
||||||
RelocationFlags::Elf { r_type: elf::R_ARM_THM_PC22 }
|
let data = &code[(address - start_addr) as usize..];
|
||||||
| RelocationFlags::Elf { r_type: elf::R_ARM_THM_XPC22 } => {
|
if data.len() < ins_size {
|
||||||
let data = section.data[address..address + 2].try_into()?;
|
// Push the remainder as data
|
||||||
let high = self.endianness.read_i16_bytes(data) as i32;
|
ops.push(InstructionRef {
|
||||||
let data = section.data[address + 2..address + 4].try_into()?;
|
address: address as u64,
|
||||||
let low = self.endianness.read_i16_bytes(data) as i32;
|
size: data.len() as u8,
|
||||||
|
opcode: u16::MAX,
|
||||||
let imm22 = ((high & 0x7ff) << 11) | (low & 0x7ff);
|
branch_dest: None,
|
||||||
(imm22 << 1) << 9 >> 9
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
let code = match (self.endianness, ins_size) {
|
||||||
|
(object::Endianness::Little, 2) => u16::from_le_bytes([data[0], data[1]]) as u32,
|
||||||
|
(object::Endianness::Little, 4) => {
|
||||||
|
u32::from_le_bytes([data[0], data[1], data[2], data[3]])
|
||||||
|
}
|
||||||
|
(object::Endianness::Big, 2) => u16::from_be_bytes([data[0], data[1]]) as u32,
|
||||||
|
(object::Endianness::Big, 4) => {
|
||||||
|
u32::from_be_bytes([data[0], data[1], data[2], data[3]])
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Invalid instruction size
|
||||||
|
ops.push(InstructionRef {
|
||||||
|
address: address as u64,
|
||||||
|
size: ins_size as u8,
|
||||||
|
opcode: u16::MAX,
|
||||||
|
branch_dest: None,
|
||||||
|
});
|
||||||
|
address += ins_size as u32;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Data
|
let (opcode, branch_dest) = match mode {
|
||||||
RelocationFlags::Elf { r_type: elf::R_ARM_ABS32 } => {
|
unarm::ParseMode::Arm => {
|
||||||
let data = section.data[address..address + 4].try_into()?;
|
let ins = arm::Ins::new(code, &parse_flags);
|
||||||
self.endianness.read_i32_bytes(data)
|
let opcode = ins.op as u16 | (1 << 15);
|
||||||
|
let branch_dest = match ins.op {
|
||||||
|
arm::Opcode::B | arm::Opcode::Bl => {
|
||||||
|
address.checked_add_signed(ins.field_branch_offset())
|
||||||
|
}
|
||||||
|
arm::Opcode::BlxI => address.checked_add_signed(ins.field_blx_offset()),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
(opcode, branch_dest)
|
||||||
|
}
|
||||||
|
unarm::ParseMode::Thumb => {
|
||||||
|
let ins = thumb::Ins::new(code, &parse_flags);
|
||||||
|
let opcode = ins.op as u16;
|
||||||
|
let branch_dest = match ins.op {
|
||||||
|
thumb::Opcode::B | thumb::Opcode::Bl => {
|
||||||
|
address.checked_add_signed(ins.field_branch_offset_8())
|
||||||
|
}
|
||||||
|
thumb::Opcode::BlH if data.len() >= 4 => {
|
||||||
|
// Combine BL instructions
|
||||||
|
let second_ins = thumb::Ins::new(
|
||||||
|
match self.endianness {
|
||||||
|
object::Endianness::Little => {
|
||||||
|
u16::from_le_bytes([data[2], data[3]]) as u32
|
||||||
|
}
|
||||||
|
object::Endianness::Big => {
|
||||||
|
u16::from_be_bytes([data[2], data[3]]) as u32
|
||||||
|
}
|
||||||
|
},
|
||||||
|
&parse_flags,
|
||||||
|
);
|
||||||
|
if let Some(low) = match second_ins.op {
|
||||||
|
thumb::Opcode::Bl => Some(second_ins.field_low_branch_offset_11()),
|
||||||
|
thumb::Opcode::BlxI => Some(second_ins.field_low_blx_offset_11()),
|
||||||
|
_ => None,
|
||||||
|
} {
|
||||||
|
ins_size = 4;
|
||||||
|
address.checked_add_signed(
|
||||||
|
(ins.field_high_branch_offset_11() + (low as i32)) << 9 >> 9,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thumb::Opcode::BLong => {
|
||||||
|
address.checked_add_signed(ins.field_branch_offset_11())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
(opcode, branch_dest)
|
||||||
|
}
|
||||||
|
unarm::ParseMode::Data => (u16::MAX, None),
|
||||||
|
};
|
||||||
|
|
||||||
|
ops.push(InstructionRef {
|
||||||
|
address: address as u64,
|
||||||
|
size: ins_size as u8,
|
||||||
|
opcode,
|
||||||
|
branch_dest: branch_dest.map(|x| x as u64),
|
||||||
|
});
|
||||||
|
address += ins_size as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_instruction(
|
||||||
|
&self,
|
||||||
|
resolved: ResolvedInstructionRef,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let (ins, parsed_ins) = self.parse_ins_ref(resolved.ins_ref, resolved.code, diff_config)?;
|
||||||
|
cb(InstructionPart::opcode(parsed_ins.mnemonic, resolved.ins_ref.opcode))?;
|
||||||
|
if ins == unarm::Ins::Data && resolved.relocation.is_some() {
|
||||||
|
cb(InstructionPart::reloc())?;
|
||||||
|
} else {
|
||||||
|
push_args(
|
||||||
|
ins,
|
||||||
|
&parsed_ins,
|
||||||
|
resolved.relocation,
|
||||||
|
resolved.ins_ref.address as u32,
|
||||||
|
self.display_options(diff_config),
|
||||||
|
cb,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn relocation_override(
|
||||||
|
&self,
|
||||||
|
_file: &object::File<'_>,
|
||||||
|
section: &object::Section,
|
||||||
|
address: u64,
|
||||||
|
relocation: &object::Relocation,
|
||||||
|
) -> Result<Option<RelocationOverride>> {
|
||||||
|
match relocation.flags() {
|
||||||
|
// Handle ELF implicit relocations
|
||||||
|
object::RelocationFlags::Elf { r_type } => {
|
||||||
|
if relocation.has_implicit_addend() {
|
||||||
|
let section_data = section.data()?;
|
||||||
|
let address = address as usize;
|
||||||
|
let addend = match r_type {
|
||||||
|
// ARM calls
|
||||||
|
elf::R_ARM_PC24 | elf::R_ARM_XPC25 | elf::R_ARM_CALL => {
|
||||||
|
let data = section_data[address..address + 4].try_into()?;
|
||||||
|
let addend = self.endianness.read_i32_bytes(data);
|
||||||
|
let imm24 = addend & 0xffffff;
|
||||||
|
(imm24 << 2) << 8 >> 8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thumb calls
|
||||||
|
elf::R_ARM_THM_PC22 | elf::R_ARM_THM_XPC22 => {
|
||||||
|
let data = section_data[address..address + 2].try_into()?;
|
||||||
|
let high = self.endianness.read_i16_bytes(data) as i32;
|
||||||
|
let data = section_data[address + 2..address + 4].try_into()?;
|
||||||
|
let low = self.endianness.read_i16_bytes(data) as i32;
|
||||||
|
|
||||||
|
let imm22 = ((high & 0x7ff) << 11) | (low & 0x7ff);
|
||||||
|
(imm22 << 1) << 9 >> 9
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data
|
||||||
|
elf::R_ARM_ABS32 => {
|
||||||
|
let data = section_data[address..address + 4].try_into()?;
|
||||||
|
self.endianness.read_i32_bytes(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
flags => bail!("Unsupported ARM implicit relocation {flags:?}"),
|
||||||
|
};
|
||||||
|
Ok(Some(RelocationOverride {
|
||||||
|
target: RelocationOverrideTarget::Keep,
|
||||||
|
addend: addend as i64,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
_ => Ok(None),
|
||||||
flags => bail!("Unsupported ARM implicit relocation {flags:?}"),
|
}
|
||||||
} as i64)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn demangle(&self, name: &str) -> Option<String> {
|
fn demangle(&self, name: &str) -> Option<String> {
|
||||||
@@ -273,51 +415,120 @@ impl ObjArch for ObjArchArm {
|
|||||||
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
|
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
|
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
||||||
Cow::Owned(format!("<{flags:?}>"))
|
match flags {
|
||||||
|
RelocationFlags::Elf(r_type) => match r_type {
|
||||||
|
elf::R_ARM_NONE => Some("R_ARM_NONE"),
|
||||||
|
elf::R_ARM_ABS32 => Some("R_ARM_ABS32"),
|
||||||
|
elf::R_ARM_REL32 => Some("R_ARM_REL32"),
|
||||||
|
elf::R_ARM_ABS16 => Some("R_ARM_ABS16"),
|
||||||
|
elf::R_ARM_ABS8 => Some("R_ARM_ABS8"),
|
||||||
|
elf::R_ARM_THM_PC22 => Some("R_ARM_THM_PC22"),
|
||||||
|
elf::R_ARM_THM_XPC22 => Some("R_ARM_THM_XPC22"),
|
||||||
|
elf::R_ARM_PC24 => Some("R_ARM_PC24"),
|
||||||
|
elf::R_ARM_XPC25 => Some("R_ARM_XPC25"),
|
||||||
|
elf::R_ARM_CALL => Some("R_ARM_CALL"),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
|
||||||
|
match flags {
|
||||||
|
RelocationFlags::Elf(r_type) => match r_type {
|
||||||
|
elf::R_ARM_NONE => 0,
|
||||||
|
elf::R_ARM_ABS32 => 4,
|
||||||
|
elf::R_ARM_REL32 => 4,
|
||||||
|
elf::R_ARM_ABS16 => 2,
|
||||||
|
elf::R_ARM_ABS8 => 1,
|
||||||
|
elf::R_ARM_THM_PC22 => 4,
|
||||||
|
elf::R_ARM_THM_XPC22 => 4,
|
||||||
|
elf::R_ARM_PC24 => 4,
|
||||||
|
elf::R_ARM_XPC25 => 4,
|
||||||
|
elf::R_ARM_CALL => 4,
|
||||||
|
_ => 1,
|
||||||
|
},
|
||||||
|
_ => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn symbol_address(&self, address: u64, kind: SymbolKind) -> u64 {
|
||||||
|
if kind == SymbolKind::Function { address & !1 } else { address }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_symbol_flags(&self, symbol: &object::Symbol) -> SymbolFlagSet {
|
||||||
|
let mut flags = SymbolFlagSet::default();
|
||||||
|
if DisasmMode::from_object_symbol(symbol).is_some() {
|
||||||
|
flags |= SymbolFlag::Hidden;
|
||||||
|
}
|
||||||
|
flags
|
||||||
|
}
|
||||||
|
|
||||||
|
fn infer_function_size(
|
||||||
|
&self,
|
||||||
|
symbol: &Symbol,
|
||||||
|
section: &Section,
|
||||||
|
mut next_address: u64,
|
||||||
|
) -> Result<u64> {
|
||||||
|
// Trim any trailing 4-byte zeroes from the end (padding)
|
||||||
|
while next_address >= symbol.address + 4
|
||||||
|
&& let Some(data) = section.data_range(next_address - 4, 4)
|
||||||
|
&& data == [0u8; 4]
|
||||||
|
{
|
||||||
|
next_address -= 4;
|
||||||
|
}
|
||||||
|
Ok(next_address.saturating_sub(symbol.address))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
struct DisasmMode {
|
struct DisasmMode {
|
||||||
address: u32,
|
address: u32,
|
||||||
mapping: ParseMode,
|
mapping: unarm::ParseMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisasmMode {
|
impl DisasmMode {
|
||||||
fn from_symbol<'a>(sym: &Symbol<'a, '_, &'a [u8]>) -> Option<Self> {
|
fn from_object_symbol<'a>(sym: &object::Symbol<'a, '_, &'a [u8]>) -> Option<Self> {
|
||||||
if let Ok(name) = sym.name() {
|
sym.name()
|
||||||
ParseMode::from_mapping_symbol(name)
|
.ok()
|
||||||
.map(|mapping| DisasmMode { address: sym.address() as u32, mapping })
|
.and_then(unarm::ParseMode::from_mapping_symbol)
|
||||||
} else {
|
.map(|mapping| DisasmMode { address: sym.address() as u32, mapping })
|
||||||
None
|
}
|
||||||
}
|
|
||||||
|
fn from_symbol(sym: &Symbol) -> Option<Self> {
|
||||||
|
unarm::ParseMode::from_mapping_symbol(&sym.name)
|
||||||
|
.map(|mapping| DisasmMode { address: sym.address as u32, mapping })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_args(
|
fn push_args(
|
||||||
parsed_ins: &ParsedIns,
|
ins: unarm::Ins,
|
||||||
config: &DiffObjConfig,
|
parsed_ins: &unarm::ParsedIns,
|
||||||
reloc_arg: Option<usize>,
|
relocation: Option<ResolvedRelocation>,
|
||||||
cur_addr: u32,
|
cur_addr: u32,
|
||||||
display_options: DisplayOptions,
|
display_options: unarm::DisplayOptions,
|
||||||
) -> Result<(Vec<ObjInsArg>, Option<u64>)> {
|
mut arg_cb: impl FnMut(InstructionPart) -> Result<()>,
|
||||||
let mut args = vec![];
|
) -> Result<()> {
|
||||||
let mut branch_dest = None;
|
let reloc_arg = find_reloc_arg(parsed_ins, relocation);
|
||||||
let mut writeback = false;
|
let mut writeback = false;
|
||||||
let mut deref = false;
|
let mut deref = false;
|
||||||
for (i, arg) in parsed_ins.args_iter().enumerate() {
|
for (i, &arg) in parsed_ins.args_iter().enumerate() {
|
||||||
// Emit punctuation before separator
|
// Emit punctuation before separator
|
||||||
if deref {
|
if deref {
|
||||||
match arg {
|
match arg {
|
||||||
Argument::OffsetImm(OffsetImm { post_indexed: true, value: _ })
|
args::Argument::OffsetImm(args::OffsetImm { post_indexed: true, value: _ })
|
||||||
| Argument::OffsetReg(OffsetReg { add: _, post_indexed: true, reg: _ })
|
| args::Argument::OffsetReg(args::OffsetReg {
|
||||||
| Argument::CoOption(_) => {
|
add: _,
|
||||||
|
post_indexed: true,
|
||||||
|
reg: _,
|
||||||
|
})
|
||||||
|
| args::Argument::CoOption(_) => {
|
||||||
deref = false;
|
deref = false;
|
||||||
args.push(ObjInsArg::PlainText("]".into()));
|
arg_cb(InstructionPart::basic("]"))?;
|
||||||
if writeback {
|
if writeback {
|
||||||
writeback = false;
|
writeback = false;
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
|
arg_cb(InstructionPart::opaque("!"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -325,117 +536,169 @@ fn push_args(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
args.push(ObjInsArg::PlainText(config.separator().into()));
|
arg_cb(InstructionPart::separator())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if reloc_arg == Some(i) {
|
if reloc_arg == Some(i) {
|
||||||
args.push(ObjInsArg::Reloc);
|
arg_cb(InstructionPart::reloc())?;
|
||||||
} else {
|
} else {
|
||||||
match arg {
|
match arg {
|
||||||
Argument::None => {}
|
args::Argument::None => {}
|
||||||
Argument::Reg(reg) => {
|
args::Argument::Reg(reg) => {
|
||||||
if reg.deref {
|
if reg.deref {
|
||||||
deref = true;
|
deref = true;
|
||||||
args.push(ObjInsArg::PlainText("[".into()));
|
arg_cb(InstructionPart::basic("["))?;
|
||||||
}
|
}
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
arg_cb(InstructionPart::opaque(
|
||||||
reg.reg.display(display_options.reg_names).to_string().into(),
|
reg.reg.display(display_options.reg_names).to_string(),
|
||||||
)));
|
))?;
|
||||||
if reg.writeback {
|
if reg.writeback {
|
||||||
if reg.deref {
|
if reg.deref {
|
||||||
writeback = true;
|
writeback = true;
|
||||||
} else {
|
} else {
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
|
arg_cb(InstructionPart::opaque("!"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Argument::RegList(reg_list) => {
|
args::Argument::RegList(reg_list) => {
|
||||||
args.push(ObjInsArg::PlainText("{".into()));
|
arg_cb(InstructionPart::basic("{"))?;
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
for i in 0..16 {
|
for i in 0..16 {
|
||||||
if (reg_list.regs & (1 << i)) != 0 {
|
if (reg_list.regs & (1 << i)) != 0 {
|
||||||
if !first {
|
if !first {
|
||||||
args.push(ObjInsArg::PlainText(config.separator().into()));
|
arg_cb(InstructionPart::separator())?;
|
||||||
}
|
}
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
arg_cb(InstructionPart::opaque(
|
||||||
Register::parse(i)
|
args::Register::parse(i)
|
||||||
.display(display_options.reg_names)
|
.display(display_options.reg_names)
|
||||||
.to_string()
|
.to_string(),
|
||||||
.into(),
|
))?;
|
||||||
)));
|
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
args.push(ObjInsArg::PlainText("}".into()));
|
arg_cb(InstructionPart::basic("}"))?;
|
||||||
if reg_list.user_mode {
|
if reg_list.user_mode {
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("^".to_string().into())));
|
arg_cb(InstructionPart::opaque("^"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Argument::UImm(value) | Argument::CoOpcode(value) | Argument::SatImm(value) => {
|
args::Argument::UImm(value)
|
||||||
args.push(ObjInsArg::PlainText("#".into()));
|
| args::Argument::CoOpcode(value)
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64)));
|
| args::Argument::SatImm(value) => {
|
||||||
|
arg_cb(InstructionPart::basic("#"))?;
|
||||||
|
arg_cb(InstructionPart::unsigned(value))?;
|
||||||
}
|
}
|
||||||
Argument::SImm(value)
|
args::Argument::SImm(value)
|
||||||
| Argument::OffsetImm(OffsetImm { post_indexed: _, value }) => {
|
| args::Argument::OffsetImm(args::OffsetImm { post_indexed: _, value }) => {
|
||||||
args.push(ObjInsArg::PlainText("#".into()));
|
arg_cb(InstructionPart::basic("#"))?;
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(*value as i64)));
|
arg_cb(InstructionPart::signed(value))?;
|
||||||
}
|
}
|
||||||
Argument::BranchDest(value) => {
|
args::Argument::BranchDest(value) => {
|
||||||
let dest = cur_addr.wrapping_add_signed(*value) as u64;
|
arg_cb(InstructionPart::branch_dest(cur_addr.wrapping_add_signed(value)))?;
|
||||||
args.push(ObjInsArg::BranchDest(dest));
|
|
||||||
branch_dest = Some(dest);
|
|
||||||
}
|
}
|
||||||
Argument::CoOption(value) => {
|
args::Argument::CoOption(value) => {
|
||||||
args.push(ObjInsArg::PlainText("{".into()));
|
arg_cb(InstructionPart::basic("{"))?;
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64)));
|
arg_cb(InstructionPart::unsigned(value))?;
|
||||||
args.push(ObjInsArg::PlainText("}".into()));
|
arg_cb(InstructionPart::basic("}"))?;
|
||||||
}
|
}
|
||||||
Argument::CoprocNum(value) => {
|
args::Argument::CoprocNum(value) => {
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(format!("p{}", value).into())));
|
arg_cb(InstructionPart::opaque(format!("p{value}")))?;
|
||||||
}
|
}
|
||||||
Argument::ShiftImm(shift) => {
|
args::Argument::ShiftImm(shift) => {
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into())));
|
arg_cb(InstructionPart::opaque(shift.op.to_string()))?;
|
||||||
args.push(ObjInsArg::PlainText(" #".into()));
|
arg_cb(InstructionPart::basic(" #"))?;
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(shift.imm as u64)));
|
arg_cb(InstructionPart::unsigned(shift.imm))?;
|
||||||
}
|
}
|
||||||
Argument::ShiftReg(shift) => {
|
args::Argument::ShiftReg(shift) => {
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into())));
|
arg_cb(InstructionPart::opaque(shift.op.to_string()))?;
|
||||||
args.push(ObjInsArg::PlainText(" ".into()));
|
arg_cb(InstructionPart::basic(" "))?;
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
arg_cb(InstructionPart::opaque(
|
||||||
shift.reg.display(display_options.reg_names).to_string().into(),
|
shift.reg.display(display_options.reg_names).to_string(),
|
||||||
)));
|
))?;
|
||||||
}
|
}
|
||||||
Argument::OffsetReg(offset) => {
|
args::Argument::OffsetReg(offset) => {
|
||||||
if !offset.add {
|
if !offset.add {
|
||||||
args.push(ObjInsArg::PlainText("-".into()));
|
arg_cb(InstructionPart::basic("-"))?;
|
||||||
}
|
}
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
arg_cb(InstructionPart::opaque(
|
||||||
offset.reg.display(display_options.reg_names).to_string().into(),
|
offset.reg.display(display_options.reg_names).to_string(),
|
||||||
)));
|
))?;
|
||||||
}
|
}
|
||||||
Argument::CpsrMode(mode) => {
|
args::Argument::CpsrMode(mode) => {
|
||||||
args.push(ObjInsArg::PlainText("#".into()));
|
arg_cb(InstructionPart::basic("#"))?;
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(mode.mode as u64)));
|
arg_cb(InstructionPart::unsigned(mode.mode))?;
|
||||||
if mode.writeback {
|
if mode.writeback {
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
|
arg_cb(InstructionPart::opaque("!"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Argument::CoReg(_)
|
args::Argument::CoReg(_)
|
||||||
| Argument::StatusReg(_)
|
| args::Argument::StatusReg(_)
|
||||||
| Argument::StatusMask(_)
|
| args::Argument::StatusMask(_)
|
||||||
| Argument::Shift(_)
|
| args::Argument::Shift(_)
|
||||||
| Argument::CpsrFlags(_)
|
| args::Argument::CpsrFlags(_)
|
||||||
| Argument::Endian(_) => args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
| args::Argument::Endian(_) => {
|
||||||
arg.display(display_options, None).to_string().into(),
|
arg_cb(InstructionPart::opaque(
|
||||||
))),
|
arg.display(display_options, None).to_string(),
|
||||||
|
))?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if deref {
|
if deref {
|
||||||
args.push(ObjInsArg::PlainText("]".into()));
|
arg_cb(InstructionPart::basic("]"))?;
|
||||||
if writeback {
|
if writeback {
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
|
arg_cb(InstructionPart::opaque("!"))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok((args, branch_dest))
|
|
||||||
|
let branch_dest = get_pc_relative_load_address(ins, cur_addr);
|
||||||
|
if let Some(branch_dest) = branch_dest {
|
||||||
|
arg_cb(InstructionPart::basic(" (->"))?;
|
||||||
|
arg_cb(InstructionPart::branch_dest(branch_dest))?;
|
||||||
|
arg_cb(InstructionPart::basic(")"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_reloc_arg(
|
||||||
|
parsed_ins: &unarm::ParsedIns,
|
||||||
|
relocation: Option<ResolvedRelocation>,
|
||||||
|
) -> Option<usize> {
|
||||||
|
if let Some(resolved) = relocation {
|
||||||
|
match resolved.relocation.flags {
|
||||||
|
// Calls
|
||||||
|
RelocationFlags::Elf(elf::R_ARM_THM_XPC22)
|
||||||
|
| RelocationFlags::Elf(elf::R_ARM_THM_PC22)
|
||||||
|
| RelocationFlags::Elf(elf::R_ARM_PC24)
|
||||||
|
| RelocationFlags::Elf(elf::R_ARM_XPC25)
|
||||||
|
| RelocationFlags::Elf(elf::R_ARM_CALL) => {
|
||||||
|
parsed_ins.args.iter().rposition(|a| matches!(a, args::Argument::BranchDest(_)))
|
||||||
|
}
|
||||||
|
// Data
|
||||||
|
RelocationFlags::Elf(elf::R_ARM_ABS32) => {
|
||||||
|
parsed_ins.args.iter().rposition(|a| matches!(a, args::Argument::UImm(_)))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_pc_relative_load_address(ins: unarm::Ins, address: u32) -> Option<u32> {
|
||||||
|
match ins {
|
||||||
|
unarm::Ins::Arm(ins)
|
||||||
|
if ins.op == arm::Opcode::Ldr
|
||||||
|
&& ins.modifier_addr_ldr_str() == arm::AddrLdrStr::Imm
|
||||||
|
&& ins.field_rn_deref().reg == args::Register::Pc =>
|
||||||
|
{
|
||||||
|
let offset = ins.field_offset_12().value;
|
||||||
|
Some(address.wrapping_add_signed(offset + 8))
|
||||||
|
}
|
||||||
|
unarm::Ins::Thumb(ins) if ins.op == thumb::Opcode::LdrPc => {
|
||||||
|
let offset = ins.field_rel_immed_8().value;
|
||||||
|
Some((address & !3).wrapping_add_signed(offset + 4))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,31 +1,32 @@
|
|||||||
use std::{borrow::Cow, collections::BTreeMap, sync::Mutex};
|
use alloc::{
|
||||||
|
collections::{BTreeMap, BTreeSet},
|
||||||
use anyhow::{anyhow, bail, Result};
|
string::{String, ToString},
|
||||||
use object::{
|
vec::Vec,
|
||||||
elf, Endian, Endianness, File, FileFlags, Object, ObjectSection, ObjectSymbol, Relocation,
|
};
|
||||||
RelocationFlags, RelocationTarget,
|
|
||||||
|
use anyhow::{Result, bail};
|
||||||
|
use object::{Endian as _, Object as _, ObjectSection as _, ObjectSymbol as _, elf};
|
||||||
|
use rabbitizer::{
|
||||||
|
IsaExtension, IsaVersion, Vram, abi::Abi, operands::ValuedOperand, registers_meta::Register,
|
||||||
};
|
};
|
||||||
use rabbitizer::{config, Abi, InstrCategory, Instruction, OperandType};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
arch::{ObjArch, ProcessCodeResult},
|
arch::{Arch, RelocationOverride, RelocationOverrideTarget},
|
||||||
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory},
|
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory, display::InstructionPart},
|
||||||
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
|
obj::{
|
||||||
|
InstructionArg, InstructionArgValue, InstructionRef, Relocation, RelocationFlags,
|
||||||
|
ResolvedInstructionRef, ResolvedRelocation, Section, Symbol, SymbolFlag, SymbolFlagSet,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static RABBITIZER_MUTEX: Mutex<()> = Mutex::new(());
|
#[derive(Debug)]
|
||||||
|
pub struct ArchMips {
|
||||||
fn configure_rabbitizer(abi: Abi) {
|
pub endianness: object::Endianness,
|
||||||
unsafe {
|
|
||||||
config::RabbitizerConfig_Cfg.reg_names.fpr_abi_names = abi;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ObjArchMips {
|
|
||||||
pub endianness: Endianness,
|
|
||||||
pub abi: Abi,
|
pub abi: Abi,
|
||||||
pub instr_category: InstrCategory,
|
pub isa_extension: Option<IsaExtension>,
|
||||||
pub ri_gp_value: i32,
|
pub ri_gp_value: i32,
|
||||||
|
pub paired_relocations: Vec<BTreeMap<u64, i64>>,
|
||||||
|
pub ignored_symbols: BTreeSet<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const EF_MIPS_ABI: u32 = 0x0000F000;
|
const EF_MIPS_ABI: u32 = 0x0000F000;
|
||||||
@@ -36,13 +37,13 @@ const EF_MIPS_MACH_5900: u32 = 0x00920000;
|
|||||||
|
|
||||||
const R_MIPS15_S3: u32 = 119;
|
const R_MIPS15_S3: u32 = 119;
|
||||||
|
|
||||||
impl ObjArchMips {
|
impl ArchMips {
|
||||||
pub fn new(object: &File) -> Result<Self> {
|
pub fn new(object: &object::File) -> Result<Self> {
|
||||||
let mut abi = Abi::NUMERIC;
|
let mut abi = Abi::O32;
|
||||||
let mut instr_category = InstrCategory::CPU;
|
let mut isa_extension = None;
|
||||||
match object.flags() {
|
match object.flags() {
|
||||||
FileFlags::None => {}
|
object::FileFlags::None => {}
|
||||||
FileFlags::Elf { e_flags, .. } => {
|
object::FileFlags::Elf { e_flags, .. } => {
|
||||||
abi = match e_flags & EF_MIPS_ABI {
|
abi = match e_flags & EF_MIPS_ABI {
|
||||||
elf::EF_MIPS_ABI_O32 | elf::EF_MIPS_ABI_O64 => Abi::O32,
|
elf::EF_MIPS_ABI_O32 | elf::EF_MIPS_ABI_O64 => Abi::O32,
|
||||||
elf::EF_MIPS_ABI_EABI32 | elf::EF_MIPS_ABI_EABI64 => Abi::N32,
|
elf::EF_MIPS_ABI_EABI32 | elf::EF_MIPS_ABI_EABI64 => Abi::N32,
|
||||||
@@ -50,14 +51,14 @@ impl ObjArchMips {
|
|||||||
if e_flags & elf::EF_MIPS_ABI2 != 0 {
|
if e_flags & elf::EF_MIPS_ABI2 != 0 {
|
||||||
Abi::N32
|
Abi::N32
|
||||||
} else {
|
} else {
|
||||||
Abi::NUMERIC
|
Abi::O32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
instr_category = match e_flags & EF_MIPS_MACH {
|
isa_extension = match e_flags & EF_MIPS_MACH {
|
||||||
EF_MIPS_MACH_ALLEGREX => InstrCategory::R4000ALLEGREX,
|
EF_MIPS_MACH_ALLEGREX => Some(IsaExtension::R4000ALLEGREX),
|
||||||
EF_MIPS_MACH_5900 => InstrCategory::R5900,
|
EF_MIPS_MACH_5900 => Some(IsaExtension::R5900EE),
|
||||||
_ => InstrCategory::CPU,
|
_ => None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
_ => bail!("Unsupported MIPS file flags"),
|
_ => bail!("Unsupported MIPS file flags"),
|
||||||
@@ -65,248 +66,424 @@ impl ObjArchMips {
|
|||||||
|
|
||||||
// Parse the ri_gp_value stored in .reginfo to be able to correctly
|
// Parse the ri_gp_value stored in .reginfo to be able to correctly
|
||||||
// calculate R_MIPS_GPREL16 relocations later. The value is stored
|
// calculate R_MIPS_GPREL16 relocations later. The value is stored
|
||||||
// 0x14 bytes into .reginfo (on 32 bit platforms)
|
// 0x14 bytes into .reginfo (on 32-bit platforms)
|
||||||
|
let endianness = object.endianness();
|
||||||
let ri_gp_value = object
|
let ri_gp_value = object
|
||||||
.section_by_name(".reginfo")
|
.section_by_name(".reginfo")
|
||||||
.and_then(|section| section.data().ok())
|
.and_then(|section| section.data().ok())
|
||||||
.and_then(|data| data.get(0x14..0x18))
|
.and_then(|data| data.get(0x14..0x18))
|
||||||
.and_then(|s| s.try_into().ok())
|
.and_then(|s| s.try_into().ok())
|
||||||
.map(|bytes| object.endianness().read_i32_bytes(bytes))
|
.map(|bytes| endianness.read_i32_bytes(bytes))
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
Ok(Self { endianness: object.endianness(), abi, instr_category, ri_gp_value })
|
// Parse all relocations to pair R_MIPS_HI16 and R_MIPS_LO16. Since the instructions only
|
||||||
}
|
// have 16-bit immediate fields, the 32-bit addend is split across the two relocations.
|
||||||
}
|
// R_MIPS_LO16 relocations without an immediately preceding R_MIPS_HI16 use the last seen
|
||||||
|
// R_MIPS_HI16 addend.
|
||||||
|
// See https://refspecs.linuxfoundation.org/elf/mipsabi.pdf pages 4-17 and 4-18
|
||||||
|
let mut paired_relocations = Vec::with_capacity(object.sections().count() + 1);
|
||||||
|
for obj_section in object.sections() {
|
||||||
|
let data = obj_section.data()?;
|
||||||
|
let mut last_hi = None;
|
||||||
|
let mut last_hi_addend = 0;
|
||||||
|
let mut addends = BTreeMap::new();
|
||||||
|
for (addr, reloc) in obj_section.relocations() {
|
||||||
|
if !reloc.has_implicit_addend() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match reloc.flags() {
|
||||||
|
object::RelocationFlags::Elf { r_type: elf::R_MIPS_HI16 } => {
|
||||||
|
let code = data[addr as usize..addr as usize + 4].try_into()?;
|
||||||
|
let addend = ((endianness.read_u32_bytes(code) & 0x0000FFFF) << 16) as i32;
|
||||||
|
last_hi = Some(addr);
|
||||||
|
last_hi_addend = addend;
|
||||||
|
}
|
||||||
|
object::RelocationFlags::Elf { r_type: elf::R_MIPS_LO16 } => {
|
||||||
|
let code = data[addr as usize..addr as usize + 4].try_into()?;
|
||||||
|
let addend = (endianness.read_u32_bytes(code) & 0x0000FFFF) as i16 as i32;
|
||||||
|
let full_addend = (last_hi_addend + addend) as i64;
|
||||||
|
if let Some(hi_addr) = last_hi.take() {
|
||||||
|
addends.insert(hi_addr, full_addend);
|
||||||
|
}
|
||||||
|
addends.insert(addr, full_addend);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
last_hi = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let section_index = obj_section.index().0;
|
||||||
|
if section_index >= paired_relocations.len() {
|
||||||
|
paired_relocations.resize_with(section_index + 1, BTreeMap::new);
|
||||||
|
}
|
||||||
|
paired_relocations[section_index] = addends;
|
||||||
|
}
|
||||||
|
|
||||||
impl ObjArch for ObjArchMips {
|
let mut ignored_symbols = BTreeSet::new();
|
||||||
fn process_code(
|
for obj_symbol in object.symbols() {
|
||||||
&self,
|
let Ok(name) = obj_symbol.name() else { continue };
|
||||||
address: u64,
|
if let Some(prefix) = name.strip_suffix(".NON_MATCHING") {
|
||||||
code: &[u8],
|
ignored_symbols.insert(obj_symbol.index().0);
|
||||||
section_index: usize,
|
if let Some(target_symbol) = object.symbol_by_name(prefix) {
|
||||||
relocations: &[ObjReloc],
|
ignored_symbols.insert(target_symbol.index().0);
|
||||||
line_info: &BTreeMap<u64, u32>,
|
}
|
||||||
config: &DiffObjConfig,
|
}
|
||||||
) -> Result<ProcessCodeResult> {
|
}
|
||||||
let _guard = RABBITIZER_MUTEX.lock().map_err(|e| anyhow!("Failed to lock mutex: {e}"))?;
|
|
||||||
configure_rabbitizer(match config.mips_abi {
|
Ok(Self {
|
||||||
|
endianness,
|
||||||
|
abi,
|
||||||
|
isa_extension,
|
||||||
|
ri_gp_value,
|
||||||
|
paired_relocations,
|
||||||
|
ignored_symbols,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_instruction_flags(&self) -> rabbitizer::InstructionFlags {
|
||||||
|
match self.isa_extension {
|
||||||
|
Some(extension) => rabbitizer::InstructionFlags::new_extension(extension),
|
||||||
|
None => rabbitizer::InstructionFlags::new(IsaVersion::MIPS_III),
|
||||||
|
}
|
||||||
|
.with_abi(self.abi)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instruction_flags(&self, diff_config: &DiffObjConfig) -> rabbitizer::InstructionFlags {
|
||||||
|
let isa_extension = match diff_config.mips_instr_category {
|
||||||
|
MipsInstrCategory::Auto => self.isa_extension,
|
||||||
|
MipsInstrCategory::Cpu => None,
|
||||||
|
MipsInstrCategory::Rsp => Some(IsaExtension::RSP),
|
||||||
|
MipsInstrCategory::R3000gte => Some(IsaExtension::R3000GTE),
|
||||||
|
MipsInstrCategory::R4000allegrex => Some(IsaExtension::R4000ALLEGREX),
|
||||||
|
MipsInstrCategory::R5900 => Some(IsaExtension::R5900EE),
|
||||||
|
};
|
||||||
|
match isa_extension {
|
||||||
|
Some(extension) => rabbitizer::InstructionFlags::new_extension(extension),
|
||||||
|
None => rabbitizer::InstructionFlags::new(IsaVersion::MIPS_III),
|
||||||
|
}
|
||||||
|
.with_abi(match diff_config.mips_abi {
|
||||||
MipsAbi::Auto => self.abi,
|
MipsAbi::Auto => self.abi,
|
||||||
MipsAbi::O32 => Abi::O32,
|
MipsAbi::O32 => Abi::O32,
|
||||||
MipsAbi::N32 => Abi::N32,
|
MipsAbi::N32 => Abi::N32,
|
||||||
MipsAbi::N64 => Abi::N64,
|
MipsAbi::N64 => Abi::N64,
|
||||||
});
|
|
||||||
let instr_category = match config.mips_instr_category {
|
|
||||||
MipsInstrCategory::Auto => self.instr_category,
|
|
||||||
MipsInstrCategory::Cpu => InstrCategory::CPU,
|
|
||||||
MipsInstrCategory::Rsp => InstrCategory::RSP,
|
|
||||||
MipsInstrCategory::R3000Gte => InstrCategory::R3000GTE,
|
|
||||||
MipsInstrCategory::R4000Allegrex => InstrCategory::R4000ALLEGREX,
|
|
||||||
MipsInstrCategory::R5900 => InstrCategory::R5900,
|
|
||||||
};
|
|
||||||
|
|
||||||
let start_address = address;
|
|
||||||
let end_address = address + code.len() as u64;
|
|
||||||
let ins_count = code.len() / 4;
|
|
||||||
let mut ops = Vec::<u16>::with_capacity(ins_count);
|
|
||||||
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
|
|
||||||
let mut cur_addr = start_address as u32;
|
|
||||||
for chunk in code.chunks_exact(4) {
|
|
||||||
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
|
|
||||||
let code = self.endianness.read_u32_bytes(chunk.try_into()?);
|
|
||||||
let instruction = Instruction::new(code, cur_addr, instr_category);
|
|
||||||
|
|
||||||
let formatted = instruction.disassemble(None, 0);
|
|
||||||
let op = instruction.unique_id as u16;
|
|
||||||
ops.push(op);
|
|
||||||
|
|
||||||
let mnemonic = instruction.opcode_name();
|
|
||||||
let is_branch = instruction.is_branch();
|
|
||||||
let branch_offset = instruction.branch_offset();
|
|
||||||
let mut branch_dest = if is_branch {
|
|
||||||
cur_addr.checked_add_signed(branch_offset).map(|a| a as u64)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let operands = instruction.get_operands_slice();
|
|
||||||
let mut args = Vec::with_capacity(operands.len() + 1);
|
|
||||||
for (idx, op) in operands.iter().enumerate() {
|
|
||||||
if idx > 0 {
|
|
||||||
args.push(ObjInsArg::PlainText(config.separator().into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
match op {
|
|
||||||
OperandType::cpu_immediate
|
|
||||||
| OperandType::cpu_label
|
|
||||||
| OperandType::cpu_branch_target_label => {
|
|
||||||
if let Some(reloc) = reloc {
|
|
||||||
// If the relocation target is within the current function, we can
|
|
||||||
// convert it into a relative branch target. Note that we check
|
|
||||||
// target_address > start_address instead of >= so that recursive
|
|
||||||
// tail calls are not considered branch targets.
|
|
||||||
let target_address =
|
|
||||||
reloc.target.address.checked_add_signed(reloc.addend);
|
|
||||||
if reloc.target.orig_section_index == Some(section_index)
|
|
||||||
&& matches!(target_address, Some(addr) if addr > start_address && addr < end_address)
|
|
||||||
{
|
|
||||||
let target_address = target_address.unwrap();
|
|
||||||
args.push(ObjInsArg::BranchDest(target_address));
|
|
||||||
branch_dest = Some(target_address);
|
|
||||||
} else {
|
|
||||||
push_reloc(&mut args, reloc)?;
|
|
||||||
branch_dest = None;
|
|
||||||
}
|
|
||||||
} else if let Some(branch_dest) = branch_dest {
|
|
||||||
args.push(ObjInsArg::BranchDest(branch_dest));
|
|
||||||
} else {
|
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
|
||||||
op.disassemble(&instruction, None).into(),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OperandType::cpu_immediate_base => {
|
|
||||||
if let Some(reloc) = reloc {
|
|
||||||
push_reloc(&mut args, reloc)?;
|
|
||||||
} else {
|
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
|
||||||
OperandType::cpu_immediate.disassemble(&instruction, None).into(),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
args.push(ObjInsArg::PlainText("(".into()));
|
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
|
||||||
OperandType::cpu_rs.disassemble(&instruction, None).into(),
|
|
||||||
)));
|
|
||||||
args.push(ObjInsArg::PlainText(")".into()));
|
|
||||||
}
|
|
||||||
// OperandType::r5900_immediate15 => match reloc {
|
|
||||||
// Some(reloc)
|
|
||||||
// if reloc.flags == RelocationFlags::Elf { r_type: R_MIPS15_S3 } =>
|
|
||||||
// {
|
|
||||||
// push_reloc(&mut args, reloc)?;
|
|
||||||
// }
|
|
||||||
// _ => {
|
|
||||||
// args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
|
||||||
// op.disassemble(&instruction, None).into(),
|
|
||||||
// )));
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
_ => {
|
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
|
||||||
op.disassemble(&instruction, None).into(),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let line = line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
|
|
||||||
insts.push(ObjIns {
|
|
||||||
address: cur_addr as u64,
|
|
||||||
size: 4,
|
|
||||||
op,
|
|
||||||
mnemonic: Cow::Borrowed(mnemonic),
|
|
||||||
args,
|
|
||||||
reloc: reloc.cloned(),
|
|
||||||
branch_dest,
|
|
||||||
line,
|
|
||||||
formatted,
|
|
||||||
orig: None,
|
|
||||||
});
|
|
||||||
cur_addr += 4;
|
|
||||||
}
|
|
||||||
Ok(ProcessCodeResult { ops, insts })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn implcit_addend(
|
|
||||||
&self,
|
|
||||||
file: &File<'_>,
|
|
||||||
section: &ObjSection,
|
|
||||||
address: u64,
|
|
||||||
reloc: &Relocation,
|
|
||||||
) -> Result<i64> {
|
|
||||||
let data = section.data[address as usize..address as usize + 4].try_into()?;
|
|
||||||
let addend = self.endianness.read_u32_bytes(data);
|
|
||||||
Ok(match reloc.flags() {
|
|
||||||
RelocationFlags::Elf { r_type: elf::R_MIPS_32 } => addend as i64,
|
|
||||||
RelocationFlags::Elf { r_type: elf::R_MIPS_26 } => ((addend & 0x03FFFFFF) << 2) as i64,
|
|
||||||
RelocationFlags::Elf { r_type: elf::R_MIPS_HI16 } => {
|
|
||||||
((addend & 0x0000FFFF) << 16) as i32 as i64
|
|
||||||
}
|
|
||||||
RelocationFlags::Elf {
|
|
||||||
r_type: elf::R_MIPS_LO16 | elf::R_MIPS_GOT16 | elf::R_MIPS_CALL16,
|
|
||||||
} => (addend & 0x0000FFFF) as i16 as i64,
|
|
||||||
RelocationFlags::Elf { r_type: elf::R_MIPS_GPREL16 | elf::R_MIPS_LITERAL } => {
|
|
||||||
let RelocationTarget::Symbol(idx) = reloc.target() else {
|
|
||||||
bail!("Unsupported R_MIPS_GPREL16 relocation against a non-symbol");
|
|
||||||
};
|
|
||||||
let sym = file.symbol_by_index(idx)?;
|
|
||||||
|
|
||||||
// if the symbol we are relocating against is in a local section we need to add
|
|
||||||
// the ri_gp_value from .reginfo to the addend.
|
|
||||||
if sym.section().index().is_some() {
|
|
||||||
((addend & 0x0000FFFF) as i16 as i64) + self.ri_gp_value as i64
|
|
||||||
} else {
|
|
||||||
(addend & 0x0000FFFF) as i16 as i64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RelocationFlags::Elf { r_type: elf::R_MIPS_PC16 } => 0, // PC-relative relocation
|
|
||||||
RelocationFlags::Elf { r_type: R_MIPS15_S3 } => ((addend & 0x001FFFC0) >> 3) as i64,
|
|
||||||
flags => bail!("Unsupported MIPS implicit relocation {flags:?}"),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
|
fn instruction_display_flags(
|
||||||
match flags {
|
&self,
|
||||||
RelocationFlags::Elf { r_type } => match r_type {
|
diff_config: &DiffObjConfig,
|
||||||
elf::R_MIPS_32 => Cow::Borrowed("R_MIPS_32"),
|
) -> rabbitizer::InstructionDisplayFlags {
|
||||||
elf::R_MIPS_26 => Cow::Borrowed("R_MIPS_26"),
|
rabbitizer::InstructionDisplayFlags::default()
|
||||||
elf::R_MIPS_HI16 => Cow::Borrowed("R_MIPS_HI16"),
|
.with_unknown_instr_comment(false)
|
||||||
elf::R_MIPS_LO16 => Cow::Borrowed("R_MIPS_LO16"),
|
.with_use_dollar(diff_config.mips_register_prefix)
|
||||||
elf::R_MIPS_GPREL16 => Cow::Borrowed("R_MIPS_GPREL16"),
|
}
|
||||||
elf::R_MIPS_LITERAL => Cow::Borrowed("R_MIPS_LITERAL"),
|
|
||||||
elf::R_MIPS_GOT16 => Cow::Borrowed("R_MIPS_GOT16"),
|
fn parse_ins_ref(
|
||||||
elf::R_MIPS_PC16 => Cow::Borrowed("R_MIPS_PC16"),
|
&self,
|
||||||
elf::R_MIPS_CALL16 => Cow::Borrowed("R_MIPS_CALL16"),
|
ins_ref: InstructionRef,
|
||||||
R_MIPS15_S3 => Cow::Borrowed("R_MIPS15_S3"),
|
code: &[u8],
|
||||||
_ => Cow::Owned(format!("<{flags:?}>")),
|
diff_config: &DiffObjConfig,
|
||||||
},
|
) -> Result<rabbitizer::Instruction> {
|
||||||
_ => Cow::Owned(format!("<{flags:?}>")),
|
Ok(rabbitizer::Instruction::new(
|
||||||
}
|
self.endianness.read_u32_bytes(code.try_into()?),
|
||||||
|
Vram::new(ins_ref.address as u32),
|
||||||
|
self.instruction_flags(diff_config),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_reloc(args: &mut Vec<ObjInsArg>, reloc: &ObjReloc) -> Result<()> {
|
impl Arch for ArchMips {
|
||||||
|
fn scan_instructions_internal(
|
||||||
|
&self,
|
||||||
|
address: u64,
|
||||||
|
code: &[u8],
|
||||||
|
_section_index: usize,
|
||||||
|
_relocations: &[Relocation],
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
) -> Result<Vec<InstructionRef>> {
|
||||||
|
let instruction_flags = self.instruction_flags(diff_config);
|
||||||
|
let mut ops = Vec::<InstructionRef>::with_capacity(code.len() / 4);
|
||||||
|
let mut cur_addr = address as u32;
|
||||||
|
for chunk in code.chunks_exact(4) {
|
||||||
|
let code = self.endianness.read_u32_bytes(chunk.try_into()?);
|
||||||
|
let instruction =
|
||||||
|
rabbitizer::Instruction::new(code, Vram::new(cur_addr), instruction_flags);
|
||||||
|
let opcode = instruction.opcode() as u16;
|
||||||
|
let branch_dest = instruction.get_branch_vram_generic().map(|v| v.inner() as u64);
|
||||||
|
ops.push(InstructionRef { address: cur_addr as u64, size: 4, opcode, branch_dest });
|
||||||
|
cur_addr += 4;
|
||||||
|
}
|
||||||
|
Ok(ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_instruction(
|
||||||
|
&self,
|
||||||
|
resolved: ResolvedInstructionRef,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let instruction = self.parse_ins_ref(resolved.ins_ref, resolved.code, diff_config)?;
|
||||||
|
let display_flags = self.instruction_display_flags(diff_config);
|
||||||
|
let opcode = instruction.opcode();
|
||||||
|
cb(InstructionPart::opcode(opcode.name(), opcode as u16))?;
|
||||||
|
push_args(&instruction, resolved.relocation, &display_flags, cb)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn relocation_override(
|
||||||
|
&self,
|
||||||
|
file: &object::File<'_>,
|
||||||
|
section: &object::Section,
|
||||||
|
address: u64,
|
||||||
|
relocation: &object::Relocation,
|
||||||
|
) -> Result<Option<RelocationOverride>> {
|
||||||
|
match relocation.flags() {
|
||||||
|
// Handle ELF implicit relocations
|
||||||
|
object::RelocationFlags::Elf { r_type } => {
|
||||||
|
if relocation.has_implicit_addend() {
|
||||||
|
// Check for paired R_MIPS_HI16 and R_MIPS_LO16 relocations.
|
||||||
|
if let elf::R_MIPS_HI16 | elf::R_MIPS_LO16 = r_type
|
||||||
|
&& let Some(addend) = self
|
||||||
|
.paired_relocations
|
||||||
|
.get(section.index().0)
|
||||||
|
.and_then(|m| m.get(&address).copied())
|
||||||
|
{
|
||||||
|
return Ok(Some(RelocationOverride {
|
||||||
|
target: RelocationOverrideTarget::Keep,
|
||||||
|
addend,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = section.data()?;
|
||||||
|
let code = self
|
||||||
|
.endianness
|
||||||
|
.read_u32_bytes(data[address as usize..address as usize + 4].try_into()?);
|
||||||
|
let addend = match r_type {
|
||||||
|
elf::R_MIPS_32 => code as i64,
|
||||||
|
elf::R_MIPS_26 => ((code & 0x03FFFFFF) << 2) as i64,
|
||||||
|
elf::R_MIPS_HI16 => ((code & 0x0000FFFF) << 16) as i32 as i64,
|
||||||
|
elf::R_MIPS_LO16 | elf::R_MIPS_GOT16 | elf::R_MIPS_CALL16 => {
|
||||||
|
(code & 0x0000FFFF) as i16 as i64
|
||||||
|
}
|
||||||
|
elf::R_MIPS_GPREL16 | elf::R_MIPS_LITERAL => {
|
||||||
|
let object::RelocationTarget::Symbol(idx) = relocation.target() else {
|
||||||
|
bail!("Unsupported R_MIPS_GPREL16 relocation against a non-symbol");
|
||||||
|
};
|
||||||
|
let sym = file.symbol_by_index(idx)?;
|
||||||
|
|
||||||
|
// if the symbol we are relocating against is in a local section we need to add
|
||||||
|
// the ri_gp_value from .reginfo to the addend.
|
||||||
|
if sym.section().index().is_some() {
|
||||||
|
((code & 0x0000FFFF) as i16 as i64) + self.ri_gp_value as i64
|
||||||
|
} else {
|
||||||
|
(code & 0x0000FFFF) as i16 as i64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elf::R_MIPS_PC16 => 0, // PC-relative relocation
|
||||||
|
R_MIPS15_S3 => ((code & 0x001FFFC0) >> 3) as i64,
|
||||||
|
flags => bail!("Unsupported MIPS implicit relocation {flags:?}"),
|
||||||
|
};
|
||||||
|
Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Keep, addend }))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn demangle(&self, name: &str) -> Option<String> {
|
||||||
|
cpp_demangle::Symbol::new(name)
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
|
||||||
|
.or_else(|| cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
||||||
|
match flags {
|
||||||
|
RelocationFlags::Elf(r_type) => match r_type {
|
||||||
|
elf::R_MIPS_NONE => Some("R_MIPS_NONE"),
|
||||||
|
elf::R_MIPS_16 => Some("R_MIPS_16"),
|
||||||
|
elf::R_MIPS_32 => Some("R_MIPS_32"),
|
||||||
|
elf::R_MIPS_26 => Some("R_MIPS_26"),
|
||||||
|
elf::R_MIPS_HI16 => Some("R_MIPS_HI16"),
|
||||||
|
elf::R_MIPS_LO16 => Some("R_MIPS_LO16"),
|
||||||
|
elf::R_MIPS_GPREL16 => Some("R_MIPS_GPREL16"),
|
||||||
|
elf::R_MIPS_LITERAL => Some("R_MIPS_LITERAL"),
|
||||||
|
elf::R_MIPS_GOT16 => Some("R_MIPS_GOT16"),
|
||||||
|
elf::R_MIPS_PC16 => Some("R_MIPS_PC16"),
|
||||||
|
elf::R_MIPS_CALL16 => Some("R_MIPS_CALL16"),
|
||||||
|
R_MIPS15_S3 => Some("R_MIPS15_S3"),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
|
||||||
|
match flags {
|
||||||
|
RelocationFlags::Elf(r_type) => match r_type {
|
||||||
|
elf::R_MIPS_16 => 2,
|
||||||
|
elf::R_MIPS_32 => 4,
|
||||||
|
_ => 1,
|
||||||
|
},
|
||||||
|
_ => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extra_symbol_flags(&self, symbol: &object::Symbol) -> SymbolFlagSet {
|
||||||
|
let mut flags = SymbolFlagSet::default();
|
||||||
|
if self.ignored_symbols.contains(&symbol.index().0) {
|
||||||
|
flags |= SymbolFlag::Ignored;
|
||||||
|
}
|
||||||
|
flags
|
||||||
|
}
|
||||||
|
|
||||||
|
fn infer_function_size(
|
||||||
|
&self,
|
||||||
|
symbol: &Symbol,
|
||||||
|
section: &Section,
|
||||||
|
next_address: u64,
|
||||||
|
) -> Result<u64> {
|
||||||
|
// Trim any trailing 4-byte zeroes from the end (nops)
|
||||||
|
let mut new_address = next_address;
|
||||||
|
while new_address >= symbol.address + 4
|
||||||
|
&& let Some(data) = section.data_range(new_address - 4, 4)
|
||||||
|
&& data == [0u8; 4]
|
||||||
|
{
|
||||||
|
new_address -= 4;
|
||||||
|
}
|
||||||
|
// Check if the last instruction has a delay slot, if so, include the delay slot nop
|
||||||
|
if new_address + 4 <= next_address
|
||||||
|
&& new_address >= symbol.address + 4
|
||||||
|
&& let Some(data) = section.data_range(new_address - 4, 4)
|
||||||
|
&& let instruction = rabbitizer::Instruction::new(
|
||||||
|
self.endianness.read_u32_bytes(data.try_into().unwrap()),
|
||||||
|
Vram::new((new_address - 4) as u32),
|
||||||
|
self.default_instruction_flags(),
|
||||||
|
)
|
||||||
|
&& instruction.opcode().has_delay_slot()
|
||||||
|
{
|
||||||
|
new_address += 4;
|
||||||
|
}
|
||||||
|
Ok(new_address.saturating_sub(symbol.address))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_args(
|
||||||
|
instruction: &rabbitizer::Instruction,
|
||||||
|
relocation: Option<ResolvedRelocation>,
|
||||||
|
display_flags: &rabbitizer::InstructionDisplayFlags,
|
||||||
|
mut arg_cb: impl FnMut(InstructionPart) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let operands = instruction.valued_operands_iter();
|
||||||
|
for (idx, op) in operands.enumerate() {
|
||||||
|
if idx > 0 {
|
||||||
|
arg_cb(InstructionPart::separator())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
match op {
|
||||||
|
ValuedOperand::core_imm_i16(imm) => {
|
||||||
|
if let Some(resolved) = relocation {
|
||||||
|
push_reloc(resolved.relocation, &mut arg_cb)?;
|
||||||
|
} else {
|
||||||
|
arg_cb(InstructionPart::signed(imm))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ValuedOperand::core_imm_u16(imm) => {
|
||||||
|
if let Some(resolved) = relocation {
|
||||||
|
push_reloc(resolved.relocation, &mut arg_cb)?;
|
||||||
|
} else {
|
||||||
|
arg_cb(InstructionPart::unsigned(imm))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ValuedOperand::core_label(..) | ValuedOperand::core_branch_target_label(..) => {
|
||||||
|
if let Some(resolved) = relocation {
|
||||||
|
push_reloc(resolved.relocation, &mut arg_cb)?;
|
||||||
|
} else if let Some(branch_dest) = instruction
|
||||||
|
.get_branch_offset_generic()
|
||||||
|
.map(|o| (instruction.vram() + o).inner() as u64)
|
||||||
|
{
|
||||||
|
arg_cb(InstructionPart::branch_dest(branch_dest))?;
|
||||||
|
} else {
|
||||||
|
arg_cb(InstructionPart::opaque(
|
||||||
|
op.display(instruction, display_flags, None::<&str>).to_string(),
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ValuedOperand::core_imm_rs(imm, base) => {
|
||||||
|
if let Some(resolved) = relocation {
|
||||||
|
push_reloc(resolved.relocation, &mut arg_cb)?;
|
||||||
|
} else {
|
||||||
|
arg_cb(InstructionPart::Arg(InstructionArg::Value(
|
||||||
|
InstructionArgValue::Signed(imm as i64),
|
||||||
|
)))?;
|
||||||
|
}
|
||||||
|
arg_cb(InstructionPart::basic("("))?;
|
||||||
|
arg_cb(InstructionPart::opaque(base.either_name(
|
||||||
|
instruction.flags().abi(),
|
||||||
|
display_flags.named_gpr(),
|
||||||
|
!display_flags.use_dollar(),
|
||||||
|
)))?;
|
||||||
|
arg_cb(InstructionPart::basic(")"))?;
|
||||||
|
}
|
||||||
|
// ValuedOperand::r5900_immediate15(..) => match relocation {
|
||||||
|
// Some(resolved)
|
||||||
|
// if resolved.relocation.flags == RelocationFlags::Elf(R_MIPS15_S3) =>
|
||||||
|
// {
|
||||||
|
// push_reloc(&resolved.relocation, &mut arg_cb)?;
|
||||||
|
// }
|
||||||
|
// _ => {
|
||||||
|
// arg_cb(InstructionPart::opaque(op.disassemble(&instruction, None)))?;
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
_ => {
|
||||||
|
arg_cb(InstructionPart::opaque(
|
||||||
|
op.display(instruction, display_flags, None::<&str>).to_string(),
|
||||||
|
))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_reloc(
|
||||||
|
reloc: &Relocation,
|
||||||
|
mut arg_cb: impl FnMut(InstructionPart) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
match reloc.flags {
|
match reloc.flags {
|
||||||
RelocationFlags::Elf { r_type } => match r_type {
|
RelocationFlags::Elf(r_type) => match r_type {
|
||||||
elf::R_MIPS_HI16 => {
|
elf::R_MIPS_HI16 => {
|
||||||
args.push(ObjInsArg::PlainText("%hi(".into()));
|
arg_cb(InstructionPart::basic("%hi("))?;
|
||||||
args.push(ObjInsArg::Reloc);
|
arg_cb(InstructionPart::reloc())?;
|
||||||
args.push(ObjInsArg::PlainText(")".into()));
|
arg_cb(InstructionPart::basic(")"))?;
|
||||||
}
|
}
|
||||||
elf::R_MIPS_LO16 => {
|
elf::R_MIPS_LO16 => {
|
||||||
args.push(ObjInsArg::PlainText("%lo(".into()));
|
arg_cb(InstructionPart::basic("%lo("))?;
|
||||||
args.push(ObjInsArg::Reloc);
|
arg_cb(InstructionPart::reloc())?;
|
||||||
args.push(ObjInsArg::PlainText(")".into()));
|
arg_cb(InstructionPart::basic(")"))?;
|
||||||
}
|
}
|
||||||
elf::R_MIPS_GOT16 => {
|
elf::R_MIPS_GOT16 => {
|
||||||
args.push(ObjInsArg::PlainText("%got(".into()));
|
arg_cb(InstructionPart::basic("%got("))?;
|
||||||
args.push(ObjInsArg::Reloc);
|
arg_cb(InstructionPart::reloc())?;
|
||||||
args.push(ObjInsArg::PlainText(")".into()));
|
arg_cb(InstructionPart::basic(")"))?;
|
||||||
}
|
}
|
||||||
elf::R_MIPS_CALL16 => {
|
elf::R_MIPS_CALL16 => {
|
||||||
args.push(ObjInsArg::PlainText("%call16(".into()));
|
arg_cb(InstructionPart::basic("%call16("))?;
|
||||||
args.push(ObjInsArg::Reloc);
|
arg_cb(InstructionPart::reloc())?;
|
||||||
args.push(ObjInsArg::PlainText(")".into()));
|
arg_cb(InstructionPart::basic(")"))?;
|
||||||
}
|
}
|
||||||
elf::R_MIPS_GPREL16 => {
|
elf::R_MIPS_GPREL16 => {
|
||||||
args.push(ObjInsArg::PlainText("%gp_rel(".into()));
|
arg_cb(InstructionPart::basic("%gp_rel("))?;
|
||||||
args.push(ObjInsArg::Reloc);
|
arg_cb(InstructionPart::reloc())?;
|
||||||
args.push(ObjInsArg::PlainText(")".into()));
|
arg_cb(InstructionPart::basic(")"))?;
|
||||||
}
|
}
|
||||||
elf::R_MIPS_32
|
elf::R_MIPS_32
|
||||||
| elf::R_MIPS_26
|
| elf::R_MIPS_26
|
||||||
| elf::R_MIPS_LITERAL
|
| elf::R_MIPS_LITERAL
|
||||||
| elf::R_MIPS_PC16
|
| elf::R_MIPS_PC16
|
||||||
| R_MIPS15_S3 => {
|
| R_MIPS15_S3 => {
|
||||||
args.push(ObjInsArg::Reloc);
|
arg_cb(InstructionPart::reloc())?;
|
||||||
}
|
}
|
||||||
_ => bail!("Unsupported ELF MIPS relocation type {r_type}"),
|
_ => bail!("Unsupported ELF MIPS relocation type {r_type}"),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,104 +1,183 @@
|
|||||||
use std::{borrow::Cow, collections::BTreeMap, ffi::CStr};
|
use alloc::{
|
||||||
|
borrow::Cow,
|
||||||
|
boxed::Box,
|
||||||
|
format,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
use core::{
|
||||||
|
any::Any,
|
||||||
|
ffi::CStr,
|
||||||
|
fmt::{self, Debug},
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{Result, bail};
|
||||||
use byteorder::ByteOrder;
|
use encoding_rs::SHIFT_JIS;
|
||||||
use object::{Architecture, File, Object, ObjectSymbol, Relocation, RelocationFlags, Symbol};
|
use object::Endian as _;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diff::DiffObjConfig,
|
diff::{
|
||||||
obj::{ObjIns, ObjReloc, ObjSection},
|
DiffObjConfig,
|
||||||
|
display::{ContextItem, HoverItem, InstructionPart},
|
||||||
|
},
|
||||||
|
obj::{
|
||||||
|
FlowAnalysisResult, InstructionArg, InstructionRef, Object, ParsedInstruction, Relocation,
|
||||||
|
RelocationFlags, ResolvedInstructionRef, ResolvedSymbol, Section, Symbol, SymbolFlagSet,
|
||||||
|
SymbolKind,
|
||||||
|
},
|
||||||
util::ReallySigned,
|
util::ReallySigned,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "arm")]
|
#[cfg(feature = "arm")]
|
||||||
mod arm;
|
pub mod arm;
|
||||||
#[cfg(feature = "arm64")]
|
#[cfg(feature = "arm64")]
|
||||||
mod arm64;
|
pub mod arm64;
|
||||||
#[cfg(feature = "mips")]
|
#[cfg(feature = "mips")]
|
||||||
pub mod mips;
|
pub mod mips;
|
||||||
#[cfg(feature = "ppc")]
|
#[cfg(feature = "ppc")]
|
||||||
pub mod ppc;
|
pub mod ppc;
|
||||||
|
#[cfg(feature = "superh")]
|
||||||
|
pub mod superh;
|
||||||
#[cfg(feature = "x86")]
|
#[cfg(feature = "x86")]
|
||||||
pub mod x86;
|
pub mod x86;
|
||||||
|
|
||||||
/// Represents the type of data associated with an instruction
|
/// Represents the type of data associated with an instruction
|
||||||
|
#[derive(PartialEq)]
|
||||||
pub enum DataType {
|
pub enum DataType {
|
||||||
Int8,
|
Int8,
|
||||||
Int16,
|
Int16,
|
||||||
Int32,
|
Int32,
|
||||||
Int64,
|
Int64,
|
||||||
Int128,
|
|
||||||
Float,
|
Float,
|
||||||
Double,
|
Double,
|
||||||
Bytes,
|
Bytes,
|
||||||
String,
|
String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for DataType {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
DataType::Int8 => "Int8",
|
||||||
|
DataType::Int16 => "Int16",
|
||||||
|
DataType::Int32 => "Int32",
|
||||||
|
DataType::Int64 => "Int64",
|
||||||
|
DataType::Float => "Float",
|
||||||
|
DataType::Double => "Double",
|
||||||
|
DataType::Bytes => "Bytes",
|
||||||
|
DataType::String => "String",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DataType {
|
impl DataType {
|
||||||
pub fn display_bytes<Endian: ByteOrder>(&self, bytes: &[u8]) -> Option<String> {
|
pub fn display_labels(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<String> {
|
||||||
// TODO: Attempt to interpret large symbols as arrays of a smaller type,
|
let mut strs = Vec::new();
|
||||||
// fallback to intrepreting it as bytes.
|
for (literal, label_override) in self.display_literals(endian, bytes) {
|
||||||
// https://github.com/encounter/objdiff/issues/124
|
let label = label_override.unwrap_or_else(|| self.to_string());
|
||||||
if self.required_len().is_some_and(|l| bytes.len() != l) {
|
strs.push(format!("{label}: {literal}"))
|
||||||
log::warn!("Failed to display a symbol value for a symbol whose size doesn't match the instruction referencing it.");
|
}
|
||||||
return None;
|
strs
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_literals(
|
||||||
|
&self,
|
||||||
|
endian: object::Endianness,
|
||||||
|
bytes: &[u8],
|
||||||
|
) -> Vec<(String, Option<String>)> {
|
||||||
|
let mut strs = Vec::new();
|
||||||
|
if self.required_len().is_some_and(|l| bytes.len() < l) {
|
||||||
|
log::warn!(
|
||||||
|
"Failed to display a symbol value for a symbol whose size is too small for instruction referencing it."
|
||||||
|
);
|
||||||
|
return strs;
|
||||||
|
}
|
||||||
|
let mut bytes = bytes;
|
||||||
|
if self.required_len().is_some_and(|l| bytes.len() > l) {
|
||||||
|
// If the symbol's size is larger a single instance of this data type, we take just the
|
||||||
|
// bytes necessary for one of them in order to display the first element of the array.
|
||||||
|
bytes = &bytes[0..self.required_len().unwrap()];
|
||||||
|
// TODO: Attempt to interpret large symbols as arrays of a smaller type and show all
|
||||||
|
// elements of the array instead. https://github.com/encounter/objdiff/issues/124
|
||||||
|
// However, note that the stride of an array can not always be determined just by the
|
||||||
|
// data type guessed by the single instruction accessing it. There can also be arrays of
|
||||||
|
// structs that contain multiple elements of different types, so if other elements after
|
||||||
|
// the first one were to be displayed in this manner, they may be inaccurate.
|
||||||
}
|
}
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
DataType::Int8 => {
|
DataType::Int8 => {
|
||||||
let i = i8::from_ne_bytes(bytes.try_into().unwrap());
|
let i = i8::from_ne_bytes(bytes.try_into().unwrap());
|
||||||
|
strs.push((format!("{i:#x}"), None));
|
||||||
|
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
format!("Int8: {:#x} ({:#x})", i, ReallySigned(i))
|
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
||||||
} else {
|
|
||||||
format!("Int8: {:#x}", i)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType::Int16 => {
|
DataType::Int16 => {
|
||||||
let i = Endian::read_i16(bytes);
|
let i = endian.read_i16_bytes(bytes.try_into().unwrap());
|
||||||
|
strs.push((format!("{i:#x}"), None));
|
||||||
|
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
format!("Int16: {:#x} ({:#x})", i, ReallySigned(i))
|
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
||||||
} else {
|
|
||||||
format!("Int16: {:#x}", i)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType::Int32 => {
|
DataType::Int32 => {
|
||||||
let i = Endian::read_i32(bytes);
|
let i = endian.read_i32_bytes(bytes.try_into().unwrap());
|
||||||
|
strs.push((format!("{i:#x}"), None));
|
||||||
|
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
format!("Int32: {:#x} ({:#x})", i, ReallySigned(i))
|
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
||||||
} else {
|
|
||||||
format!("Int32: {:#x}", i)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType::Int64 => {
|
DataType::Int64 => {
|
||||||
let i = Endian::read_i64(bytes);
|
let i = endian.read_i64_bytes(bytes.try_into().unwrap());
|
||||||
|
strs.push((format!("{i:#x}"), None));
|
||||||
|
|
||||||
if i < 0 {
|
if i < 0 {
|
||||||
format!("Int64: {:#x} ({:#x})", i, ReallySigned(i))
|
strs.push((format!("{:#x}", ReallySigned(i)), None));
|
||||||
} else {
|
|
||||||
format!("Int64: {:#x}", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType::Int128 => {
|
|
||||||
let i = Endian::read_i128(bytes);
|
|
||||||
if i < 0 {
|
|
||||||
format!("Int128: {:#x} ({:#x})", i, ReallySigned(i))
|
|
||||||
} else {
|
|
||||||
format!("Int128: {:#x}", i)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType::Float => {
|
DataType::Float => {
|
||||||
format!("Float: {}", Endian::read_f32(bytes))
|
let bytes: [u8; 4] = bytes.try_into().unwrap();
|
||||||
|
strs.push((
|
||||||
|
format!("{:?}f", match endian {
|
||||||
|
object::Endianness::Little => f32::from_le_bytes(bytes),
|
||||||
|
object::Endianness::Big => f32::from_be_bytes(bytes),
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
DataType::Double => {
|
DataType::Double => {
|
||||||
format!("Double: {}", Endian::read_f64(bytes))
|
let bytes: [u8; 8] = bytes.try_into().unwrap();
|
||||||
|
strs.push((
|
||||||
|
format!("{:?}", match endian {
|
||||||
|
object::Endianness::Little => f64::from_le_bytes(bytes),
|
||||||
|
object::Endianness::Big => f64::from_be_bytes(bytes),
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
DataType::Bytes => {
|
DataType::Bytes => {
|
||||||
format!("Bytes: {:#?}", bytes)
|
strs.push((format!("{bytes:#?}"), None));
|
||||||
}
|
}
|
||||||
DataType::String => {
|
DataType::String => {
|
||||||
format!("String: {:?}", CStr::from_bytes_until_nul(bytes).ok()?)
|
if let Ok(cstr) = CStr::from_bytes_until_nul(bytes) {
|
||||||
|
strs.push((format!("{cstr:?}"), None));
|
||||||
|
}
|
||||||
|
if let Some(nul_idx) = bytes.iter().position(|&c| c == b'\0') {
|
||||||
|
let (cow, _, had_errors) = SHIFT_JIS.decode(&bytes[..nul_idx]);
|
||||||
|
if !had_errors {
|
||||||
|
let str = format!("{cow:?}");
|
||||||
|
// Only add the Shift JIS string if it's different from the ASCII string.
|
||||||
|
if !strs.iter().any(|x| x.0 == str) {
|
||||||
|
strs.push((str, Some("Shift JIS".into())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.into()
|
|
||||||
|
strs
|
||||||
}
|
}
|
||||||
|
|
||||||
fn required_len(&self) -> Option<usize> {
|
fn required_len(&self) -> Option<usize> {
|
||||||
@@ -107,7 +186,6 @@ impl DataType {
|
|||||||
DataType::Int16 => Some(2),
|
DataType::Int16 => Some(2),
|
||||||
DataType::Int32 => Some(4),
|
DataType::Int32 => Some(4),
|
||||||
DataType::Int64 => Some(8),
|
DataType::Int64 => Some(8),
|
||||||
DataType::Int128 => Some(16),
|
|
||||||
DataType::Float => Some(4),
|
DataType::Float => Some(4),
|
||||||
DataType::Double => Some(8),
|
DataType::Double => Some(8),
|
||||||
DataType::Bytes => None,
|
DataType::Bytes => None,
|
||||||
@@ -116,59 +194,294 @@ impl DataType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ObjArch: Send + Sync {
|
impl dyn Arch {
|
||||||
fn process_code(
|
/// Generate a list of instructions references (offset, size, opcode) from the given code.
|
||||||
|
///
|
||||||
|
/// See [`scan_instructions_internal`] for more details.
|
||||||
|
pub fn scan_instructions(
|
||||||
|
&self,
|
||||||
|
resolved: ResolvedSymbol,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
) -> Result<Vec<InstructionRef>> {
|
||||||
|
let mut result = self.scan_instructions_internal(
|
||||||
|
resolved.symbol.address,
|
||||||
|
resolved.data,
|
||||||
|
resolved.section_index,
|
||||||
|
&resolved.section.relocations,
|
||||||
|
diff_config,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let function_start = resolved.symbol.address;
|
||||||
|
let function_end = function_start + resolved.symbol.size;
|
||||||
|
|
||||||
|
// Remove any branch destinations that are outside the function range
|
||||||
|
for ins in result.iter_mut() {
|
||||||
|
if let Some(branch_dest) = ins.branch_dest
|
||||||
|
&& (branch_dest < function_start || branch_dest >= function_end)
|
||||||
|
{
|
||||||
|
ins.branch_dest = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve relocation targets within the same function to branch destinations
|
||||||
|
let mut ins_iter = result.iter_mut().peekable();
|
||||||
|
'outer: for reloc in resolved
|
||||||
|
.section
|
||||||
|
.relocations
|
||||||
|
.iter()
|
||||||
|
.skip_while(|r| r.address < function_start)
|
||||||
|
.take_while(|r| r.address < function_end)
|
||||||
|
{
|
||||||
|
let ins = loop {
|
||||||
|
let Some(ins) = ins_iter.peek_mut() else {
|
||||||
|
break 'outer;
|
||||||
|
};
|
||||||
|
if reloc.address < ins.address {
|
||||||
|
continue 'outer;
|
||||||
|
}
|
||||||
|
let ins = ins_iter.next().unwrap();
|
||||||
|
if reloc.address >= ins.address && reloc.address < ins.address + ins.size as u64 {
|
||||||
|
break ins;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Clear existing branch destination for instructions with relocations
|
||||||
|
ins.branch_dest = None;
|
||||||
|
let Some(target) = resolved.obj.symbols.get(reloc.target_symbol) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if target.section != Some(resolved.section_index) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let Some(target_address) = target.address.checked_add_signed(reloc.addend) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
// If the target address is within the function range, set it as a branch destination
|
||||||
|
if target_address >= function_start && target_address < function_end {
|
||||||
|
ins.branch_dest = Some(target_address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse an instruction to gather its mnemonic and arguments for more detailed comparison.
|
||||||
|
///
|
||||||
|
/// This is called only when we need to compare the arguments of an instruction.
|
||||||
|
pub fn process_instruction(
|
||||||
|
&self,
|
||||||
|
resolved: ResolvedInstructionRef,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
) -> Result<ParsedInstruction> {
|
||||||
|
let mut mnemonic = None;
|
||||||
|
let mut args = Vec::with_capacity(8);
|
||||||
|
let mut relocation_emitted = false;
|
||||||
|
self.display_instruction(resolved, diff_config, &mut |part| {
|
||||||
|
match part {
|
||||||
|
InstructionPart::Opcode(m, _) => mnemonic = Some(Cow::Owned(m.into_owned())),
|
||||||
|
InstructionPart::Arg(arg) => {
|
||||||
|
if arg == InstructionArg::Reloc {
|
||||||
|
relocation_emitted = true;
|
||||||
|
// If the relocation was resolved to a branch destination, emit that instead.
|
||||||
|
if let Some(dest) = resolved.ins_ref.branch_dest {
|
||||||
|
args.push(InstructionArg::BranchDest(dest));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args.push(arg.into_static());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
// If the instruction has a relocation, but we didn't format it in the display, add it to
|
||||||
|
// the end of the arguments list.
|
||||||
|
if resolved.relocation.is_some() && !relocation_emitted {
|
||||||
|
args.push(InstructionArg::Reloc);
|
||||||
|
}
|
||||||
|
Ok(ParsedInstruction {
|
||||||
|
ins_ref: resolved.ins_ref,
|
||||||
|
mnemonic: mnemonic.unwrap_or_default(),
|
||||||
|
args,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Arch: Any + Debug + Send + Sync {
|
||||||
|
/// Finishes arch-specific initialization that must be done after sections have been combined.
|
||||||
|
fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol]) {}
|
||||||
|
|
||||||
|
/// Generate a list of instructions references (offset, size, opcode) from the given code.
|
||||||
|
///
|
||||||
|
/// The opcode IDs are used to generate the initial diff. Implementations should do as little
|
||||||
|
/// parsing as possible here: just enough to identify the base instruction opcode, size, and
|
||||||
|
/// possible branch destination (for visual representation). As needed, instructions are parsed
|
||||||
|
/// via `process_instruction` to compare their arguments.
|
||||||
|
fn scan_instructions_internal(
|
||||||
&self,
|
&self,
|
||||||
address: u64,
|
address: u64,
|
||||||
code: &[u8],
|
code: &[u8],
|
||||||
section_index: usize,
|
section_index: usize,
|
||||||
relocations: &[ObjReloc],
|
relocations: &[Relocation],
|
||||||
line_info: &BTreeMap<u64, u32>,
|
diff_config: &DiffObjConfig,
|
||||||
config: &DiffObjConfig,
|
) -> Result<Vec<InstructionRef>>;
|
||||||
) -> Result<ProcessCodeResult>;
|
|
||||||
|
|
||||||
fn implcit_addend(
|
/// Format an instruction for display.
|
||||||
|
///
|
||||||
|
/// Implementations should call the callback for each part of the instruction: usually the
|
||||||
|
/// mnemonic and arguments, plus any separators and visual formatting.
|
||||||
|
fn display_instruction(
|
||||||
&self,
|
&self,
|
||||||
file: &File<'_>,
|
resolved: ResolvedInstructionRef,
|
||||||
section: &ObjSection,
|
diff_config: &DiffObjConfig,
|
||||||
address: u64,
|
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
|
||||||
reloc: &Relocation,
|
) -> Result<()>;
|
||||||
) -> Result<i64>;
|
|
||||||
|
/// Generate a list of fake relocations from the given code that represent pooled data accesses.
|
||||||
|
fn generate_pooled_relocations(
|
||||||
|
&self,
|
||||||
|
_address: u64,
|
||||||
|
_code: &[u8],
|
||||||
|
_relocations: &[Relocation],
|
||||||
|
_symbols: &[Symbol],
|
||||||
|
) -> Vec<Relocation> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform detailed data flow analysis
|
||||||
|
fn data_flow_analysis(
|
||||||
|
&self,
|
||||||
|
_obj: &Object,
|
||||||
|
_symbol: &Symbol,
|
||||||
|
_code: &[u8],
|
||||||
|
_relocations: &[Relocation],
|
||||||
|
) -> Option<Box<dyn FlowAnalysisResult>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn relocation_override(
|
||||||
|
&self,
|
||||||
|
_file: &object::File<'_>,
|
||||||
|
_section: &object::Section,
|
||||||
|
_address: u64,
|
||||||
|
_relocation: &object::Relocation,
|
||||||
|
) -> Result<Option<RelocationOverride>> {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
fn demangle(&self, _name: &str) -> Option<String> { None }
|
fn demangle(&self, _name: &str) -> Option<String> { None }
|
||||||
|
|
||||||
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str>;
|
fn reloc_name(&self, _flags: RelocationFlags) -> Option<&'static str> { None }
|
||||||
|
|
||||||
fn symbol_address(&self, symbol: &Symbol) -> u64 { symbol.address() }
|
fn data_reloc_size(&self, flags: RelocationFlags) -> usize;
|
||||||
|
|
||||||
fn guess_data_type(&self, _instruction: &ObjIns) -> Option<DataType> { None }
|
fn symbol_address(&self, address: u64, _kind: SymbolKind) -> u64 { address }
|
||||||
|
|
||||||
fn display_data_type(&self, _ty: DataType, bytes: &[u8]) -> Option<String> {
|
fn extra_symbol_flags(&self, _symbol: &object::Symbol) -> SymbolFlagSet {
|
||||||
Some(format!("Bytes: {:#x?}", bytes))
|
SymbolFlagSet::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Downcast methods
|
fn guess_data_type(
|
||||||
#[cfg(feature = "ppc")]
|
&self,
|
||||||
fn ppc(&self) -> Option<&ppc::ObjArchPpc> { None }
|
_resolved: ResolvedInstructionRef,
|
||||||
|
_bytes: &[u8],
|
||||||
|
) -> Option<DataType> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn symbol_hover(&self, _obj: &Object, _symbol_index: usize) -> Vec<HoverItem> { Vec::new() }
|
||||||
|
|
||||||
|
fn symbol_context(&self, _obj: &Object, _symbol_index: usize) -> Vec<ContextItem> { Vec::new() }
|
||||||
|
|
||||||
|
fn instruction_hover(
|
||||||
|
&self,
|
||||||
|
_obj: &Object,
|
||||||
|
_resolved: ResolvedInstructionRef,
|
||||||
|
) -> Vec<HoverItem> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instruction_context(
|
||||||
|
&self,
|
||||||
|
_obj: &Object,
|
||||||
|
_resolved: ResolvedInstructionRef,
|
||||||
|
) -> Vec<ContextItem> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn infer_function_size(
|
||||||
|
&self,
|
||||||
|
symbol: &Symbol,
|
||||||
|
_section: &Section,
|
||||||
|
next_address: u64,
|
||||||
|
) -> Result<u64> {
|
||||||
|
Ok(next_address.saturating_sub(symbol.address))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ProcessCodeResult {
|
pub fn new_arch(object: &object::File) -> Result<Box<dyn Arch>> {
|
||||||
pub ops: Vec<u16>,
|
use object::Object as _;
|
||||||
pub insts: Vec<ObjIns>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_arch(object: &File) -> Result<Box<dyn ObjArch>> {
|
|
||||||
Ok(match object.architecture() {
|
Ok(match object.architecture() {
|
||||||
#[cfg(feature = "ppc")]
|
#[cfg(feature = "ppc")]
|
||||||
Architecture::PowerPc => Box::new(ppc::ObjArchPpc::new(object)?),
|
object::Architecture::PowerPc | object::Architecture::PowerPc64 => {
|
||||||
|
Box::new(ppc::ArchPpc::new(object)?)
|
||||||
|
}
|
||||||
#[cfg(feature = "mips")]
|
#[cfg(feature = "mips")]
|
||||||
Architecture::Mips => Box::new(mips::ObjArchMips::new(object)?),
|
object::Architecture::Mips => Box::new(mips::ArchMips::new(object)?),
|
||||||
#[cfg(feature = "x86")]
|
#[cfg(feature = "x86")]
|
||||||
Architecture::I386 | Architecture::X86_64 => Box::new(x86::ObjArchX86::new(object)?),
|
object::Architecture::I386 | object::Architecture::X86_64 => {
|
||||||
|
Box::new(x86::ArchX86::new(object)?)
|
||||||
|
}
|
||||||
#[cfg(feature = "arm")]
|
#[cfg(feature = "arm")]
|
||||||
Architecture::Arm => Box::new(arm::ObjArchArm::new(object)?),
|
object::Architecture::Arm => Box::new(arm::ArchArm::new(object)?),
|
||||||
#[cfg(feature = "arm64")]
|
#[cfg(feature = "arm64")]
|
||||||
Architecture::Aarch64 => Box::new(arm64::ObjArchArm64::new(object)?),
|
object::Architecture::Aarch64 => Box::new(arm64::ArchArm64::new(object)?),
|
||||||
|
#[cfg(feature = "superh")]
|
||||||
|
object::Architecture::SuperH => Box::new(superh::ArchSuperH::new(object)?),
|
||||||
arch => bail!("Unsupported architecture: {arch:?}"),
|
arch => bail!("Unsupported architecture: {arch:?}"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct ArchDummy {}
|
||||||
|
|
||||||
|
impl ArchDummy {
|
||||||
|
pub fn new() -> Box<Self> { Box::new(Self {}) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arch for ArchDummy {
|
||||||
|
fn scan_instructions_internal(
|
||||||
|
&self,
|
||||||
|
_address: u64,
|
||||||
|
_code: &[u8],
|
||||||
|
_section_index: usize,
|
||||||
|
_relocations: &[Relocation],
|
||||||
|
_diff_config: &DiffObjConfig,
|
||||||
|
) -> Result<Vec<InstructionRef>> {
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_instruction(
|
||||||
|
&self,
|
||||||
|
_resolved: ResolvedInstructionRef,
|
||||||
|
_diff_config: &DiffObjConfig,
|
||||||
|
_cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_reloc_size(&self, _flags: RelocationFlags) -> usize { 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum RelocationOverrideTarget {
|
||||||
|
Keep,
|
||||||
|
Skip,
|
||||||
|
Symbol(object::SymbolIndex),
|
||||||
|
Section(object::SectionIndex),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct RelocationOverride {
|
||||||
|
pub target: RelocationOverrideTarget,
|
||||||
|
pub addend: i64,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,383 +0,0 @@
|
|||||||
use std::{borrow::Cow, collections::BTreeMap};
|
|
||||||
|
|
||||||
use anyhow::{bail, ensure, Result};
|
|
||||||
use byteorder::BigEndian;
|
|
||||||
use cwextab::{decode_extab, ExceptionTableData};
|
|
||||||
use object::{
|
|
||||||
elf, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, RelocationTarget,
|
|
||||||
Symbol, SymbolKind,
|
|
||||||
};
|
|
||||||
use ppc750cl::{Argument, InsIter, Opcode, GPR};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
arch::{DataType, ObjArch, ProcessCodeResult},
|
|
||||||
diff::DiffObjConfig,
|
|
||||||
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection, ObjSymbol},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Relative relocation, can be Simm, Offset or BranchDest
|
|
||||||
fn is_relative_arg(arg: &Argument) -> bool {
|
|
||||||
matches!(arg, Argument::Simm(_) | Argument::Offset(_) | Argument::BranchDest(_))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Relative or absolute relocation, can be Uimm, Simm or Offset
|
|
||||||
fn is_rel_abs_arg(arg: &Argument) -> bool {
|
|
||||||
matches!(arg, Argument::Uimm(_) | Argument::Simm(_) | Argument::Offset(_))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_offset_arg(arg: &Argument) -> bool { matches!(arg, Argument::Offset(_)) }
|
|
||||||
|
|
||||||
pub struct ObjArchPpc {
|
|
||||||
/// Exception info
|
|
||||||
pub extab: Option<BTreeMap<usize, ExceptionInfo>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjArchPpc {
|
|
||||||
pub fn new(file: &File) -> Result<Self> { Ok(Self { extab: decode_exception_info(file)? }) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjArch for ObjArchPpc {
|
|
||||||
fn process_code(
|
|
||||||
&self,
|
|
||||||
address: u64,
|
|
||||||
code: &[u8],
|
|
||||||
_section_index: usize,
|
|
||||||
relocations: &[ObjReloc],
|
|
||||||
line_info: &BTreeMap<u64, u32>,
|
|
||||||
config: &DiffObjConfig,
|
|
||||||
) -> Result<ProcessCodeResult> {
|
|
||||||
let ins_count = code.len() / 4;
|
|
||||||
let mut ops = Vec::<u16>::with_capacity(ins_count);
|
|
||||||
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
|
|
||||||
for (cur_addr, mut ins) in InsIter::new(code, address as u32) {
|
|
||||||
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
|
|
||||||
if let Some(reloc) = reloc {
|
|
||||||
// Zero out relocations
|
|
||||||
ins.code = match reloc.flags {
|
|
||||||
RelocationFlags::Elf { r_type: elf::R_PPC_EMB_SDA21 } => ins.code & !0x1FFFFF,
|
|
||||||
RelocationFlags::Elf { r_type: elf::R_PPC_REL24 } => ins.code & !0x3FFFFFC,
|
|
||||||
RelocationFlags::Elf { r_type: elf::R_PPC_REL14 } => ins.code & !0xFFFC,
|
|
||||||
RelocationFlags::Elf {
|
|
||||||
r_type: elf::R_PPC_ADDR16_HI | elf::R_PPC_ADDR16_HA | elf::R_PPC_ADDR16_LO,
|
|
||||||
} => ins.code & !0xFFFF,
|
|
||||||
_ => ins.code,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let orig = ins.basic().to_string();
|
|
||||||
let simplified = ins.simplified();
|
|
||||||
let formatted = simplified.to_string();
|
|
||||||
|
|
||||||
let mut reloc_arg = None;
|
|
||||||
if let Some(reloc) = reloc {
|
|
||||||
match reloc.flags {
|
|
||||||
RelocationFlags::Elf { r_type: elf::R_PPC_EMB_SDA21 } => {
|
|
||||||
reloc_arg = Some(1);
|
|
||||||
}
|
|
||||||
RelocationFlags::Elf { r_type: elf::R_PPC_REL24 | elf::R_PPC_REL14 } => {
|
|
||||||
reloc_arg = simplified.args.iter().rposition(is_relative_arg);
|
|
||||||
}
|
|
||||||
RelocationFlags::Elf {
|
|
||||||
r_type: elf::R_PPC_ADDR16_HI | elf::R_PPC_ADDR16_HA | elf::R_PPC_ADDR16_LO,
|
|
||||||
} => {
|
|
||||||
reloc_arg = simplified.args.iter().rposition(is_rel_abs_arg);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut args = vec![];
|
|
||||||
let mut branch_dest = None;
|
|
||||||
let mut writing_offset = false;
|
|
||||||
for (idx, arg) in simplified.args_iter().enumerate() {
|
|
||||||
if idx > 0 && !writing_offset {
|
|
||||||
args.push(ObjInsArg::PlainText(config.separator().into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if reloc_arg == Some(idx) {
|
|
||||||
let reloc = reloc.unwrap();
|
|
||||||
push_reloc(&mut args, reloc)?;
|
|
||||||
// For @sda21, we can omit the register argument
|
|
||||||
if matches!(reloc.flags, RelocationFlags::Elf { r_type: elf::R_PPC_EMB_SDA21 })
|
|
||||||
// Sanity check: the next argument should be r0
|
|
||||||
&& matches!(simplified.args.get(idx + 1), Some(Argument::GPR(GPR(0))))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match arg {
|
|
||||||
Argument::Simm(simm) => {
|
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(simm.0 as i64)));
|
|
||||||
}
|
|
||||||
Argument::Uimm(uimm) => {
|
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(uimm.0 as u64)));
|
|
||||||
}
|
|
||||||
Argument::Offset(offset) => {
|
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(offset.0 as i64)));
|
|
||||||
}
|
|
||||||
Argument::BranchDest(dest) => {
|
|
||||||
let dest = cur_addr.wrapping_add_signed(dest.0) as u64;
|
|
||||||
args.push(ObjInsArg::BranchDest(dest));
|
|
||||||
branch_dest = Some(dest);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
|
|
||||||
arg.to_string().into(),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if writing_offset {
|
|
||||||
args.push(ObjInsArg::PlainText(")".into()));
|
|
||||||
writing_offset = false;
|
|
||||||
}
|
|
||||||
if is_offset_arg(arg) {
|
|
||||||
args.push(ObjInsArg::PlainText("(".into()));
|
|
||||||
writing_offset = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ops.push(ins.op as u16);
|
|
||||||
let line = line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
|
|
||||||
insts.push(ObjIns {
|
|
||||||
address: cur_addr as u64,
|
|
||||||
size: 4,
|
|
||||||
mnemonic: Cow::Borrowed(simplified.mnemonic),
|
|
||||||
args,
|
|
||||||
reloc: reloc.cloned(),
|
|
||||||
op: ins.op as u16,
|
|
||||||
branch_dest,
|
|
||||||
line,
|
|
||||||
formatted,
|
|
||||||
orig: Some(orig),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(ProcessCodeResult { ops, insts })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn implcit_addend(
|
|
||||||
&self,
|
|
||||||
_file: &File<'_>,
|
|
||||||
_section: &ObjSection,
|
|
||||||
address: u64,
|
|
||||||
reloc: &Relocation,
|
|
||||||
) -> Result<i64> {
|
|
||||||
bail!("Unsupported PPC implicit relocation {:#x}:{:?}", address, reloc.flags())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn demangle(&self, name: &str) -> Option<String> {
|
|
||||||
cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
|
|
||||||
match flags {
|
|
||||||
RelocationFlags::Elf { r_type } => match r_type {
|
|
||||||
elf::R_PPC_ADDR16_LO => Cow::Borrowed("R_PPC_ADDR16_LO"),
|
|
||||||
elf::R_PPC_ADDR16_HI => Cow::Borrowed("R_PPC_ADDR16_HI"),
|
|
||||||
elf::R_PPC_ADDR16_HA => Cow::Borrowed("R_PPC_ADDR16_HA"),
|
|
||||||
elf::R_PPC_EMB_SDA21 => Cow::Borrowed("R_PPC_EMB_SDA21"),
|
|
||||||
elf::R_PPC_ADDR32 => Cow::Borrowed("R_PPC_ADDR32"),
|
|
||||||
elf::R_PPC_UADDR32 => Cow::Borrowed("R_PPC_UADDR32"),
|
|
||||||
elf::R_PPC_REL24 => Cow::Borrowed("R_PPC_REL24"),
|
|
||||||
elf::R_PPC_REL14 => Cow::Borrowed("R_PPC_REL14"),
|
|
||||||
_ => Cow::Owned(format!("<{flags:?}>")),
|
|
||||||
},
|
|
||||||
_ => Cow::Owned(format!("<{flags:?}>")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn guess_data_type(&self, instruction: &ObjIns) -> Option<super::DataType> {
|
|
||||||
// Always shows the first string of the table. Not ideal, but it's really hard to find
|
|
||||||
// the actual string being referenced.
|
|
||||||
if instruction.reloc.as_ref().is_some_and(|r| r.target.name.starts_with("@stringBase")) {
|
|
||||||
return Some(DataType::String);
|
|
||||||
}
|
|
||||||
|
|
||||||
match Opcode::from(instruction.op as u8) {
|
|
||||||
Opcode::Lbz | Opcode::Lbzu | Opcode::Lbzux | Opcode::Lbzx => Some(DataType::Int8),
|
|
||||||
Opcode::Lhz | Opcode::Lhzu | Opcode::Lhzux | Opcode::Lhzx => Some(DataType::Int16),
|
|
||||||
Opcode::Lha | Opcode::Lhau | Opcode::Lhaux | Opcode::Lhax => Some(DataType::Int16),
|
|
||||||
Opcode::Lwz | Opcode::Lwzu | Opcode::Lwzux | Opcode::Lwzx => Some(DataType::Int32),
|
|
||||||
Opcode::Lfs | Opcode::Lfsu | Opcode::Lfsux | Opcode::Lfsx => Some(DataType::Float),
|
|
||||||
Opcode::Lfd | Opcode::Lfdu | Opcode::Lfdux | Opcode::Lfdx => Some(DataType::Double),
|
|
||||||
|
|
||||||
Opcode::Stb | Opcode::Stbu | Opcode::Stbux | Opcode::Stbx => Some(DataType::Int8),
|
|
||||||
Opcode::Sth | Opcode::Sthu | Opcode::Sthux | Opcode::Sthx => Some(DataType::Int16),
|
|
||||||
Opcode::Stw | Opcode::Stwu | Opcode::Stwux | Opcode::Stwx => Some(DataType::Int32),
|
|
||||||
Opcode::Stfs | Opcode::Stfsu | Opcode::Stfsux | Opcode::Stfsx => Some(DataType::Float),
|
|
||||||
Opcode::Stfd | Opcode::Stfdu | Opcode::Stfdux | Opcode::Stfdx => Some(DataType::Double),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_data_type(&self, ty: DataType, bytes: &[u8]) -> Option<String> {
|
|
||||||
ty.display_bytes::<BigEndian>(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ppc(&self) -> Option<&ObjArchPpc> { Some(self) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjArchPpc {
|
|
||||||
pub fn extab_for_symbol(&self, symbol: &ObjSymbol) -> Option<&ExceptionInfo> {
|
|
||||||
symbol.original_index.and_then(|i| self.extab.as_ref()?.get(&i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_reloc(args: &mut Vec<ObjInsArg>, reloc: &ObjReloc) -> Result<()> {
|
|
||||||
match reloc.flags {
|
|
||||||
RelocationFlags::Elf { r_type } => match r_type {
|
|
||||||
elf::R_PPC_ADDR16_LO => {
|
|
||||||
args.push(ObjInsArg::Reloc);
|
|
||||||
args.push(ObjInsArg::PlainText("@l".into()));
|
|
||||||
}
|
|
||||||
elf::R_PPC_ADDR16_HI => {
|
|
||||||
args.push(ObjInsArg::Reloc);
|
|
||||||
args.push(ObjInsArg::PlainText("@h".into()));
|
|
||||||
}
|
|
||||||
elf::R_PPC_ADDR16_HA => {
|
|
||||||
args.push(ObjInsArg::Reloc);
|
|
||||||
args.push(ObjInsArg::PlainText("@ha".into()));
|
|
||||||
}
|
|
||||||
elf::R_PPC_EMB_SDA21 => {
|
|
||||||
args.push(ObjInsArg::Reloc);
|
|
||||||
args.push(ObjInsArg::PlainText("@sda21".into()));
|
|
||||||
}
|
|
||||||
elf::R_PPC_ADDR32 | elf::R_PPC_UADDR32 | elf::R_PPC_REL24 | elf::R_PPC_REL14 => {
|
|
||||||
args.push(ObjInsArg::Reloc);
|
|
||||||
}
|
|
||||||
_ => bail!("Unsupported ELF PPC relocation type {r_type}"),
|
|
||||||
},
|
|
||||||
flags => bail!("Unsupported PPC relocation kind: {flags:?}"),
|
|
||||||
};
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ExtabSymbolRef {
|
|
||||||
pub original_index: usize,
|
|
||||||
pub name: String,
|
|
||||||
pub demangled_name: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ExceptionInfo {
|
|
||||||
pub eti_symbol: ExtabSymbolRef,
|
|
||||||
pub etb_symbol: ExtabSymbolRef,
|
|
||||||
pub data: ExceptionTableData,
|
|
||||||
pub dtors: Vec<ExtabSymbolRef>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn decode_exception_info(file: &File<'_>) -> Result<Option<BTreeMap<usize, ExceptionInfo>>> {
|
|
||||||
let Some(extab_section) = file.section_by_name("extab") else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
let Some(extabindex_section) = file.section_by_name("extabindex") else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut result = BTreeMap::new();
|
|
||||||
let extab_relocations = extab_section.relocations().collect::<BTreeMap<u64, Relocation>>();
|
|
||||||
let extabindex_relocations =
|
|
||||||
extabindex_section.relocations().collect::<BTreeMap<u64, Relocation>>();
|
|
||||||
|
|
||||||
for extabindex in file.symbols().filter(|symbol| {
|
|
||||||
symbol.section_index() == Some(extabindex_section.index())
|
|
||||||
&& symbol.kind() == SymbolKind::Data
|
|
||||||
}) {
|
|
||||||
if extabindex.size() != 12 {
|
|
||||||
log::warn!("Invalid extabindex entry size {}", extabindex.size());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Each extabindex entry has two relocations:
|
|
||||||
// - 0x0: The function that the exception table is for
|
|
||||||
// - 0x8: The relevant entry in extab section
|
|
||||||
let Some(extab_func_reloc) = extabindex_relocations.get(&extabindex.address()) else {
|
|
||||||
log::warn!("Failed to find function relocation for extabindex entry");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let Some(extab_reloc) = extabindex_relocations.get(&(extabindex.address() + 8)) else {
|
|
||||||
log::warn!("Failed to find extab relocation for extabindex entry");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Resolve the function and extab symbols
|
|
||||||
let Some(extab_func) = relocation_symbol(file, extab_func_reloc)? else {
|
|
||||||
log::warn!("Failed to find function symbol for extabindex entry");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let extab_func_name = extab_func.name()?;
|
|
||||||
let Some(extab) = relocation_symbol(file, extab_reloc)? else {
|
|
||||||
log::warn!("Failed to find extab symbol for extabindex entry");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let extab_start_addr = extab.address() - extab_section.address();
|
|
||||||
let extab_end_addr = extab_start_addr + extab.size();
|
|
||||||
|
|
||||||
// All relocations in the extab section are dtors
|
|
||||||
let mut dtors: Vec<ExtabSymbolRef> = vec![];
|
|
||||||
for (_, reloc) in extab_relocations.range(extab_start_addr..extab_end_addr) {
|
|
||||||
let Some(symbol) = relocation_symbol(file, reloc)? else {
|
|
||||||
log::warn!("Failed to find symbol for extab relocation");
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
dtors.push(make_symbol_ref(&symbol)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode the extab data
|
|
||||||
let Some(extab_data) = extab_section.data_range(extab_start_addr, extab.size())? else {
|
|
||||||
log::warn!("Failed to get extab data for function {}", extab_func_name);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let data = match decode_extab(extab_data) {
|
|
||||||
Ok(decoded_data) => decoded_data,
|
|
||||||
Err(e) => {
|
|
||||||
log::warn!(
|
|
||||||
"Exception table decoding failed for function {}, reason: {}",
|
|
||||||
extab_func_name,
|
|
||||||
e.to_string()
|
|
||||||
);
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Add the new entry to the list
|
|
||||||
result.insert(extab_func.index().0, ExceptionInfo {
|
|
||||||
eti_symbol: make_symbol_ref(&extabindex)?,
|
|
||||||
etb_symbol: make_symbol_ref(&extab)?,
|
|
||||||
data,
|
|
||||||
dtors,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Some(result))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn relocation_symbol<'data, 'file>(
|
|
||||||
file: &'file File<'data>,
|
|
||||||
relocation: &Relocation,
|
|
||||||
) -> Result<Option<Symbol<'data, 'file>>> {
|
|
||||||
let addend = relocation.addend();
|
|
||||||
match relocation.target() {
|
|
||||||
RelocationTarget::Symbol(idx) => {
|
|
||||||
ensure!(addend == 0, "Symbol relocations must have zero addend");
|
|
||||||
Ok(Some(file.symbol_by_index(idx)?))
|
|
||||||
}
|
|
||||||
RelocationTarget::Section(idx) => {
|
|
||||||
ensure!(addend >= 0, "Section relocations must have non-negative addend");
|
|
||||||
let addend = addend as u64;
|
|
||||||
Ok(file
|
|
||||||
.symbols()
|
|
||||||
.find(|symbol| symbol.section_index() == Some(idx) && symbol.address() == addend))
|
|
||||||
}
|
|
||||||
target => bail!("Unsupported relocation target: {target:?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_symbol_ref(symbol: &Symbol) -> Result<ExtabSymbolRef> {
|
|
||||||
let name = symbol.name()?.to_string();
|
|
||||||
let demangled_name = cwdemangle::demangle(&name, &cwdemangle::DemangleOptions::default());
|
|
||||||
Ok(ExtabSymbolRef { original_index: symbol.index().0, name, demangled_name })
|
|
||||||
}
|
|
||||||
649
objdiff-core/src/arch/ppc/flow_analysis.rs
Normal file
649
objdiff-core/src/arch/ppc/flow_analysis.rs
Normal file
@@ -0,0 +1,649 @@
|
|||||||
|
use alloc::{
|
||||||
|
boxed::Box,
|
||||||
|
collections::{BTreeMap, BTreeSet},
|
||||||
|
format,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
use core::{
|
||||||
|
ffi::CStr,
|
||||||
|
ops::{Index, IndexMut},
|
||||||
|
};
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
use powerpc::{Extensions, Simm};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
arch::DataType,
|
||||||
|
obj::{FlowAnalysisResult, FlowAnalysisValue, Object, Relocation, Symbol},
|
||||||
|
util::{RawDouble, RawFloat},
|
||||||
|
};
|
||||||
|
|
||||||
|
fn is_store_instruction(op: powerpc::Opcode) -> bool {
|
||||||
|
use powerpc::Opcode;
|
||||||
|
matches!(
|
||||||
|
op,
|
||||||
|
Opcode::Stbux
|
||||||
|
| Opcode::Stbx
|
||||||
|
| Opcode::Stfdux
|
||||||
|
| Opcode::Stfdx
|
||||||
|
| Opcode::Stfiwx
|
||||||
|
| Opcode::Stfsux
|
||||||
|
| Opcode::Stfsx
|
||||||
|
| Opcode::Sthbrx
|
||||||
|
| Opcode::Sthux
|
||||||
|
| Opcode::Sthx
|
||||||
|
| Opcode::Stswi
|
||||||
|
| Opcode::Stswx
|
||||||
|
| Opcode::Stwbrx
|
||||||
|
| Opcode::Stwcx_
|
||||||
|
| Opcode::Stwux
|
||||||
|
| Opcode::Stwx
|
||||||
|
| Opcode::Stwu
|
||||||
|
| Opcode::Stb
|
||||||
|
| Opcode::Stbu
|
||||||
|
| Opcode::Sth
|
||||||
|
| Opcode::Sthu
|
||||||
|
| Opcode::Stmw
|
||||||
|
| Opcode::Stfs
|
||||||
|
| Opcode::Stfsu
|
||||||
|
| Opcode::Stfd
|
||||||
|
| Opcode::Stfdu
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn guess_data_type_from_load_store_inst_op(inst_op: powerpc::Opcode) -> Option<DataType> {
|
||||||
|
use powerpc::Opcode;
|
||||||
|
match inst_op {
|
||||||
|
Opcode::Lbz | Opcode::Lbzu | Opcode::Lbzux | Opcode::Lbzx => Some(DataType::Int8),
|
||||||
|
Opcode::Lhz | Opcode::Lhzu | Opcode::Lhzux | Opcode::Lhzx => Some(DataType::Int16),
|
||||||
|
Opcode::Lha | Opcode::Lhau | Opcode::Lhaux | Opcode::Lhax => Some(DataType::Int16),
|
||||||
|
Opcode::Lwz | Opcode::Lwzu | Opcode::Lwzux | Opcode::Lwzx => Some(DataType::Int32),
|
||||||
|
Opcode::Lfs | Opcode::Lfsu | Opcode::Lfsux | Opcode::Lfsx => Some(DataType::Float),
|
||||||
|
Opcode::Lfd | Opcode::Lfdu | Opcode::Lfdux | Opcode::Lfdx => Some(DataType::Double),
|
||||||
|
|
||||||
|
Opcode::Stb | Opcode::Stbu | Opcode::Stbux | Opcode::Stbx => Some(DataType::Int8),
|
||||||
|
Opcode::Sth | Opcode::Sthu | Opcode::Sthux | Opcode::Sthx => Some(DataType::Int16),
|
||||||
|
Opcode::Stw | Opcode::Stwu | Opcode::Stwux | Opcode::Stwx => Some(DataType::Int32),
|
||||||
|
Opcode::Stfs | Opcode::Stfsu | Opcode::Stfsux | Opcode::Stfsx => Some(DataType::Float),
|
||||||
|
Opcode::Stfd | Opcode::Stfdu | Opcode::Stfdux | Opcode::Stfdx => Some(DataType::Double),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Eq, Copy, Clone, Debug, PartialOrd, Ord)]
|
||||||
|
enum RegisterContent {
|
||||||
|
#[default]
|
||||||
|
Unknown,
|
||||||
|
Variable, // Multiple potential values
|
||||||
|
FloatConstant(RawFloat),
|
||||||
|
DoubleConstant(RawDouble),
|
||||||
|
IntConstant(i32),
|
||||||
|
InputRegister(u8),
|
||||||
|
Symbol(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Display for RegisterContent {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
match self {
|
||||||
|
RegisterContent::Unknown => write!(f, "unknown"),
|
||||||
|
RegisterContent::Variable => write!(f, "variable"),
|
||||||
|
RegisterContent::IntConstant(i) =>
|
||||||
|
// -i is safe because it's at most a 16 bit constant in the i32
|
||||||
|
{
|
||||||
|
if *i >= 0 {
|
||||||
|
write!(f, "0x{i:x}")
|
||||||
|
} else {
|
||||||
|
write!(f, "-0x{:x}", -i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RegisterContent::FloatConstant(RawFloat(fp)) => write!(f, "{fp:?}f"),
|
||||||
|
RegisterContent::DoubleConstant(RawDouble(fp)) => write!(f, "{fp:?}d"),
|
||||||
|
RegisterContent::InputRegister(p) => write!(f, "input{p}"),
|
||||||
|
RegisterContent::Symbol(_u) => write!(f, "relocation"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Ord, PartialOrd)]
|
||||||
|
struct RegisterState {
|
||||||
|
gpr: [RegisterContent; 32],
|
||||||
|
fpr: [RegisterContent; 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegisterState {
|
||||||
|
fn new() -> Self {
|
||||||
|
RegisterState { gpr: [RegisterContent::Unknown; 32], fpr: [RegisterContent::Unknown; 32] }
|
||||||
|
}
|
||||||
|
|
||||||
|
// During a function call, these registers must be assumed trashed.
|
||||||
|
fn clear_volatile(&mut self) {
|
||||||
|
self[powerpc::GPR(0)] = RegisterContent::Unknown;
|
||||||
|
for i in 0..=13 {
|
||||||
|
self[powerpc::GPR(i)] = RegisterContent::Unknown;
|
||||||
|
}
|
||||||
|
for i in 0..=13 {
|
||||||
|
self[powerpc::FPR(i)] = RegisterContent::Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark potential input values.
|
||||||
|
// Subsequent flow analysis will "realize" that they are not actually inputs if
|
||||||
|
// they get overwritten with another value before getting read.
|
||||||
|
fn set_potential_inputs(&mut self) {
|
||||||
|
for g_reg in 3..=13 {
|
||||||
|
self[powerpc::GPR(g_reg)] = RegisterContent::InputRegister(g_reg);
|
||||||
|
}
|
||||||
|
for f_reg in 1..=13 {
|
||||||
|
self[powerpc::FPR(f_reg)] = RegisterContent::InputRegister(f_reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the there is no value, we can take the new known value.
|
||||||
|
// If there's a known value different than the new value, the content
|
||||||
|
// must is variable.
|
||||||
|
// Returns whether the current value was updated.
|
||||||
|
fn unify_values(current: &mut RegisterContent, new: &RegisterContent) -> bool {
|
||||||
|
if *current == *new {
|
||||||
|
false
|
||||||
|
} else if *current == RegisterContent::Unknown {
|
||||||
|
*current = *new;
|
||||||
|
true
|
||||||
|
} else if *current == RegisterContent::Variable {
|
||||||
|
// Already variable
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
*current = RegisterContent::Variable;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unify currently known register contents in a give situation with new
|
||||||
|
// information about the register contents in that situation.
|
||||||
|
// Currently unknown register contents can be filled, but if there are
|
||||||
|
// conflicting contents, we go back to unknown.
|
||||||
|
fn unify(&mut self, other: &RegisterState) -> bool {
|
||||||
|
let mut updated = false;
|
||||||
|
for i in 0..32 {
|
||||||
|
updated |= Self::unify_values(&mut self.gpr[i], &other.gpr[i]);
|
||||||
|
updated |= Self::unify_values(&mut self.fpr[i], &other.fpr[i]);
|
||||||
|
}
|
||||||
|
updated
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<powerpc::GPR> for RegisterState {
|
||||||
|
type Output = RegisterContent;
|
||||||
|
|
||||||
|
fn index(&self, gpr: powerpc::GPR) -> &Self::Output { &self.gpr[gpr.0 as usize] }
|
||||||
|
}
|
||||||
|
impl IndexMut<powerpc::GPR> for RegisterState {
|
||||||
|
fn index_mut(&mut self, gpr: powerpc::GPR) -> &mut Self::Output {
|
||||||
|
&mut self.gpr[gpr.0 as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Index<powerpc::FPR> for RegisterState {
|
||||||
|
type Output = RegisterContent;
|
||||||
|
|
||||||
|
fn index(&self, fpr: powerpc::FPR) -> &Self::Output { &self.fpr[fpr.0 as usize] }
|
||||||
|
}
|
||||||
|
impl IndexMut<powerpc::FPR> for RegisterState {
|
||||||
|
fn index_mut(&mut self, fpr: powerpc::FPR) -> &mut Self::Output {
|
||||||
|
&mut self.fpr[fpr.0 as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_instruction(
|
||||||
|
registers: &mut RegisterState,
|
||||||
|
op: &powerpc::Opcode,
|
||||||
|
args: &powerpc::Arguments,
|
||||||
|
) {
|
||||||
|
use powerpc::{Argument, GPR, Opcode};
|
||||||
|
match (op, args[0], args[1], args[2]) {
|
||||||
|
(Opcode::Or, Argument::GPR(a), Argument::GPR(b), Argument::GPR(c)) => {
|
||||||
|
// Move is implemented as or with self for ints
|
||||||
|
if b == c {
|
||||||
|
registers[a] = registers[b];
|
||||||
|
} else {
|
||||||
|
registers[a] = RegisterContent::Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Opcode::Fmr, Argument::FPR(a), Argument::FPR(b), _) => {
|
||||||
|
registers[a] = registers[b];
|
||||||
|
}
|
||||||
|
(Opcode::Addi, Argument::GPR(a), Argument::GPR(GPR(0)), Argument::Simm(c)) => {
|
||||||
|
// Load immidiate implemented as addi with addend = r0
|
||||||
|
// Let Addi with other addends fall through to the case which
|
||||||
|
// overwrites the destination
|
||||||
|
registers[a] = RegisterContent::IntConstant(c.0 as i32);
|
||||||
|
}
|
||||||
|
(Opcode::Bcctr, _, _, _) => {
|
||||||
|
// Called a function pointer, may have erased volatile registers
|
||||||
|
registers.clear_volatile();
|
||||||
|
}
|
||||||
|
(Opcode::B, _, _, _) => {
|
||||||
|
if get_branch_offset(args) == 0 {
|
||||||
|
// Call to another function
|
||||||
|
registers.clear_volatile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(
|
||||||
|
Opcode::Stbu | Opcode::Sthu | Opcode::Stwu | Opcode::Stfsu | Opcode::Stfdu,
|
||||||
|
_,
|
||||||
|
_,
|
||||||
|
Argument::GPR(rel),
|
||||||
|
) => {
|
||||||
|
// Storing with update, clear updated register (third arg)
|
||||||
|
registers[rel] = RegisterContent::Unknown;
|
||||||
|
}
|
||||||
|
(
|
||||||
|
Opcode::Stbux | Opcode::Sthux | Opcode::Stwux | Opcode::Stfsux | Opcode::Stfdux,
|
||||||
|
_,
|
||||||
|
Argument::GPR(rel),
|
||||||
|
_,
|
||||||
|
) => {
|
||||||
|
// Storing indexed with update, clear updated register (second arg)
|
||||||
|
registers[rel] = RegisterContent::Unknown;
|
||||||
|
}
|
||||||
|
(Opcode::Lmw, Argument::GPR(target), _, _) => {
|
||||||
|
// `lmw` overwrites all registers from rd to r31.
|
||||||
|
for reg in target.0..31 {
|
||||||
|
registers[GPR(reg)] = RegisterContent::Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(_, Argument::GPR(a), _, _) => {
|
||||||
|
// Store instructions don't modify the GPR
|
||||||
|
if !is_store_instruction(*op) {
|
||||||
|
// Other operations which write to GPR a
|
||||||
|
registers[a] = RegisterContent::Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(_, Argument::FPR(a), _, _) => {
|
||||||
|
// Store instructions don't modify the FPR
|
||||||
|
if !is_store_instruction(*op) {
|
||||||
|
// Other operations which write to FPR a
|
||||||
|
registers[a] = RegisterContent::Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(_, _, _, _) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_branch_offset(args: &powerpc::Arguments) -> i32 {
|
||||||
|
for arg in args.iter() {
|
||||||
|
match arg {
|
||||||
|
powerpc::Argument::BranchDest(dest) => return dest.0 / 4,
|
||||||
|
powerpc::Argument::None => break,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct PPCFlowAnalysisResult {
|
||||||
|
argument_contents: BTreeMap<(u64, u8), FlowAnalysisValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PPCFlowAnalysisResult {
|
||||||
|
fn set_argument_value_at_address(
|
||||||
|
&mut self,
|
||||||
|
address: u64,
|
||||||
|
argument: u8,
|
||||||
|
value: FlowAnalysisValue,
|
||||||
|
) {
|
||||||
|
self.argument_contents.insert((address, argument), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new() -> Self { PPCFlowAnalysisResult { argument_contents: Default::default() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FlowAnalysisResult for PPCFlowAnalysisResult {
|
||||||
|
fn get_argument_value_at_address(
|
||||||
|
&self,
|
||||||
|
address: u64,
|
||||||
|
argument: u8,
|
||||||
|
) -> Option<&FlowAnalysisValue> {
|
||||||
|
self.argument_contents.get(&(address, argument))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clamp_text_length(s: String, max: usize) -> String {
|
||||||
|
if s.len() <= max { s } else { format!("{}…", s.chars().take(max - 3).collect::<String>()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_register_content_from_reloc(
|
||||||
|
reloc: &Relocation,
|
||||||
|
obj: &Object,
|
||||||
|
op: powerpc::Opcode,
|
||||||
|
) -> RegisterContent {
|
||||||
|
if let Some(bytes) = obj.symbol_data(reloc.target_symbol) {
|
||||||
|
match guess_data_type_from_load_store_inst_op(op) {
|
||||||
|
Some(DataType::Float) => {
|
||||||
|
RegisterContent::FloatConstant(RawFloat(match obj.endianness {
|
||||||
|
object::Endianness::Little => {
|
||||||
|
f32::from_le_bytes(bytes.try_into().unwrap_or([0; 4]))
|
||||||
|
}
|
||||||
|
object::Endianness::Big => {
|
||||||
|
f32::from_be_bytes(bytes.try_into().unwrap_or([0; 4]))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Some(DataType::Double) => {
|
||||||
|
RegisterContent::DoubleConstant(RawDouble(match obj.endianness {
|
||||||
|
object::Endianness::Little => {
|
||||||
|
f64::from_le_bytes(bytes.try_into().unwrap_or([0; 8]))
|
||||||
|
}
|
||||||
|
object::Endianness::Big => {
|
||||||
|
f64::from_be_bytes(bytes.try_into().unwrap_or([0; 8]))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
_ => RegisterContent::Symbol(reloc.target_symbol),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
RegisterContent::Symbol(reloc.target_symbol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executing op with args at cur_address, update current_state with symbols that
|
||||||
|
// come from relocations. That is, references to globals, floating point
|
||||||
|
// constants, string constants, etc.
|
||||||
|
fn fill_registers_from_relocation(
|
||||||
|
reloc: &Relocation,
|
||||||
|
current_state: &mut RegisterState,
|
||||||
|
obj: &Object,
|
||||||
|
op: powerpc::Opcode,
|
||||||
|
args: &powerpc::Arguments,
|
||||||
|
) {
|
||||||
|
// Only update the register state for loads. We may store to a reloc
|
||||||
|
// address but that doesn't update register contents.
|
||||||
|
if !is_store_instruction(op) {
|
||||||
|
match (op, args[0]) {
|
||||||
|
// Everything else is a load of some sort
|
||||||
|
(_, powerpc::Argument::GPR(gpr)) => {
|
||||||
|
current_state[gpr] = get_register_content_from_reloc(reloc, obj, op);
|
||||||
|
}
|
||||||
|
(_, powerpc::Argument::FPR(fpr)) => {
|
||||||
|
current_state[fpr] = get_register_content_from_reloc(reloc, obj, op);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special helper fragments generated by MWCC.
|
||||||
|
// See: https://github.com/encounter/decomp-toolkit/blob/main/src/analysis/pass.rs
|
||||||
|
const SLEDS: [&str; 6] = ["_savefpr_", "_restfpr_", "_savegpr_", "_restgpr_", "_savev", "_restv"];
|
||||||
|
|
||||||
|
fn is_sled_function(name: &str) -> bool { SLEDS.iter().any(|sled| name.starts_with(sled)) }
|
||||||
|
|
||||||
|
pub fn ppc_data_flow_analysis(
|
||||||
|
obj: &Object,
|
||||||
|
func_symbol: &Symbol,
|
||||||
|
code: &[u8],
|
||||||
|
relocations: &[Relocation],
|
||||||
|
extensions: Extensions,
|
||||||
|
) -> Box<dyn FlowAnalysisResult> {
|
||||||
|
use alloc::collections::VecDeque;
|
||||||
|
|
||||||
|
use powerpc::InsIter;
|
||||||
|
let instructions = InsIter::new(code, func_symbol.address as u32, extensions)
|
||||||
|
.map(|(_addr, ins)| (ins.op, ins.basic().args))
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
let func_address = func_symbol.address;
|
||||||
|
|
||||||
|
// Get initial register values from function parameters
|
||||||
|
let mut initial_register_state = RegisterState::new();
|
||||||
|
initial_register_state.set_potential_inputs();
|
||||||
|
|
||||||
|
let mut execution_queue = VecDeque::<(usize, RegisterState)>::new();
|
||||||
|
execution_queue.push_back((0, initial_register_state));
|
||||||
|
|
||||||
|
// Execute the instructions against abstract data
|
||||||
|
let mut failsafe_counter = 0;
|
||||||
|
let mut taken_branches = BTreeSet::<(usize, RegisterState)>::new();
|
||||||
|
let mut register_state_at = Vec::<RegisterState>::new();
|
||||||
|
let mut completed_first_pass = false;
|
||||||
|
register_state_at.resize_with(instructions.len(), RegisterState::new);
|
||||||
|
while let Some((mut index, mut current_state)) = execution_queue.pop_front() {
|
||||||
|
while let Some((op, args)) = instructions.get(index) {
|
||||||
|
// Record the state at this index
|
||||||
|
// If recording does not result in any changes to the known values
|
||||||
|
// we're done, because the subsequent values are a function of the
|
||||||
|
// current values so we'll get the same result as the last time
|
||||||
|
// we went down this path.
|
||||||
|
// Don't break out if we haven't even completed the first pass
|
||||||
|
// through the function though.
|
||||||
|
if !register_state_at[index].unify(¤t_state) && completed_first_pass {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get symbol used in this instruction
|
||||||
|
let cur_addr = (func_address as u32) + ((index * 4) as u32);
|
||||||
|
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
|
||||||
|
|
||||||
|
// Is this a branch to a compiler generated helper? These helpers
|
||||||
|
// do not trash registers like normal function calls, so we don't
|
||||||
|
// want to treat this as normal execution.
|
||||||
|
let symbol = reloc.and_then(|r| obj.symbols.get(r.target_symbol));
|
||||||
|
let is_sled_invocation = symbol.is_some_and(|x| is_sled_function(&x.name));
|
||||||
|
|
||||||
|
// Execute the instruction to update the state
|
||||||
|
// Since sled invocations are only used to save / restore registers
|
||||||
|
// as part of prelude / cleanup in a function call we don't have to
|
||||||
|
// do any execution for them.
|
||||||
|
if !is_sled_invocation {
|
||||||
|
execute_instruction(&mut current_state, op, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in register state coming from relocations at this line. This
|
||||||
|
// handles references to global variables, floating point constants,
|
||||||
|
// etc.
|
||||||
|
if let Some(reloc) = reloc {
|
||||||
|
fill_registers_from_relocation(reloc, &mut current_state, obj, *op, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add conditional branches to execution queue
|
||||||
|
// Only take a given (address, register state) combination once. If
|
||||||
|
// the known register state is different we have to take the branch
|
||||||
|
// again to stabilize the known values for backwards branches.
|
||||||
|
if op == &powerpc::Opcode::Bc {
|
||||||
|
let branch_state = (index, current_state.clone());
|
||||||
|
if !taken_branches.contains(&branch_state) {
|
||||||
|
let offset = get_branch_offset(args);
|
||||||
|
let target_index = ((index as i32) + offset) as usize;
|
||||||
|
execution_queue.push_back((target_index, current_state.clone()));
|
||||||
|
taken_branches.insert(branch_state);
|
||||||
|
|
||||||
|
// We should never hit this case, but avoid getting stuck in
|
||||||
|
// an infinite loop if we hit some kind of bad behavior.
|
||||||
|
failsafe_counter += 1;
|
||||||
|
if failsafe_counter > 256 {
|
||||||
|
//println!("Analysis of {} failed to stabilize", func_symbol.name);
|
||||||
|
return Box::new(PPCFlowAnalysisResult::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update index
|
||||||
|
if op == &powerpc::Opcode::B {
|
||||||
|
// Unconditional branch
|
||||||
|
let offset = get_branch_offset(args);
|
||||||
|
if offset > 0 {
|
||||||
|
// Jump table or branch to over else clause.
|
||||||
|
index += offset as usize;
|
||||||
|
} else if offset == 0 {
|
||||||
|
// Function call with relocation. We'll return to
|
||||||
|
// the next instruction.
|
||||||
|
index += 1;
|
||||||
|
} else {
|
||||||
|
// Unconditional branch (E.g.: loop { ... })
|
||||||
|
// Also some compilations of loops put the conditional at
|
||||||
|
// the end and B to it for the check of the first iteration.
|
||||||
|
let branch_state = (index, current_state.clone());
|
||||||
|
if taken_branches.contains(&branch_state) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
taken_branches.insert(branch_state);
|
||||||
|
index = ((index as i32) + offset) as usize;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Normal execution of next instruction
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark that we've completed at least one pass over the function, at
|
||||||
|
// this point we can break out if the code we're running doesn't change
|
||||||
|
// any register outcomes.
|
||||||
|
completed_first_pass = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the relevant data flow values for simplified instructions
|
||||||
|
generate_flow_analysis_result(
|
||||||
|
obj,
|
||||||
|
func_address,
|
||||||
|
code,
|
||||||
|
register_state_at,
|
||||||
|
relocations,
|
||||||
|
extensions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_string_data(obj: &Object, symbol_index: usize, offset: Simm) -> Option<&str> {
|
||||||
|
if let Some(sym) = obj.symbols.get(symbol_index)
|
||||||
|
&& sym.name.starts_with("@stringBase")
|
||||||
|
&& offset.0 != 0
|
||||||
|
&& let Some(data) = obj.symbol_data(symbol_index)
|
||||||
|
{
|
||||||
|
let bytes = &data[offset.0 as usize..];
|
||||||
|
if let Ok(Ok(str)) = CStr::from_bytes_until_nul(bytes).map(|x| x.to_str()) {
|
||||||
|
return Some(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the relevant part of the flow analysis out into the FlowAnalysisResult
|
||||||
|
// the rest of the application will use to query results of the flow analysis.
|
||||||
|
// Flow analysis will compute the known contents of every register at every
|
||||||
|
// line, but we only need to record the values of registers that are actually
|
||||||
|
// referenced at each line.
|
||||||
|
fn generate_flow_analysis_result(
|
||||||
|
obj: &Object,
|
||||||
|
base_address: u64,
|
||||||
|
code: &[u8],
|
||||||
|
register_state_at: Vec<RegisterState>,
|
||||||
|
relocations: &[Relocation],
|
||||||
|
extensions: Extensions,
|
||||||
|
) -> Box<PPCFlowAnalysisResult> {
|
||||||
|
use powerpc::{Argument, InsIter};
|
||||||
|
let mut analysis_result = PPCFlowAnalysisResult::new();
|
||||||
|
let default_register_state = RegisterState::new();
|
||||||
|
for (addr, ins) in InsIter::new(code, 0, extensions) {
|
||||||
|
let ins_address = base_address + (addr as u64);
|
||||||
|
let index = addr / 4;
|
||||||
|
let powerpc::ParsedIns { mnemonic: _, args } = ins.simplified();
|
||||||
|
|
||||||
|
// If we're already showing relocations on a line don't also show data flow
|
||||||
|
let reloc = relocations.iter().find(|r| (r.address & !3) == ins_address);
|
||||||
|
|
||||||
|
// Special case to show float and double constants on the line where
|
||||||
|
// they are being loaded.
|
||||||
|
// We need to do this before we break out on showing relocations in the
|
||||||
|
// subsequent if statement.
|
||||||
|
if let (powerpc::Opcode::Lfs | powerpc::Opcode::Lfd, Some(reloc)) = (ins.op, reloc) {
|
||||||
|
let content = get_register_content_from_reloc(reloc, obj, ins.op);
|
||||||
|
if matches!(
|
||||||
|
content,
|
||||||
|
RegisterContent::FloatConstant(_) | RegisterContent::DoubleConstant(_)
|
||||||
|
) {
|
||||||
|
analysis_result.set_argument_value_at_address(
|
||||||
|
ins_address,
|
||||||
|
1,
|
||||||
|
FlowAnalysisValue::Text(content.to_string()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Don't need to show any other data flow if we're showing that
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case to show string constants on the line where they are
|
||||||
|
// being indexed to. This will typically be "addi t, stringbase, offset"
|
||||||
|
let registers = register_state_at.get(index as usize).unwrap_or(&default_register_state);
|
||||||
|
if let (powerpc::Opcode::Addi, Argument::GPR(rel), Argument::Simm(offset)) =
|
||||||
|
(ins.op, args[1], args[2])
|
||||||
|
&& let RegisterContent::Symbol(sym_index) = registers[rel]
|
||||||
|
&& let Some(str) = get_string_data(obj, sym_index, offset)
|
||||||
|
{
|
||||||
|
// Show the string constant in the analysis result
|
||||||
|
let formatted = format!("\"{str}\"");
|
||||||
|
analysis_result.set_argument_value_at_address(
|
||||||
|
ins_address,
|
||||||
|
2,
|
||||||
|
FlowAnalysisValue::Text(clamp_text_length(formatted, 20)),
|
||||||
|
);
|
||||||
|
// Don't continue, we want to show the stringbase value as well
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_store = is_store_instruction(ins.op);
|
||||||
|
for (arg_index, arg) in args.into_iter().enumerate() {
|
||||||
|
// Hacky shorthand for determining which arguments are sources,
|
||||||
|
// We only want to show data flow for source registers, not target
|
||||||
|
// registers. Technically there are some non-"st_" operations which
|
||||||
|
// read from their first argument but they're rare.
|
||||||
|
if (arg_index == 0) && !is_store {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = match arg {
|
||||||
|
Argument::GPR(gpr) => Some(registers[gpr]),
|
||||||
|
Argument::FPR(fpr) => Some(registers[fpr]),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let analysis_value = match content {
|
||||||
|
Some(RegisterContent::Symbol(s)) => {
|
||||||
|
if reloc.is_none() {
|
||||||
|
// Only symbols if there isn't already a relocation, because
|
||||||
|
// code other than the data flow analysis will be showing
|
||||||
|
// the symbol for a relocation on the line it is for. If we
|
||||||
|
// also showed it as data flow analysis value we would be
|
||||||
|
// showing redundant information.
|
||||||
|
obj.symbols.get(s).map(|sym| {
|
||||||
|
FlowAnalysisValue::Text(clamp_text_length(
|
||||||
|
sym.demangled_name.as_ref().unwrap_or(&sym.name).clone(),
|
||||||
|
20,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(RegisterContent::InputRegister(reg)) => {
|
||||||
|
let reg_name = match arg {
|
||||||
|
Argument::GPR(_) => format!("in_r{reg}"),
|
||||||
|
Argument::FPR(_) => format!("in_f{reg}"),
|
||||||
|
_ => panic!("Register content should only be in a register"),
|
||||||
|
};
|
||||||
|
Some(FlowAnalysisValue::Text(reg_name))
|
||||||
|
}
|
||||||
|
Some(RegisterContent::Unknown) | Some(RegisterContent::Variable) => None,
|
||||||
|
Some(value) => Some(FlowAnalysisValue::Text(value.to_string())),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
if let Some(analysis_value) = analysis_value {
|
||||||
|
analysis_result.set_argument_value_at_address(
|
||||||
|
ins_address,
|
||||||
|
arg_index as u8,
|
||||||
|
analysis_value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box::new(analysis_result)
|
||||||
|
}
|
||||||
1009
objdiff-core/src/arch/ppc/mod.rs
Normal file
1009
objdiff-core/src/arch/ppc/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
1372
objdiff-core/src/arch/superh/disasm.rs
Normal file
1372
objdiff-core/src/arch/superh/disasm.rs
Normal file
File diff suppressed because it is too large
Load Diff
810
objdiff-core/src/arch/superh/mod.rs
Normal file
810
objdiff-core/src/arch/superh/mod.rs
Normal file
@@ -0,0 +1,810 @@
|
|||||||
|
use alloc::{collections::BTreeMap, format, string::String, vec, vec::Vec};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use object::elf;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
arch::{Arch, superh::disasm::sh2_disasm},
|
||||||
|
diff::{DiffObjConfig, display::InstructionPart},
|
||||||
|
obj::{InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod disasm;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ArchSuperH {}
|
||||||
|
|
||||||
|
impl ArchSuperH {
|
||||||
|
pub fn new(_file: &object::File) -> Result<Self> { Ok(Self {}) }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DataInfo {
|
||||||
|
address: u64,
|
||||||
|
size: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arch for ArchSuperH {
|
||||||
|
fn scan_instructions_internal(
|
||||||
|
&self,
|
||||||
|
address: u64,
|
||||||
|
code: &[u8],
|
||||||
|
_section_index: usize,
|
||||||
|
_relocations: &[Relocation],
|
||||||
|
_diff_config: &DiffObjConfig,
|
||||||
|
) -> Result<Vec<InstructionRef>> {
|
||||||
|
let mut ops = Vec::<InstructionRef>::with_capacity(code.len() / 2);
|
||||||
|
let mut offset = address;
|
||||||
|
|
||||||
|
for chunk in code.chunks_exact(2) {
|
||||||
|
let opcode = u16::from_be_bytes(chunk.try_into().unwrap());
|
||||||
|
let mut parts: Vec<InstructionPart> = vec![];
|
||||||
|
let resolved: ResolvedInstructionRef = Default::default();
|
||||||
|
let mut branch_dest: Option<u64> = None;
|
||||||
|
sh2_disasm(
|
||||||
|
offset.try_into().unwrap(),
|
||||||
|
opcode,
|
||||||
|
true,
|
||||||
|
&mut parts,
|
||||||
|
&resolved,
|
||||||
|
&mut branch_dest,
|
||||||
|
);
|
||||||
|
|
||||||
|
let opcode_enum: u16 = match parts.first() {
|
||||||
|
Some(InstructionPart::Opcode(_, val)) => *val,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
ops.push(InstructionRef { address: offset, size: 2, opcode: opcode_enum, branch_dest });
|
||||||
|
offset += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn display_instruction(
|
||||||
|
&self,
|
||||||
|
resolved: ResolvedInstructionRef,
|
||||||
|
_diff_config: &DiffObjConfig,
|
||||||
|
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let opcode = u16::from_be_bytes(resolved.code.try_into().unwrap());
|
||||||
|
let mut parts: Vec<InstructionPart> = vec![];
|
||||||
|
let mut branch_dest: Option<u64> = None;
|
||||||
|
|
||||||
|
sh2_disasm(0, opcode, true, &mut parts, &resolved, &mut branch_dest);
|
||||||
|
|
||||||
|
if let Some(symbol_data) =
|
||||||
|
resolved.section.data_range(resolved.symbol.address, resolved.symbol.size as usize)
|
||||||
|
{
|
||||||
|
// scan for data
|
||||||
|
// map of instruction offsets to data target offsets
|
||||||
|
let mut data_offsets = BTreeMap::<u64, DataInfo>::new();
|
||||||
|
|
||||||
|
let mut pos: u64 = 0;
|
||||||
|
for chunk in symbol_data.chunks_exact(2) {
|
||||||
|
let opcode = u16::from_be_bytes(chunk.try_into().unwrap());
|
||||||
|
// mov.w
|
||||||
|
if (opcode & 0xf000) == 0x9000 {
|
||||||
|
let target = (opcode as u64 & 0xff) * 2 + 4 + pos;
|
||||||
|
let data_info = DataInfo { address: target, size: 2 };
|
||||||
|
data_offsets.insert(pos, data_info);
|
||||||
|
}
|
||||||
|
// mov.l
|
||||||
|
else if (opcode & 0xf000) == 0xd000 {
|
||||||
|
let target = ((opcode as u64 & 0xff) * 4 + 4 + pos) & 0xfffffffc;
|
||||||
|
let data_info = DataInfo { address: target, size: 4 };
|
||||||
|
data_offsets.insert(pos, data_info);
|
||||||
|
}
|
||||||
|
pos += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pos = resolved.ins_ref.address - resolved.symbol.address;
|
||||||
|
|
||||||
|
// add the data info
|
||||||
|
if let Some(value) = data_offsets.get(&pos) {
|
||||||
|
if value.size == 2 && value.address as usize + 1 < symbol_data.len() {
|
||||||
|
let data = u16::from_be_bytes(
|
||||||
|
symbol_data[value.address as usize..value.address as usize + 2]
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
parts.push(InstructionPart::basic(" /* "));
|
||||||
|
parts.push(InstructionPart::basic("0x"));
|
||||||
|
parts.push(InstructionPart::basic(format!("{data:04X}")));
|
||||||
|
parts.push(InstructionPart::basic(" */"));
|
||||||
|
} else if value.size == 4 && value.address as usize + 3 < symbol_data.len() {
|
||||||
|
let data = u32::from_be_bytes(
|
||||||
|
symbol_data[value.address as usize..value.address as usize + 4]
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
parts.push(InstructionPart::basic(" /* "));
|
||||||
|
parts.push(InstructionPart::basic("0x"));
|
||||||
|
parts.push(InstructionPart::basic(format!("{data:08X}")));
|
||||||
|
parts.push(InstructionPart::basic(" */"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for part in parts {
|
||||||
|
cb(part)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn demangle(&self, name: &str) -> Option<String> {
|
||||||
|
cpp_demangle::Symbol::new(name)
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
|
||||||
|
match flags {
|
||||||
|
RelocationFlags::Elf(r_type) => match r_type {
|
||||||
|
elf::R_SH_NONE => Some("R_SH_NONE"),
|
||||||
|
elf::R_SH_DIR32 => Some("R_SH_DIR32"),
|
||||||
|
elf::R_SH_REL32 => Some("R_SH_REL32"),
|
||||||
|
elf::R_SH_DIR8WPN => Some("R_SH_DIR8WPN"),
|
||||||
|
elf::R_SH_IND12W => Some("R_SH_IND12W"),
|
||||||
|
elf::R_SH_DIR8WPL => Some("R_SH_DIR8WPL"),
|
||||||
|
elf::R_SH_DIR8WPZ => Some("R_SH_DIR8WPZ"),
|
||||||
|
elf::R_SH_DIR8BP => Some("R_SH_DIR8BP"),
|
||||||
|
elf::R_SH_DIR8W => Some("R_SH_DIR8W"),
|
||||||
|
elf::R_SH_DIR8L => Some("R_SH_DIR8L"),
|
||||||
|
elf::R_SH_SWITCH16 => Some("R_SH_SWITCH16"),
|
||||||
|
elf::R_SH_SWITCH32 => Some("R_SH_SWITCH32"),
|
||||||
|
elf::R_SH_USES => Some("R_SH_USES"),
|
||||||
|
elf::R_SH_COUNT => Some("R_SH_COUNT"),
|
||||||
|
elf::R_SH_ALIGN => Some("R_SH_ALIGN"),
|
||||||
|
elf::R_SH_CODE => Some("R_SH_CODE"),
|
||||||
|
elf::R_SH_DATA => Some("R_SH_DATA"),
|
||||||
|
elf::R_SH_LABEL => Some("R_SH_LABEL"),
|
||||||
|
elf::R_SH_SWITCH8 => Some("R_SH_SWITCH8"),
|
||||||
|
elf::R_SH_GNU_VTINHERIT => Some("R_SH_GNU_VTINHERIT"),
|
||||||
|
elf::R_SH_GNU_VTENTRY => Some("R_SH_GNU_VTENTRY"),
|
||||||
|
elf::R_SH_TLS_GD_32 => Some("R_SH_TLS_GD_32"),
|
||||||
|
elf::R_SH_TLS_LD_32 => Some("R_SH_TLS_LD_32"),
|
||||||
|
elf::R_SH_TLS_LDO_32 => Some("R_SH_TLS_LDO_32"),
|
||||||
|
elf::R_SH_TLS_IE_32 => Some("R_SH_TLS_IE_32"),
|
||||||
|
elf::R_SH_TLS_LE_32 => Some("R_SH_TLS_LE_32"),
|
||||||
|
elf::R_SH_TLS_DTPMOD32 => Some("R_SH_TLS_DTPMOD32"),
|
||||||
|
elf::R_SH_TLS_DTPOFF32 => Some("R_SH_TLS_DTPOFF32"),
|
||||||
|
elf::R_SH_TLS_TPOFF32 => Some("R_SH_TLS_TPOFF32"),
|
||||||
|
elf::R_SH_GOT32 => Some("R_SH_GOT32"),
|
||||||
|
elf::R_SH_PLT32 => Some("R_SH_PLT32"),
|
||||||
|
elf::R_SH_COPY => Some("R_SH_COPY"),
|
||||||
|
elf::R_SH_GLOB_DAT => Some("R_SH_GLOB_DAT"),
|
||||||
|
elf::R_SH_JMP_SLOT => Some("R_SH_JMP_SLOT"),
|
||||||
|
elf::R_SH_RELATIVE => Some("R_SH_RELATIVE"),
|
||||||
|
elf::R_SH_GOTOFF => Some("R_SH_GOTOFF"),
|
||||||
|
elf::R_SH_GOTPC => Some("R_SH_GOTPC"),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
|
||||||
|
match flags {
|
||||||
|
RelocationFlags::Elf(elf::R_SH_DIR32) => 4,
|
||||||
|
RelocationFlags::Elf(_) => 1,
|
||||||
|
_ => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::obj::{InstructionArg, Section, SectionData, Symbol};
|
||||||
|
|
||||||
|
impl Display for InstructionPart<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
InstructionPart::Basic(s) => f.write_str(s),
|
||||||
|
InstructionPart::Opcode(s, _o) => write!(f, "{s} "),
|
||||||
|
InstructionPart::Arg(arg) => write!(f, "{arg}"),
|
||||||
|
InstructionPart::Separator => f.write_str(", "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for InstructionArg<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
InstructionArg::Value(v) => write!(f, "{v}"),
|
||||||
|
InstructionArg::BranchDest(v) => write!(f, "{v}"),
|
||||||
|
InstructionArg::Reloc => f.write_str("reloc"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sh2_display_instruction_basic_ops() {
|
||||||
|
let arch = ArchSuperH {};
|
||||||
|
let ops: [(u16, &str); 8] = [
|
||||||
|
(0x0008, "clrt "),
|
||||||
|
(0x0028, "clrmac "),
|
||||||
|
(0x0019, "div0u "),
|
||||||
|
(0x0009, "nop "),
|
||||||
|
(0x002b, "rte "),
|
||||||
|
(0x000b, "rts "),
|
||||||
|
(0x0018, "sett "),
|
||||||
|
(0x001b, "sleep "),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (opcode, expected_str) in ops {
|
||||||
|
let code = opcode.to_be_bytes();
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
|
||||||
|
arch.display_instruction(
|
||||||
|
ResolvedInstructionRef {
|
||||||
|
ins_ref: InstructionRef { address: 0x1000, size: 2, opcode, branch_dest: None },
|
||||||
|
code: &code,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&DiffObjConfig::default(),
|
||||||
|
&mut |part| {
|
||||||
|
parts.push(part.into_static());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let joined_str: String = parts.iter().map(<_>::to_string).collect();
|
||||||
|
assert_eq!(joined_str, expected_str.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sh2_display_instruction_f0ff_ops() {
|
||||||
|
let arch = ArchSuperH {};
|
||||||
|
let ops: [(u16, &str); 49] = [
|
||||||
|
(0x4015, "cmp/pl r0"),
|
||||||
|
(0x4115, "cmp/pl r1"),
|
||||||
|
(0x4215, "cmp/pl r2"),
|
||||||
|
(0x4315, "cmp/pl r3"),
|
||||||
|
(0x4011, "cmp/pz r0"),
|
||||||
|
(0x4010, "dt r0"),
|
||||||
|
(0x0029, "movt r0"),
|
||||||
|
(0x4004, "rotl r0"),
|
||||||
|
(0x4005, "rotr r0"),
|
||||||
|
(0x4024, "rotcl r0"),
|
||||||
|
(0x4025, "rotcr r0"),
|
||||||
|
(0x4020, "shal r0"),
|
||||||
|
(0x4021, "shar r0"),
|
||||||
|
(0x4000, "shll r0"),
|
||||||
|
(0x4001, "shlr r0"),
|
||||||
|
(0x4008, "shll2 r0"),
|
||||||
|
(0x4009, "shlr2 r0"),
|
||||||
|
(0x4018, "shll8 r0"),
|
||||||
|
(0x4019, "shlr8 r0"),
|
||||||
|
(0x4028, "shll16 r0"),
|
||||||
|
(0x4029, "shlr16 r0"),
|
||||||
|
(0x0002, "stc sr, r0"),
|
||||||
|
(0x0012, "stc gbr, r0"),
|
||||||
|
(0x0022, "stc vbr, r0"),
|
||||||
|
(0x000a, "sts mach, r0"),
|
||||||
|
(0x001a, "sts macl, r0"),
|
||||||
|
(0x402a, "lds r0, pr"),
|
||||||
|
(0x401b, "tas.b r0"),
|
||||||
|
(0x4003, "stc.l sr, @-r0"),
|
||||||
|
(0x4013, "stc.l gbr, @-r0"),
|
||||||
|
(0x4023, "stc.l vbr, @-r0"),
|
||||||
|
(0x4002, "sts.l mach, @-r0"),
|
||||||
|
(0x4012, "sts.l macl, @-r0"),
|
||||||
|
(0x4022, "sts.l pr, @-r0"),
|
||||||
|
(0x400e, "ldc r0, sr"),
|
||||||
|
(0x401e, "ldc r0, gbr"),
|
||||||
|
(0x402e, "ldc r0, vbr"),
|
||||||
|
(0x400a, "lds r0, mach"),
|
||||||
|
(0x401a, "lds r0, macl"),
|
||||||
|
(0x402b, "jmp @r0"),
|
||||||
|
(0x400b, "jsr @r0"),
|
||||||
|
(0x4007, "ldc.l @r0+, sr"),
|
||||||
|
(0x4017, "ldc.l @r0+, gbr"),
|
||||||
|
(0x4027, "ldc.l @r0+, vbr"),
|
||||||
|
(0x4006, "lds.l @r0+, mach"),
|
||||||
|
(0x4016, "lds.l @r0+, macl"),
|
||||||
|
(0x4026, "lds.l @r0+, pr"),
|
||||||
|
(0x0023, "braf r0"),
|
||||||
|
(0x0003, "bsrf r0"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (opcode, expected_str) in ops {
|
||||||
|
let code = opcode.to_be_bytes();
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
|
||||||
|
arch.display_instruction(
|
||||||
|
ResolvedInstructionRef {
|
||||||
|
ins_ref: InstructionRef { address: 0x1000, size: 2, opcode, branch_dest: None },
|
||||||
|
code: &code,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&DiffObjConfig::default(),
|
||||||
|
&mut |part| {
|
||||||
|
parts.push(part.into_static());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let joined_str: String = parts.iter().map(<_>::to_string).collect();
|
||||||
|
assert_eq!(joined_str, expected_str.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sh2_display_instructions_f00f() {
|
||||||
|
let arch = ArchSuperH {};
|
||||||
|
let ops: [(u16, &str); 54] = [
|
||||||
|
(0x300c, "add r0, r0"),
|
||||||
|
(0x300e, "addc r0, r0"),
|
||||||
|
(0x300f, "addv r0, r0"),
|
||||||
|
(0x2009, "and r0, r0"),
|
||||||
|
(0x3000, "cmp/eq r0, r0"),
|
||||||
|
(0x3002, "cmp/hs r0, r0"),
|
||||||
|
(0x3003, "cmp/ge r0, r0"),
|
||||||
|
(0x3006, "cmp/hi r0, r0"),
|
||||||
|
(0x3007, "cmp/gt r0, r0"),
|
||||||
|
(0x200c, "cmp/str r0, r0"),
|
||||||
|
(0x3004, "div1 r0, r0"),
|
||||||
|
(0x2007, "div0s r0, r0"),
|
||||||
|
(0x300d, "dmuls.l r0, r0"),
|
||||||
|
(0x3005, "dmulu.l r0, r0"),
|
||||||
|
(0x600e, "exts.b r0, r0"),
|
||||||
|
(0x600f, "exts.w r0, r0"),
|
||||||
|
(0x600c, "extu.b r0, r0"),
|
||||||
|
(0x600d, "extu.w r0, r0"),
|
||||||
|
(0x6003, "mov r0, r0"),
|
||||||
|
(0x0007, "mul.l r0, r0"),
|
||||||
|
(0x200f, "muls r0, r0"),
|
||||||
|
(0x200e, "mulu r0, r0"),
|
||||||
|
(0x600b, "neg r0, r0"),
|
||||||
|
(0x600a, "negc r0, r0"),
|
||||||
|
(0x6007, "not r0, r0"),
|
||||||
|
(0x200b, "or r0, r0"),
|
||||||
|
(0x3008, "sub r0, r0"),
|
||||||
|
(0x300a, "subc r0, r0"),
|
||||||
|
(0x300b, "subv r0, r0"),
|
||||||
|
(0x6008, "swap.b r0, r0"),
|
||||||
|
(0x6009, "swap.w r0, r0"),
|
||||||
|
(0x2008, "tst r0, r0"),
|
||||||
|
(0x200a, "xor r0, r0"),
|
||||||
|
(0x200d, "xtrct r0, r0"),
|
||||||
|
(0x2000, "mov.b r0, @r0"),
|
||||||
|
(0x2001, "mov.w r0, @r0"),
|
||||||
|
(0x2002, "mov.l r0, @r0"),
|
||||||
|
(0x6000, "mov.b @r0, r0"),
|
||||||
|
(0x6001, "mov.w @r0, r0"),
|
||||||
|
(0x6002, "mov.l @r0, r0"),
|
||||||
|
(0x000f, "mac.l @r0+, @r0+"),
|
||||||
|
(0x400f, "mac.w @r0+, @r0+"),
|
||||||
|
(0x6004, "mov.b @r0+, r0"),
|
||||||
|
(0x6005, "mov.w @r0+, r0"),
|
||||||
|
(0x6006, "mov.l @r0+, r0"),
|
||||||
|
(0x2004, "mov.b r0, @-r0"),
|
||||||
|
(0x2005, "mov.w r0, @-r0"),
|
||||||
|
(0x2006, "mov.l r0, @-r0"),
|
||||||
|
(0x0004, "mov.b r0, @(r0, r0)"),
|
||||||
|
(0x0005, "mov.w r0, @(r0, r0)"),
|
||||||
|
(0x0006, "mov.l r0, @(r0, r0)"),
|
||||||
|
(0x000c, "mov.b @(r0, r0), r0"),
|
||||||
|
(0x000d, "mov.w @(r0, r0), r0"),
|
||||||
|
(0x000e, "mov.l @(r0, r0), r0"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (opcode, expected_str) in ops {
|
||||||
|
let code = opcode.to_be_bytes();
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
|
||||||
|
arch.display_instruction(
|
||||||
|
ResolvedInstructionRef {
|
||||||
|
ins_ref: InstructionRef { address: 0x1000, size: 2, opcode, branch_dest: None },
|
||||||
|
code: &code,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&DiffObjConfig::default(),
|
||||||
|
&mut |part| {
|
||||||
|
parts.push(part.into_static());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let joined_str: String = parts.iter().map(<_>::to_string).collect();
|
||||||
|
assert_eq!(joined_str, expected_str.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sh2_display_instruction_mov_immediate_offset() {
|
||||||
|
let arch = ArchSuperH {};
|
||||||
|
let ops: [(u16, &str); 8] = [
|
||||||
|
(0x8000, "mov.b r0, @(0x0, r0)"),
|
||||||
|
(0x8011, "mov.b r0, @(0x1, r1)"),
|
||||||
|
(0x8102, "mov.w r0, @(0x4, r0)"),
|
||||||
|
(0x8113, "mov.w r0, @(0x6, r1)"),
|
||||||
|
(0x8404, "mov.b @(0x4, r0), r0"),
|
||||||
|
(0x8415, "mov.b @(0x5, r1), r0"),
|
||||||
|
(0x8506, "mov.w @(0xc, r0), r0"),
|
||||||
|
(0x8517, "mov.w @(0xe, r1), r0"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (opcode, expected_str) in ops {
|
||||||
|
let code = opcode.to_be_bytes();
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
|
||||||
|
arch.display_instruction(
|
||||||
|
ResolvedInstructionRef {
|
||||||
|
ins_ref: InstructionRef { address: 0x1000, size: 2, opcode, branch_dest: None },
|
||||||
|
code: &code,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&DiffObjConfig::default(),
|
||||||
|
&mut |part| {
|
||||||
|
parts.push(part.into_static());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let joined_str: String = parts.iter().map(<_>::to_string).collect();
|
||||||
|
assert_eq!(joined_str, expected_str.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sh2_display_instruction_gbr_and_branches() {
|
||||||
|
let arch = ArchSuperH {};
|
||||||
|
let ops: &[(u16, u32, &str)] = &[
|
||||||
|
(0xc000, 0x0000, "mov.b r0, @(0x0, gbr)"),
|
||||||
|
(0xc07f, 0x0000, "mov.b r0, @(0x7f, gbr)"),
|
||||||
|
(0xc100, 0x0000, "mov.w r0, @(0x0, gbr)"),
|
||||||
|
(0xc17f, 0x0000, "mov.w r0, @(0xfe, gbr)"),
|
||||||
|
(0xc200, 0x0000, "mov.l r0, @(0x0, gbr)"),
|
||||||
|
(0xc27f, 0x0000, "mov.l r0, @(0x1fc, gbr)"),
|
||||||
|
(0xc400, 0x0000, "mov.b @(0x0, gbr), r0"),
|
||||||
|
(0xc47f, 0x0000, "mov.b @(0x7f, gbr), r0"),
|
||||||
|
(0xc500, 0x0000, "mov.w @(0x0, gbr), r0"),
|
||||||
|
(0xc57f, 0x0000, "mov.w @(0xfe, gbr), r0"),
|
||||||
|
(0xc600, 0x0000, "mov.l @(0x0, gbr), r0"),
|
||||||
|
(0xc67f, 0x0000, "mov.l @(0x1fc, gbr), r0"),
|
||||||
|
(0x8b20, 0x1000, "bf 0x44"),
|
||||||
|
(0x8b80, 0x1000, "bf 0xffffff04"),
|
||||||
|
(0x8f10, 0x2000, "bf.s 0x24"),
|
||||||
|
(0x8f90, 0x2000, "bf.s 0xffffff24"),
|
||||||
|
(0x8904, 0x3000, "bt 0xc"),
|
||||||
|
(0x8980, 0x3000, "bt 0xffffff04"),
|
||||||
|
(0x8d04, 0x4000, "bt.s 0xc"),
|
||||||
|
(0x8d80, 0x4000, "bt.s 0xffffff04"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for &(opcode, addr, expected_str) in ops {
|
||||||
|
let code = opcode.to_be_bytes();
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
|
||||||
|
arch.display_instruction(
|
||||||
|
ResolvedInstructionRef {
|
||||||
|
ins_ref: InstructionRef {
|
||||||
|
address: addr as u64,
|
||||||
|
size: 2,
|
||||||
|
opcode,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
code: &code,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&DiffObjConfig::default(),
|
||||||
|
&mut |part| {
|
||||||
|
parts.push(part.into_static());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let joined_str: String = parts.iter().map(<_>::to_string).collect();
|
||||||
|
assert_eq!(joined_str, expected_str.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sh2_display_instruction_mov_l() {
|
||||||
|
let arch = ArchSuperH {};
|
||||||
|
let ops: &[(u16, u32, &str)] = &[
|
||||||
|
// mov.l rX, @(0xXXX, rY)
|
||||||
|
(0x1000, 0x0000, "mov.l r0, @(0x0, r0)"),
|
||||||
|
(0x1001, 0x0000, "mov.l r0, @(0x4, r0)"),
|
||||||
|
(0x100f, 0x0000, "mov.l r0, @(0x3c, r0)"),
|
||||||
|
(0x101f, 0x0000, "mov.l r1, @(0x3c, r0)"),
|
||||||
|
// mov.l @(0xXXX, rY), rX
|
||||||
|
(0x5000, 0x0000, "mov.l @(0x0, r0), r0"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for &(opcode, addr, expected_str) in ops {
|
||||||
|
let code = opcode.to_be_bytes();
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
|
||||||
|
arch.display_instruction(
|
||||||
|
ResolvedInstructionRef {
|
||||||
|
ins_ref: InstructionRef {
|
||||||
|
address: addr as u64,
|
||||||
|
size: 2,
|
||||||
|
opcode,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
code: &code,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&DiffObjConfig::default(),
|
||||||
|
&mut |part| {
|
||||||
|
parts.push(part.into_static());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let joined_str: String = parts.iter().map(<_>::to_string).collect();
|
||||||
|
assert_eq!(joined_str, expected_str.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sh2_display_instruction_bra_bsr() {
|
||||||
|
let arch: ArchSuperH = ArchSuperH {};
|
||||||
|
let ops: &[(u16, u32, &str)] = &[
|
||||||
|
// bra
|
||||||
|
(0xa000, 0x0000, "bra 0x4"),
|
||||||
|
(0xa001, 0x0000, "bra 0x6"),
|
||||||
|
(0xa800, 0x0000, "bra 0xfffff004"),
|
||||||
|
(0xa801, 0x0000, "bra 0xfffff006"),
|
||||||
|
// bsr
|
||||||
|
(0xb000, 0x0000, "bsr 0x4"),
|
||||||
|
(0xb001, 0x0000, "bsr 0x6"),
|
||||||
|
(0xb800, 0x0000, "bsr 0xfffff004"),
|
||||||
|
(0xb801, 0x0000, "bsr 0xfffff006"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for &(opcode, addr, expected_str) in ops {
|
||||||
|
let code = opcode.to_be_bytes();
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
|
||||||
|
arch.display_instruction(
|
||||||
|
ResolvedInstructionRef {
|
||||||
|
ins_ref: InstructionRef {
|
||||||
|
address: addr as u64,
|
||||||
|
size: 2,
|
||||||
|
opcode,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
code: &code,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&DiffObjConfig::default(),
|
||||||
|
&mut |part| {
|
||||||
|
parts.push(part.into_static());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let joined_str: String = parts.iter().map(<_>::to_string).collect();
|
||||||
|
assert_eq!(joined_str, expected_str.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sh2_display_instruction_operations() {
|
||||||
|
let arch = ArchSuperH {};
|
||||||
|
let ops: &[(u16, u32, &str)] = &[
|
||||||
|
(0xcdff, 0x0000, "and.b #0xff, @(r0, gbr)"),
|
||||||
|
(0xcfff, 0x0000, "or.b #0xff, @(r0, gbr)"),
|
||||||
|
(0xccff, 0x0000, "tst.b #0xff, @(r0, gbr)"),
|
||||||
|
(0xceff, 0x0000, "xor.b #0xff, @(r0, gbr)"),
|
||||||
|
(0xc9ff, 0x0000, "and #0xff, r0"),
|
||||||
|
(0x88ff, 0x0000, "cmp/eq #0xff, r0"),
|
||||||
|
(0xcbff, 0x0000, "or #0xff, r0"),
|
||||||
|
(0xc8ff, 0x0000, "tst #0xff, r0"),
|
||||||
|
(0xcaff, 0x0000, "xor #0xff, r0"),
|
||||||
|
(0xc3ff, 0x0000, "trapa #0xff"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for &(opcode, addr, expected_str) in ops {
|
||||||
|
let code = opcode.to_be_bytes();
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
|
||||||
|
arch.display_instruction(
|
||||||
|
ResolvedInstructionRef {
|
||||||
|
ins_ref: InstructionRef {
|
||||||
|
address: addr as u64,
|
||||||
|
size: 2,
|
||||||
|
opcode,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
code: &code,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&DiffObjConfig::default(),
|
||||||
|
&mut |part| {
|
||||||
|
parts.push(part.into_static());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let joined_str: String = parts.iter().map(<_>::to_string).collect();
|
||||||
|
assert_eq!(joined_str, expected_str.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sh2_add_mov_unknown_instructions() {
|
||||||
|
let arch = ArchSuperH {};
|
||||||
|
let ops: &[(u16, u32, &str)] = &[
|
||||||
|
(0x70FF, 0x0000, "add #0xff, r0"),
|
||||||
|
(0xE0FF, 0x0000, "mov #0xff, r0"),
|
||||||
|
(0x0000, 0x0000, ".word 0x0000 /* unknown instruction */"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for &(opcode, addr, expected_str) in ops {
|
||||||
|
let code = opcode.to_be_bytes();
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
|
||||||
|
arch.display_instruction(
|
||||||
|
ResolvedInstructionRef {
|
||||||
|
ins_ref: InstructionRef {
|
||||||
|
address: addr as u64,
|
||||||
|
size: 2,
|
||||||
|
opcode,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
code: &code,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&DiffObjConfig::default(),
|
||||||
|
&mut |part| {
|
||||||
|
parts.push(part.into_static());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let joined_str: String = parts.iter().map(<_>::to_string).collect();
|
||||||
|
assert_eq!(joined_str, expected_str.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sh2_mov_instructions_with_labels() {
|
||||||
|
let arch = ArchSuperH {};
|
||||||
|
let ops: &[(u16, u32, &str)] =
|
||||||
|
&[(0x9000, 0x0000, "mov.w @(0x4, pc), r0"), (0xd000, 0x0000, "mov.l @(0x4, pc), r0")];
|
||||||
|
|
||||||
|
for &(opcode, addr, expected_str) in ops {
|
||||||
|
let code = opcode.to_be_bytes();
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
|
||||||
|
arch.display_instruction(
|
||||||
|
ResolvedInstructionRef {
|
||||||
|
ins_ref: InstructionRef {
|
||||||
|
address: addr as u64,
|
||||||
|
size: 2,
|
||||||
|
opcode,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
code: &code,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&DiffObjConfig::default(),
|
||||||
|
&mut |part| {
|
||||||
|
parts.push(part.into_static());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let joined_str: String = parts.iter().map(<_>::to_string).collect();
|
||||||
|
assert_eq!(joined_str, expected_str.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_func_0606_f378_mov_w_data_labeling() {
|
||||||
|
let arch = ArchSuperH {};
|
||||||
|
let ops: &[(u16, u32, &str)] = &[(0x9000, 0x0606F378, "mov.w @(0x4, pc), r0 /* 0x00B0 */")];
|
||||||
|
|
||||||
|
let mut code = Vec::new();
|
||||||
|
code.extend_from_slice(&0x9000_u16.to_be_bytes());
|
||||||
|
code.extend_from_slice(&0x0009_u16.to_be_bytes());
|
||||||
|
code.extend_from_slice(&0x00B0_u16.to_be_bytes());
|
||||||
|
|
||||||
|
for &(opcode, addr, expected_str) in ops {
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
|
||||||
|
arch.display_instruction(
|
||||||
|
ResolvedInstructionRef {
|
||||||
|
ins_ref: InstructionRef {
|
||||||
|
address: addr as u64,
|
||||||
|
size: 2,
|
||||||
|
opcode,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
code: &opcode.to_be_bytes(),
|
||||||
|
symbol: &Symbol {
|
||||||
|
address: 0x0606F378, // func base address
|
||||||
|
size: code.len() as u64,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
section: &Section {
|
||||||
|
address: 0x0606F378,
|
||||||
|
size: code.len() as u64,
|
||||||
|
data: SectionData(code.clone()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&DiffObjConfig::default(),
|
||||||
|
&mut |part| {
|
||||||
|
parts.push(part.into_static());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let joined_str: String = parts.iter().map(<_>::to_string).collect();
|
||||||
|
assert_eq!(joined_str, expected_str.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_func_0606_f378_mov_l_data_labeling() {
|
||||||
|
let arch = ArchSuperH {};
|
||||||
|
let ops: &[(u16, u32, &str)] =
|
||||||
|
&[(0xd000, 0x0606F378, "mov.l @(0x4, pc), r0 /* 0x00B000B0 */")];
|
||||||
|
|
||||||
|
let mut code = Vec::new();
|
||||||
|
code.extend_from_slice(&0xd000_u16.to_be_bytes());
|
||||||
|
code.extend_from_slice(&0x0009_u16.to_be_bytes());
|
||||||
|
code.extend_from_slice(&0x00B0_u16.to_be_bytes());
|
||||||
|
code.extend_from_slice(&0x00B0_u16.to_be_bytes());
|
||||||
|
|
||||||
|
for &(opcode, addr, expected_str) in ops {
|
||||||
|
let mut parts = Vec::new();
|
||||||
|
|
||||||
|
arch.display_instruction(
|
||||||
|
ResolvedInstructionRef {
|
||||||
|
ins_ref: InstructionRef {
|
||||||
|
address: addr as u64,
|
||||||
|
size: 2,
|
||||||
|
opcode,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
code: &opcode.to_be_bytes(),
|
||||||
|
symbol: &Symbol {
|
||||||
|
address: 0x0606F378, // func base address
|
||||||
|
size: code.len() as u64,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
section: &Section {
|
||||||
|
address: 0x0606F378,
|
||||||
|
size: code.len() as u64,
|
||||||
|
data: SectionData(code.clone()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&DiffObjConfig::default(),
|
||||||
|
&mut |part| {
|
||||||
|
parts.push(part.into_static());
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let joined_str: String = parts.iter().map(<_>::to_string).collect();
|
||||||
|
assert_eq!(joined_str, expected_str.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,246 +0,0 @@
|
|||||||
#![allow(clippy::needless_lifetimes)] // Generated serde code
|
|
||||||
use crate::{
|
|
||||||
diff::{
|
|
||||||
ObjDataDiff, ObjDataDiffKind, ObjDiff, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo,
|
|
||||||
ObjInsDiff, ObjInsDiffKind, ObjSectionDiff, ObjSymbolDiff,
|
|
||||||
},
|
|
||||||
obj::{
|
|
||||||
ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSectionKind, ObjSymbol,
|
|
||||||
ObjSymbolFlagSet, ObjSymbolFlags,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Protobuf diff types
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.rs"));
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.serde.rs"));
|
|
||||||
|
|
||||||
impl DiffResult {
|
|
||||||
pub fn new(left: Option<(&ObjInfo, &ObjDiff)>, right: Option<(&ObjInfo, &ObjDiff)>) -> Self {
|
|
||||||
Self {
|
|
||||||
left: left.map(|(obj, diff)| ObjectDiff::new(obj, diff)),
|
|
||||||
right: right.map(|(obj, diff)| ObjectDiff::new(obj, diff)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ObjectDiff {
|
|
||||||
pub fn new(obj: &ObjInfo, diff: &ObjDiff) -> Self {
|
|
||||||
Self {
|
|
||||||
sections: diff
|
|
||||||
.sections
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, d)| SectionDiff::new(obj, i, d))
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SectionDiff {
|
|
||||||
pub fn new(obj: &ObjInfo, section_index: usize, section_diff: &ObjSectionDiff) -> Self {
|
|
||||||
let section = &obj.sections[section_index];
|
|
||||||
let functions = section_diff.symbols.iter().map(|d| FunctionDiff::new(obj, d)).collect();
|
|
||||||
let data = section_diff.data_diff.iter().map(|d| DataDiff::new(obj, d)).collect();
|
|
||||||
Self {
|
|
||||||
name: section.name.to_string(),
|
|
||||||
kind: SectionKind::from(section.kind) as i32,
|
|
||||||
size: section.size,
|
|
||||||
address: section.address,
|
|
||||||
functions,
|
|
||||||
data,
|
|
||||||
match_percent: section_diff.match_percent,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ObjSectionKind> for SectionKind {
|
|
||||||
fn from(value: ObjSectionKind) -> Self {
|
|
||||||
match value {
|
|
||||||
ObjSectionKind::Code => SectionKind::SectionText,
|
|
||||||
ObjSectionKind::Data => SectionKind::SectionData,
|
|
||||||
ObjSectionKind::Bss => SectionKind::SectionBss,
|
|
||||||
// TODO common
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FunctionDiff {
|
|
||||||
pub fn new(object: &ObjInfo, symbol_diff: &ObjSymbolDiff) -> Self {
|
|
||||||
let (_section, symbol) = object.section_symbol(symbol_diff.symbol_ref);
|
|
||||||
// let diff_symbol = symbol_diff.diff_symbol.map(|symbol_ref| {
|
|
||||||
// let (_section, symbol) = object.section_symbol(symbol_ref);
|
|
||||||
// Symbol::from(symbol)
|
|
||||||
// });
|
|
||||||
let instructions = symbol_diff
|
|
||||||
.instructions
|
|
||||||
.iter()
|
|
||||||
.map(|ins_diff| InstructionDiff::new(object, ins_diff))
|
|
||||||
.collect();
|
|
||||||
Self {
|
|
||||||
symbol: Some(Symbol::new(symbol)),
|
|
||||||
// diff_symbol,
|
|
||||||
instructions,
|
|
||||||
match_percent: symbol_diff.match_percent,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DataDiff {
|
|
||||||
pub fn new(_object: &ObjInfo, data_diff: &ObjDataDiff) -> Self {
|
|
||||||
Self {
|
|
||||||
kind: DiffKind::from(data_diff.kind) as i32,
|
|
||||||
data: data_diff.data.clone(),
|
|
||||||
size: data_diff.len as u64,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Symbol {
|
|
||||||
pub fn new(value: &ObjSymbol) -> Self {
|
|
||||||
Self {
|
|
||||||
name: value.name.to_string(),
|
|
||||||
demangled_name: value.demangled_name.clone(),
|
|
||||||
address: value.address,
|
|
||||||
size: value.size,
|
|
||||||
flags: symbol_flags(value.flags),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn symbol_flags(value: ObjSymbolFlagSet) -> u32 {
|
|
||||||
let mut flags = 0u32;
|
|
||||||
if value.0.contains(ObjSymbolFlags::Global) {
|
|
||||||
flags |= SymbolFlag::SymbolNone as u32;
|
|
||||||
}
|
|
||||||
if value.0.contains(ObjSymbolFlags::Local) {
|
|
||||||
flags |= SymbolFlag::SymbolLocal as u32;
|
|
||||||
}
|
|
||||||
if value.0.contains(ObjSymbolFlags::Weak) {
|
|
||||||
flags |= SymbolFlag::SymbolWeak as u32;
|
|
||||||
}
|
|
||||||
if value.0.contains(ObjSymbolFlags::Common) {
|
|
||||||
flags |= SymbolFlag::SymbolCommon as u32;
|
|
||||||
}
|
|
||||||
if value.0.contains(ObjSymbolFlags::Hidden) {
|
|
||||||
flags |= SymbolFlag::SymbolHidden as u32;
|
|
||||||
}
|
|
||||||
flags
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Instruction {
|
|
||||||
pub fn new(object: &ObjInfo, instruction: &ObjIns) -> Self {
|
|
||||||
Self {
|
|
||||||
address: instruction.address,
|
|
||||||
size: instruction.size as u32,
|
|
||||||
opcode: instruction.op as u32,
|
|
||||||
mnemonic: instruction.mnemonic.to_string(),
|
|
||||||
formatted: instruction.formatted.clone(),
|
|
||||||
arguments: instruction.args.iter().map(Argument::new).collect(),
|
|
||||||
relocation: instruction.reloc.as_ref().map(|reloc| Relocation::new(object, reloc)),
|
|
||||||
branch_dest: instruction.branch_dest,
|
|
||||||
line_number: instruction.line,
|
|
||||||
original: instruction.orig.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Argument {
|
|
||||||
pub fn new(value: &ObjInsArg) -> Self {
|
|
||||||
Self {
|
|
||||||
value: Some(match value {
|
|
||||||
ObjInsArg::PlainText(s) => argument::Value::PlainText(s.to_string()),
|
|
||||||
ObjInsArg::Arg(v) => argument::Value::Argument(ArgumentValue::new(v)),
|
|
||||||
ObjInsArg::Reloc => argument::Value::Relocation(ArgumentRelocation {}),
|
|
||||||
ObjInsArg::BranchDest(dest) => argument::Value::BranchDest(*dest),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArgumentValue {
|
|
||||||
pub fn new(value: &ObjInsArgValue) -> Self {
|
|
||||||
Self {
|
|
||||||
value: Some(match value {
|
|
||||||
ObjInsArgValue::Signed(v) => argument_value::Value::Signed(*v),
|
|
||||||
ObjInsArgValue::Unsigned(v) => argument_value::Value::Unsigned(*v),
|
|
||||||
ObjInsArgValue::Opaque(v) => argument_value::Value::Opaque(v.to_string()),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Relocation {
|
|
||||||
pub fn new(object: &ObjInfo, reloc: &ObjReloc) -> Self {
|
|
||||||
Self {
|
|
||||||
r#type: match reloc.flags {
|
|
||||||
object::RelocationFlags::Elf { r_type } => r_type,
|
|
||||||
object::RelocationFlags::MachO { r_type, .. } => r_type as u32,
|
|
||||||
object::RelocationFlags::Coff { typ } => typ as u32,
|
|
||||||
object::RelocationFlags::Xcoff { r_rtype, .. } => r_rtype as u32,
|
|
||||||
_ => unreachable!(),
|
|
||||||
},
|
|
||||||
type_name: object.arch.display_reloc(reloc.flags).into_owned(),
|
|
||||||
target: Some(RelocationTarget {
|
|
||||||
symbol: Some(Symbol::new(&reloc.target)),
|
|
||||||
addend: reloc.addend,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InstructionDiff {
|
|
||||||
pub fn new(object: &ObjInfo, instruction_diff: &ObjInsDiff) -> Self {
|
|
||||||
Self {
|
|
||||||
instruction: instruction_diff.ins.as_ref().map(|ins| Instruction::new(object, ins)),
|
|
||||||
diff_kind: DiffKind::from(instruction_diff.kind) as i32,
|
|
||||||
branch_from: instruction_diff.branch_from.as_ref().map(InstructionBranchFrom::new),
|
|
||||||
branch_to: instruction_diff.branch_to.as_ref().map(InstructionBranchTo::new),
|
|
||||||
arg_diff: instruction_diff.arg_diff.iter().map(ArgumentDiff::new).collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ArgumentDiff {
|
|
||||||
pub fn new(value: &Option<ObjInsArgDiff>) -> Self {
|
|
||||||
Self { diff_index: value.as_ref().map(|v| v.idx as u32) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ObjInsDiffKind> for DiffKind {
|
|
||||||
fn from(value: ObjInsDiffKind) -> Self {
|
|
||||||
match value {
|
|
||||||
ObjInsDiffKind::None => DiffKind::DiffNone,
|
|
||||||
ObjInsDiffKind::OpMismatch => DiffKind::DiffOpMismatch,
|
|
||||||
ObjInsDiffKind::ArgMismatch => DiffKind::DiffArgMismatch,
|
|
||||||
ObjInsDiffKind::Replace => DiffKind::DiffReplace,
|
|
||||||
ObjInsDiffKind::Delete => DiffKind::DiffDelete,
|
|
||||||
ObjInsDiffKind::Insert => DiffKind::DiffInsert,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ObjDataDiffKind> for DiffKind {
|
|
||||||
fn from(value: ObjDataDiffKind) -> Self {
|
|
||||||
match value {
|
|
||||||
ObjDataDiffKind::None => DiffKind::DiffNone,
|
|
||||||
ObjDataDiffKind::Replace => DiffKind::DiffReplace,
|
|
||||||
ObjDataDiffKind::Delete => DiffKind::DiffDelete,
|
|
||||||
ObjDataDiffKind::Insert => DiffKind::DiffInsert,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InstructionBranchFrom {
|
|
||||||
pub fn new(value: &ObjInsBranchFrom) -> Self {
|
|
||||||
Self {
|
|
||||||
instruction_index: value.ins_idx.iter().map(|&x| x as u32).collect(),
|
|
||||||
branch_index: value.branch_idx as u32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InstructionBranchTo {
|
|
||||||
pub fn new(value: &ObjInsBranchTo) -> Self {
|
|
||||||
Self { instruction_index: value.ins_idx as u32, branch_index: value.branch_idx as u32 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1 @@
|
|||||||
#[cfg(feature = "any-arch")]
|
|
||||||
pub mod diff;
|
|
||||||
pub mod report;
|
pub mod report;
|
||||||
#[cfg(feature = "wasm")]
|
|
||||||
pub mod wasm;
|
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
#![allow(clippy::needless_lifetimes)] // Generated serde code
|
#![allow(clippy::needless_lifetimes)] // Generated serde code
|
||||||
use std::ops::AddAssign;
|
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use alloc::{
|
||||||
|
string::{String, ToString},
|
||||||
|
vec,
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
use core::ops::AddAssign;
|
||||||
|
|
||||||
|
use anyhow::{Result, bail};
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use serde_json::error::Category;
|
|
||||||
|
|
||||||
// Protobuf report types
|
// Protobuf report types
|
||||||
include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs"));
|
include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs"));
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs"));
|
include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs"));
|
||||||
|
|
||||||
pub const REPORT_VERSION: u32 = 2;
|
pub const REPORT_VERSION: u32 = 2;
|
||||||
@@ -15,23 +21,30 @@ impl Report {
|
|||||||
/// Attempts to parse the report as binary protobuf or JSON.
|
/// Attempts to parse the report as binary protobuf or JSON.
|
||||||
pub fn parse(data: &[u8]) -> Result<Self> {
|
pub fn parse(data: &[u8]) -> Result<Self> {
|
||||||
if data.is_empty() {
|
if data.is_empty() {
|
||||||
bail!(std::io::Error::from(std::io::ErrorKind::UnexpectedEof));
|
bail!("Empty data");
|
||||||
}
|
}
|
||||||
let report = if data[0] == b'{' {
|
let report = if data[0] == b'{' {
|
||||||
// Load as JSON
|
// Load as JSON
|
||||||
Self::from_json(data)?
|
#[cfg(feature = "serde")]
|
||||||
|
{
|
||||||
|
Self::from_json(data)?
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "serde"))]
|
||||||
|
bail!("JSON report parsing requires the `serde` feature")
|
||||||
} else {
|
} else {
|
||||||
// Load as binary protobuf
|
// Load as binary protobuf
|
||||||
Self::decode(data)?
|
Self::decode(data).map_err(|e| anyhow::Error::msg(e.to_string()))?
|
||||||
};
|
};
|
||||||
Ok(report)
|
Ok(report)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
/// Attempts to parse the report as JSON, migrating from the legacy report format if necessary.
|
/// Attempts to parse the report as JSON, migrating from the legacy report format if necessary.
|
||||||
fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
|
fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
|
||||||
match serde_json::from_slice::<Self>(bytes) {
|
match serde_json::from_slice::<Self>(bytes) {
|
||||||
Ok(report) => Ok(report),
|
Ok(report) => Ok(report),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
use serde_json::error::Category;
|
||||||
match e.classify() {
|
match e.classify() {
|
||||||
Category::Io | Category::Eof | Category::Syntax => Err(e),
|
Category::Io | Category::Eof | Category::Syntax => Err(e),
|
||||||
Category::Data => {
|
Category::Data => {
|
||||||
@@ -304,7 +317,8 @@ impl FromIterator<Measures> for Measures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Older JSON report types
|
// Older JSON report types
|
||||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
struct LegacyReport {
|
struct LegacyReport {
|
||||||
fuzzy_match_percent: f32,
|
fuzzy_match_percent: f32,
|
||||||
total_code: u64,
|
total_code: u64,
|
||||||
@@ -341,7 +355,8 @@ impl From<LegacyReport> for Report {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
struct LegacyReportUnit {
|
struct LegacyReportUnit {
|
||||||
name: String,
|
name: String,
|
||||||
fuzzy_match_percent: f32,
|
fuzzy_match_percent: f32,
|
||||||
@@ -351,11 +366,11 @@ struct LegacyReportUnit {
|
|||||||
matched_data: u64,
|
matched_data: u64,
|
||||||
total_functions: u32,
|
total_functions: u32,
|
||||||
matched_functions: u32,
|
matched_functions: u32,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
complete: Option<bool>,
|
complete: Option<bool>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
module_name: Option<String>,
|
module_name: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
module_id: Option<u32>,
|
module_id: Option<u32>,
|
||||||
sections: Vec<LegacyReportItem>,
|
sections: Vec<LegacyReportItem>,
|
||||||
functions: Vec<LegacyReportItem>,
|
functions: Vec<LegacyReportItem>,
|
||||||
@@ -389,16 +404,20 @@ impl From<LegacyReportUnit> for ReportUnit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
struct LegacyReportItem {
|
struct LegacyReportItem {
|
||||||
name: String,
|
name: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
demangled_name: Option<String>,
|
demangled_name: Option<String>,
|
||||||
#[serde(
|
#[cfg_attr(
|
||||||
default,
|
feature = "serde",
|
||||||
skip_serializing_if = "Option::is_none",
|
serde(
|
||||||
serialize_with = "serialize_hex",
|
default,
|
||||||
deserialize_with = "deserialize_hex"
|
skip_serializing_if = "Option::is_none",
|
||||||
|
serialize_with = "serialize_hex",
|
||||||
|
deserialize_with = "deserialize_hex"
|
||||||
|
)
|
||||||
)]
|
)]
|
||||||
address: Option<u64>,
|
address: Option<u64>,
|
||||||
size: u64,
|
size: u64,
|
||||||
@@ -415,19 +434,18 @@ impl From<LegacyReportItem> for ReportItem {
|
|||||||
demangled_name: value.demangled_name,
|
demangled_name: value.demangled_name,
|
||||||
virtual_address: value.address,
|
virtual_address: value.address,
|
||||||
}),
|
}),
|
||||||
|
address: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
fn serialize_hex<S>(x: &Option<u64>, s: S) -> Result<S::Ok, S::Error>
|
fn serialize_hex<S>(x: &Option<u64>, s: S) -> Result<S::Ok, S::Error>
|
||||||
where S: serde::Serializer {
|
where S: serde::Serializer {
|
||||||
if let Some(x) = x {
|
if let Some(x) = x { s.serialize_str(&format!("{x:#x}")) } else { s.serialize_none() }
|
||||||
s.serialize_str(&format!("{:#x}", x))
|
|
||||||
} else {
|
|
||||||
s.serialize_none()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
fn deserialize_hex<'de, D>(d: D) -> Result<Option<u64>, D::Error>
|
fn deserialize_hex<'de, D>(d: D) -> Result<Option<u64>, D::Error>
|
||||||
where D: serde::Deserializer<'de> {
|
where D: serde::Deserializer<'de> {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
use prost::Message;
|
|
||||||
use wasm_bindgen::prelude::*;
|
|
||||||
|
|
||||||
use crate::{bindings::diff::DiffResult, diff, obj};
|
|
||||||
|
|
||||||
fn parse_object(
|
|
||||||
data: Option<Box<[u8]>>,
|
|
||||||
config: &diff::DiffObjConfig,
|
|
||||||
) -> Result<Option<obj::ObjInfo>, JsError> {
|
|
||||||
data.as_ref().map(|data| obj::read::parse(data, config)).transpose().to_js()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_and_run_diff(
|
|
||||||
left: Option<Box<[u8]>>,
|
|
||||||
right: Option<Box<[u8]>>,
|
|
||||||
config: diff::DiffObjConfig,
|
|
||||||
) -> Result<DiffResult, JsError> {
|
|
||||||
let target = parse_object(left, &config)?;
|
|
||||||
let base = parse_object(right, &config)?;
|
|
||||||
run_diff(target.as_ref(), base.as_ref(), config)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_diff(
|
|
||||||
left: Option<&obj::ObjInfo>,
|
|
||||||
right: Option<&obj::ObjInfo>,
|
|
||||||
config: diff::DiffObjConfig,
|
|
||||||
) -> Result<DiffResult, JsError> {
|
|
||||||
log::debug!("Running diff with config: {:?}", config);
|
|
||||||
let result = diff::diff_objs(&config, left, right, None).to_js()?;
|
|
||||||
let left = left.and_then(|o| result.left.as_ref().map(|d| (o, d)));
|
|
||||||
let right = right.and_then(|o| result.right.as_ref().map(|d| (o, d)));
|
|
||||||
Ok(DiffResult::new(left, right))
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[wasm_bindgen]
|
|
||||||
// pub fn run_diff_json(
|
|
||||||
// left: Option<Box<[u8]>>,
|
|
||||||
// right: Option<Box<[u8]>>,
|
|
||||||
// config: diff::DiffObjConfig,
|
|
||||||
// ) -> Result<String, JsError> {
|
|
||||||
// let out = run_diff_opt_box(left, right, config)?;
|
|
||||||
// serde_json::to_string(&out).map_err(|e| JsError::new(&e.to_string()))
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn run_diff_proto(
|
|
||||||
left: Option<Box<[u8]>>,
|
|
||||||
right: Option<Box<[u8]>>,
|
|
||||||
config: diff::DiffObjConfig,
|
|
||||||
) -> Result<Box<[u8]>, JsError> {
|
|
||||||
let out = parse_and_run_diff(left, right, config)?;
|
|
||||||
Ok(out.encode_to_vec().into_boxed_slice())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen(start)]
|
|
||||||
fn start() -> Result<(), JsError> {
|
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
console_log::init_with_level(log::Level::Debug).to_js()?;
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
console_log::init_with_level(log::Level::Info).to_js()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn to_js_error(e: impl std::fmt::Display) -> JsError { JsError::new(&e.to_string()) }
|
|
||||||
|
|
||||||
trait ToJsResult {
|
|
||||||
type Output;
|
|
||||||
|
|
||||||
fn to_js(self) -> Result<Self::Output, JsError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, E: std::fmt::Display> ToJsResult for Result<T, E> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
fn to_js(self) -> Result<T, JsError> { self.map_err(to_js_error) }
|
|
||||||
}
|
|
||||||
109
objdiff-core/src/build/mod.rs
Normal file
109
objdiff-core/src/build/mod.rs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
pub mod watcher;
|
||||||
|
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use typed_path::{Utf8PlatformPathBuf, Utf8UnixPath};
|
||||||
|
|
||||||
|
pub struct BuildStatus {
|
||||||
|
pub success: bool,
|
||||||
|
pub cmdline: String,
|
||||||
|
pub stdout: String,
|
||||||
|
pub stderr: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for BuildStatus {
|
||||||
|
fn default() -> Self {
|
||||||
|
BuildStatus {
|
||||||
|
success: true,
|
||||||
|
cmdline: String::new(),
|
||||||
|
stdout: String::new(),
|
||||||
|
stderr: String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BuildConfig {
|
||||||
|
pub project_dir: Option<Utf8PlatformPathBuf>,
|
||||||
|
pub custom_make: Option<String>,
|
||||||
|
pub custom_args: Option<Vec<String>>,
|
||||||
|
#[allow(unused)]
|
||||||
|
pub selected_wsl_distro: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_make(config: &BuildConfig, arg: &Utf8UnixPath) -> BuildStatus {
|
||||||
|
let Some(cwd) = &config.project_dir else {
|
||||||
|
return BuildStatus {
|
||||||
|
success: false,
|
||||||
|
stderr: "Missing project dir".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let make = config.custom_make.as_deref().unwrap_or("make");
|
||||||
|
let make_args = config.custom_args.as_deref().unwrap_or(&[]);
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
let mut command = {
|
||||||
|
let mut command = Command::new(make);
|
||||||
|
command.current_dir(cwd).args(make_args).arg(arg);
|
||||||
|
command
|
||||||
|
};
|
||||||
|
#[cfg(windows)]
|
||||||
|
let mut command = {
|
||||||
|
use alloc::borrow::Cow;
|
||||||
|
use std::os::windows::process::CommandExt;
|
||||||
|
|
||||||
|
let mut command = if config.selected_wsl_distro.is_some() {
|
||||||
|
Command::new("wsl")
|
||||||
|
} else {
|
||||||
|
Command::new(make)
|
||||||
|
};
|
||||||
|
if let Some(distro) = &config.selected_wsl_distro {
|
||||||
|
// Strip distro root prefix \\wsl.localhost\{distro}
|
||||||
|
let wsl_path_prefix = format!("\\\\wsl.localhost\\{}", distro);
|
||||||
|
let cwd = match cwd.strip_prefix(wsl_path_prefix) {
|
||||||
|
// Convert to absolute Unix path
|
||||||
|
Ok(new_cwd) => Cow::Owned(
|
||||||
|
Utf8UnixPath::new("/").join(new_cwd.with_unix_encoding()).into_string(),
|
||||||
|
),
|
||||||
|
// Otherwise, use the Windows path as is
|
||||||
|
Err(_) => Cow::Borrowed(cwd.as_str()),
|
||||||
|
};
|
||||||
|
|
||||||
|
command
|
||||||
|
.arg("--cd")
|
||||||
|
.arg::<&str>(cwd.as_ref())
|
||||||
|
.arg("-d")
|
||||||
|
.arg(distro)
|
||||||
|
.arg("--")
|
||||||
|
.arg(make)
|
||||||
|
.args(make_args)
|
||||||
|
.arg(arg.as_str());
|
||||||
|
} else {
|
||||||
|
command.current_dir(cwd).args(make_args).arg(arg.as_str());
|
||||||
|
}
|
||||||
|
command.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW);
|
||||||
|
command
|
||||||
|
};
|
||||||
|
let mut cmdline = shell_escape::escape(command.get_program().to_string_lossy()).into_owned();
|
||||||
|
for arg in command.get_args() {
|
||||||
|
cmdline.push(' ');
|
||||||
|
cmdline.push_str(shell_escape::escape(arg.to_string_lossy()).as_ref());
|
||||||
|
}
|
||||||
|
let output = match command.output() {
|
||||||
|
Ok(output) => output,
|
||||||
|
Err(e) => {
|
||||||
|
return BuildStatus {
|
||||||
|
success: false,
|
||||||
|
cmdline,
|
||||||
|
stdout: Default::default(),
|
||||||
|
stderr: e.to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Try from_utf8 first to avoid copying the buffer if it's valid, then fall back to from_utf8_lossy
|
||||||
|
let stdout = String::from_utf8(output.stdout)
|
||||||
|
.unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned());
|
||||||
|
let stderr = String::from_utf8(output.stderr)
|
||||||
|
.unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned());
|
||||||
|
BuildStatus { success: output.status.success(), cmdline, stdout, stderr }
|
||||||
|
}
|
||||||
76
objdiff-core/src/build/watcher.rs
Normal file
76
objdiff-core/src/build/watcher.rs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
use std::{
|
||||||
|
fs,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::{
|
||||||
|
Arc,
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
},
|
||||||
|
task::Waker,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
use globset::GlobSet;
|
||||||
|
use notify::RecursiveMode;
|
||||||
|
use notify_debouncer_full::{DebounceEventResult, new_debouncer_opt};
|
||||||
|
|
||||||
|
pub type Watcher = notify_debouncer_full::Debouncer<
|
||||||
|
notify::RecommendedWatcher,
|
||||||
|
notify_debouncer_full::RecommendedCache,
|
||||||
|
>;
|
||||||
|
|
||||||
|
pub struct WatcherState {
|
||||||
|
pub config_path: Option<PathBuf>,
|
||||||
|
pub left_obj_path: Option<PathBuf>,
|
||||||
|
pub right_obj_path: Option<PathBuf>,
|
||||||
|
pub patterns: GlobSet,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_watcher(
|
||||||
|
modified: Arc<AtomicBool>,
|
||||||
|
project_dir: &Path,
|
||||||
|
patterns: GlobSet,
|
||||||
|
ignore_patterns: GlobSet,
|
||||||
|
waker: Waker,
|
||||||
|
) -> notify::Result<Watcher> {
|
||||||
|
let base_dir = fs::canonicalize(project_dir)?;
|
||||||
|
let base_dir_clone = base_dir.clone();
|
||||||
|
let timeout = Duration::from_millis(200);
|
||||||
|
let config = notify::Config::default().with_poll_interval(Duration::from_secs(2));
|
||||||
|
let mut debouncer = new_debouncer_opt(
|
||||||
|
timeout,
|
||||||
|
None,
|
||||||
|
move |result: DebounceEventResult| match result {
|
||||||
|
Ok(events) => {
|
||||||
|
let mut any_match = false;
|
||||||
|
for event in events.iter() {
|
||||||
|
if !matches!(
|
||||||
|
event.kind,
|
||||||
|
notify::EventKind::Modify(..)
|
||||||
|
| notify::EventKind::Create(..)
|
||||||
|
| notify::EventKind::Remove(..)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for path in &event.paths {
|
||||||
|
let Ok(path) = path.strip_prefix(&base_dir_clone) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if patterns.is_match(path) && !ignore_patterns.is_match(path) {
|
||||||
|
log::debug!("File modified: {}", path.display());
|
||||||
|
any_match = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if any_match {
|
||||||
|
modified.store(true, Ordering::Relaxed);
|
||||||
|
waker.wake_by_ref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(errors) => errors.iter().for_each(|e| log::error!("Watch error: {e:?}")),
|
||||||
|
},
|
||||||
|
notify_debouncer_full::RecommendedCache::new(),
|
||||||
|
config,
|
||||||
|
)?;
|
||||||
|
debouncer.watch(base_dir, RecursiveMode::Recursive)?;
|
||||||
|
Ok(debouncer)
|
||||||
|
}
|
||||||
@@ -1,36 +1,49 @@
|
|||||||
use std::{
|
pub mod path;
|
||||||
fs,
|
|
||||||
fs::File,
|
use alloc::{
|
||||||
io::{BufReader, BufWriter, Read},
|
collections::BTreeMap,
|
||||||
path::{Path, PathBuf},
|
string::{String, ToString},
|
||||||
|
vec::Vec,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{Context, Result, anyhow};
|
||||||
use bimap::BiBTreeMap;
|
|
||||||
use filetime::FileTime;
|
|
||||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
|
use path::unix_path_serde_option;
|
||||||
|
use typed_path::Utf8UnixPathBuf;
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Default, Clone)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
|
||||||
pub struct ProjectConfig {
|
pub struct ProjectConfig {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub min_version: Option<String>,
|
pub min_version: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub custom_make: Option<String>,
|
pub custom_make: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub custom_args: Option<Vec<String>>,
|
pub custom_args: Option<Vec<String>>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(
|
||||||
pub target_dir: Option<PathBuf>,
|
feature = "serde",
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
pub base_dir: Option<PathBuf>,
|
)]
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
pub target_dir: Option<Utf8UnixPathBuf>,
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
|
)]
|
||||||
|
pub base_dir: Option<Utf8UnixPathBuf>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub build_base: Option<bool>,
|
pub build_base: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub build_target: Option<bool>,
|
pub build_target: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub watch_patterns: Option<Vec<Glob>>,
|
pub watch_patterns: Option<Vec<String>>,
|
||||||
#[serde(default, alias = "objects", skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
|
pub ignore_patterns: Option<Vec<String>>,
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(alias = "objects", skip_serializing_if = "Option::is_none")
|
||||||
|
)]
|
||||||
pub units: Option<Vec<ProjectObject>>,
|
pub units: Option<Vec<ProjectObject>>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub progress_categories: Option<Vec<ProjectProgressCategory>>,
|
pub progress_categories: Option<Vec<ProjectProgressCategory>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,11 +51,6 @@ impl ProjectConfig {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn units(&self) -> &[ProjectObject] { self.units.as_deref().unwrap_or_default() }
|
pub fn units(&self) -> &[ProjectObject] { self.units.as_deref().unwrap_or_default() }
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn units_mut(&mut self) -> &mut Vec<ProjectObject> {
|
|
||||||
self.units.get_or_insert_with(Vec::new)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn progress_categories(&self) -> &[ProjectProgressCategory] {
|
pub fn progress_categories(&self) -> &[ProjectProgressCategory] {
|
||||||
self.progress_categories.as_deref().unwrap_or_default()
|
self.progress_categories.as_deref().unwrap_or_default()
|
||||||
@@ -52,53 +60,86 @@ impl ProjectConfig {
|
|||||||
pub fn progress_categories_mut(&mut self) -> &mut Vec<ProjectProgressCategory> {
|
pub fn progress_categories_mut(&mut self) -> &mut Vec<ProjectProgressCategory> {
|
||||||
self.progress_categories.get_or_insert_with(Vec::new)
|
self.progress_categories.get_or_insert_with(Vec::new)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn build_watch_patterns(&self) -> Result<Vec<Glob>, globset::Error> {
|
||||||
|
Ok(if let Some(watch_patterns) = &self.watch_patterns {
|
||||||
|
watch_patterns
|
||||||
|
.iter()
|
||||||
|
.map(|s| Glob::new(s))
|
||||||
|
.collect::<Result<Vec<Glob>, globset::Error>>()?
|
||||||
|
} else {
|
||||||
|
default_watch_patterns()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_ignore_patterns(&self) -> Result<Vec<Glob>, globset::Error> {
|
||||||
|
Ok(if let Some(ignore_patterns) = &self.ignore_patterns {
|
||||||
|
ignore_patterns
|
||||||
|
.iter()
|
||||||
|
.map(|s| Glob::new(s))
|
||||||
|
.collect::<Result<Vec<Glob>, globset::Error>>()?
|
||||||
|
} else {
|
||||||
|
default_ignore_patterns()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Default, Clone)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
|
||||||
pub struct ProjectObject {
|
pub struct ProjectObject {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(
|
||||||
pub path: Option<PathBuf>,
|
feature = "serde",
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
pub target_path: Option<PathBuf>,
|
)]
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
pub path: Option<Utf8UnixPathBuf>,
|
||||||
pub base_path: Option<PathBuf>,
|
#[cfg_attr(
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
feature = "serde",
|
||||||
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
|
)]
|
||||||
|
pub target_path: Option<Utf8UnixPathBuf>,
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
|
)]
|
||||||
|
pub base_path: Option<Utf8UnixPathBuf>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
#[deprecated(note = "Use metadata.reverse_fn_order")]
|
#[deprecated(note = "Use metadata.reverse_fn_order")]
|
||||||
pub reverse_fn_order: Option<bool>,
|
pub reverse_fn_order: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
#[deprecated(note = "Use metadata.complete")]
|
#[deprecated(note = "Use metadata.complete")]
|
||||||
pub complete: Option<bool>,
|
pub complete: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub scratch: Option<ScratchConfig>,
|
pub scratch: Option<ScratchConfig>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub metadata: Option<ProjectObjectMetadata>,
|
pub metadata: Option<ProjectObjectMetadata>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub symbol_mappings: Option<SymbolMappings>,
|
pub symbol_mappings: Option<BTreeMap<String, String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type SymbolMappings = BiBTreeMap<String, String>;
|
#[derive(Default, Clone)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
|
||||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub struct ProjectObjectMetadata {
|
pub struct ProjectObjectMetadata {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub complete: Option<bool>,
|
pub complete: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub reverse_fn_order: Option<bool>,
|
pub reverse_fn_order: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(
|
||||||
pub source_path: Option<String>,
|
feature = "serde",
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
|
)]
|
||||||
|
pub source_path: Option<Utf8UnixPathBuf>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub progress_categories: Option<Vec<String>>,
|
pub progress_categories: Option<Vec<String>>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub auto_generated: Option<bool>,
|
pub auto_generated: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Default, Clone)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
|
||||||
pub struct ProjectProgressCategory {
|
pub struct ProjectProgressCategory {
|
||||||
#[serde(default)]
|
|
||||||
pub id: String,
|
pub id: String,
|
||||||
#[serde(default)]
|
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,33 +148,12 @@ impl ProjectObject {
|
|||||||
if let Some(name) = &self.name {
|
if let Some(name) = &self.name {
|
||||||
name
|
name
|
||||||
} else if let Some(path) = &self.path {
|
} else if let Some(path) = &self.path {
|
||||||
path.to_str().unwrap_or("[invalid path]")
|
path.as_str()
|
||||||
} else {
|
} else {
|
||||||
"[unknown]"
|
"[unknown]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_paths(
|
|
||||||
&mut self,
|
|
||||||
project_dir: &Path,
|
|
||||||
target_obj_dir: Option<&Path>,
|
|
||||||
base_obj_dir: Option<&Path>,
|
|
||||||
) {
|
|
||||||
if let (Some(target_obj_dir), Some(path), None) =
|
|
||||||
(target_obj_dir, &self.path, &self.target_path)
|
|
||||||
{
|
|
||||||
self.target_path = Some(target_obj_dir.join(path));
|
|
||||||
} else if let Some(path) = &self.target_path {
|
|
||||||
self.target_path = Some(project_dir.join(path));
|
|
||||||
}
|
|
||||||
if let (Some(base_obj_dir), Some(path), None) = (base_obj_dir, &self.path, &self.base_path)
|
|
||||||
{
|
|
||||||
self.base_path = Some(base_obj_dir.join(path));
|
|
||||||
} else if let Some(path) = &self.base_path {
|
|
||||||
self.base_path = Some(project_dir.join(path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn complete(&self) -> Option<bool> {
|
pub fn complete(&self) -> Option<bool> {
|
||||||
#[expect(deprecated)]
|
#[expect(deprecated)]
|
||||||
self.metadata.as_ref().and_then(|m| m.complete).or(self.complete)
|
self.metadata.as_ref().and_then(|m| m.complete).or(self.complete)
|
||||||
@@ -148,24 +168,36 @@ impl ProjectObject {
|
|||||||
self.metadata.as_ref().and_then(|m| m.auto_generated).unwrap_or(false)
|
self.metadata.as_ref().and_then(|m| m.auto_generated).unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn source_path(&self) -> Option<&String> {
|
pub fn source_path(&self) -> Option<&Utf8UnixPathBuf> {
|
||||||
self.metadata.as_ref().and_then(|m| m.source_path.as_ref())
|
self.metadata.as_ref().and_then(|m| m.source_path.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn progress_categories(&self) -> &[String] {
|
||||||
|
self.metadata.as_ref().and_then(|m| m.progress_categories.as_deref()).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn auto_generated(&self) -> Option<bool> {
|
||||||
|
self.metadata.as_ref().and_then(|m| m.auto_generated)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Default, Clone, Eq, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(default))]
|
||||||
pub struct ScratchConfig {
|
pub struct ScratchConfig {
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub platform: Option<String>,
|
pub platform: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub compiler: Option<String>,
|
pub compiler: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub c_flags: Option<String>,
|
pub c_flags: Option<String>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(
|
||||||
pub ctx_path: Option<PathBuf>,
|
feature = "serde",
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
|
||||||
|
)]
|
||||||
|
pub ctx_path: Option<Utf8UnixPathBuf>,
|
||||||
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub build_ctx: Option<bool>,
|
pub build_ctx: Option<bool>,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
pub preset_id: Option<u32>,
|
pub preset_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,16 +208,30 @@ pub const DEFAULT_WATCH_PATTERNS: &[&str] = &[
|
|||||||
"*.inc", "*.py", "*.yml", "*.txt", "*.json",
|
"*.inc", "*.py", "*.yml", "*.txt", "*.json",
|
||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
pub const DEFAULT_IGNORE_PATTERNS: &[&str] = &["build/**/*"];
|
||||||
pub struct ProjectConfigInfo {
|
|
||||||
pub path: PathBuf,
|
pub fn default_watch_patterns() -> Vec<Glob> {
|
||||||
pub timestamp: Option<FileTime>,
|
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> {
|
pub fn default_ignore_patterns() -> Vec<Glob> {
|
||||||
|
DEFAULT_IGNORE_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
|
pub struct ProjectConfigInfo {
|
||||||
|
pub path: std::path::PathBuf,
|
||||||
|
pub timestamp: Option<filetime::FileTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub fn try_project_config(
|
||||||
|
dir: &std::path::Path,
|
||||||
|
) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> {
|
||||||
for filename in CONFIG_FILENAMES.iter() {
|
for filename in CONFIG_FILENAMES.iter() {
|
||||||
let config_path = dir.join(filename);
|
let config_path = dir.join(filename);
|
||||||
let Ok(file) = File::open(&config_path) else {
|
let Ok(file) = std::fs::File::open(&config_path) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let metadata = file.metadata();
|
let metadata = file.metadata();
|
||||||
@@ -193,12 +239,9 @@ pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectC
|
|||||||
if !metadata.is_file() {
|
if !metadata.is_file() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let ts = FileTime::from_last_modification_time(&metadata);
|
let ts = filetime::FileTime::from_last_modification_time(&metadata);
|
||||||
let mut reader = BufReader::new(file);
|
let mut reader = std::io::BufReader::new(file);
|
||||||
let mut result = match filename.contains("json") {
|
let mut result = read_json_config(&mut reader);
|
||||||
true => read_json_config(&mut reader),
|
|
||||||
false => read_yml_config(&mut reader),
|
|
||||||
};
|
|
||||||
if let Ok(config) = &result {
|
if let Ok(config) = &result {
|
||||||
// Validate min_version if present
|
// Validate min_version if present
|
||||||
if let Err(e) = validate_min_version(config) {
|
if let Err(e) = validate_min_version(config) {
|
||||||
@@ -211,40 +254,42 @@ pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectC
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
pub fn save_project_config(
|
pub fn save_project_config(
|
||||||
config: &ProjectConfig,
|
config: &ProjectConfig,
|
||||||
info: &ProjectConfigInfo,
|
info: &ProjectConfigInfo,
|
||||||
) -> Result<ProjectConfigInfo> {
|
) -> Result<ProjectConfigInfo> {
|
||||||
if let Some(last_ts) = info.timestamp {
|
if let Some(last_ts) = info.timestamp {
|
||||||
// Check if the file has changed since we last read it
|
// Check if the file has changed since we last read it
|
||||||
if let Ok(metadata) = fs::metadata(&info.path) {
|
if let Ok(metadata) = std::fs::metadata(&info.path) {
|
||||||
let ts = FileTime::from_last_modification_time(&metadata);
|
let ts = filetime::FileTime::from_last_modification_time(&metadata);
|
||||||
if ts != last_ts {
|
if ts != last_ts {
|
||||||
return Err(anyhow!("Config file has changed since last read"));
|
return Err(anyhow!("Config file has changed since last read"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut writer =
|
let mut writer = std::io::BufWriter::new(
|
||||||
BufWriter::new(File::create(&info.path).context("Failed to create config file")?);
|
std::fs::File::create(&info.path).context("Failed to create config file")?,
|
||||||
|
);
|
||||||
let ext = info.path.extension().and_then(|ext| ext.to_str()).unwrap_or("json");
|
let ext = info.path.extension().and_then(|ext| ext.to_str()).unwrap_or("json");
|
||||||
match ext {
|
match ext {
|
||||||
"json" => serde_json::to_writer_pretty(&mut writer, config).context("Failed to write JSON"),
|
"json" => serde_json::to_writer_pretty(&mut writer, config).context("Failed to write JSON"),
|
||||||
"yml" | "yaml" => {
|
|
||||||
serde_yaml::to_writer(&mut writer, config).context("Failed to write YAML")
|
|
||||||
}
|
|
||||||
_ => Err(anyhow!("Unknown config file extension: {ext}")),
|
_ => Err(anyhow!("Unknown config file extension: {ext}")),
|
||||||
}?;
|
}?;
|
||||||
let file = writer.into_inner().context("Failed to flush file")?;
|
let file = writer.into_inner().context("Failed to flush file")?;
|
||||||
let metadata = file.metadata().context("Failed to get file metadata")?;
|
let metadata = file.metadata().context("Failed to get file metadata")?;
|
||||||
let ts = FileTime::from_last_modification_time(&metadata);
|
let ts = filetime::FileTime::from_last_modification_time(&metadata);
|
||||||
Ok(ProjectConfigInfo { path: info.path.clone(), timestamp: Some(ts) })
|
Ok(ProjectConfigInfo { path: info.path.clone(), timestamp: Some(ts) })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_min_version(config: &ProjectConfig) -> Result<()> {
|
fn validate_min_version(config: &ProjectConfig) -> Result<()> {
|
||||||
let Some(min_version) = &config.min_version else { return Ok(()) };
|
let Some(min_version) = &config.min_version else { return Ok(()) };
|
||||||
let version = semver::Version::parse(env!("CARGO_PKG_VERSION"))
|
let version = semver::Version::parse(env!("CARGO_PKG_VERSION"))
|
||||||
|
.map_err(|e| anyhow::Error::msg(e.to_string()))
|
||||||
.context("Failed to parse package version")?;
|
.context("Failed to parse package version")?;
|
||||||
let min_version = semver::Version::parse(min_version).context("Failed to parse min_version")?;
|
let min_version = semver::Version::parse(min_version)
|
||||||
|
.map_err(|e| anyhow::Error::msg(e.to_string()))
|
||||||
|
.context("Failed to parse min_version")?;
|
||||||
if version >= min_version {
|
if version >= min_version {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
@@ -252,15 +297,12 @@ fn validate_min_version(config: &ProjectConfig) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_yml_config<R: Read>(reader: &mut R) -> Result<ProjectConfig> {
|
#[cfg(feature = "std")]
|
||||||
Ok(serde_yaml::from_reader(reader)?)
|
fn read_json_config<R: std::io::Read>(reader: &mut R) -> Result<ProjectConfig> {
|
||||||
}
|
|
||||||
|
|
||||||
fn read_json_config<R: Read>(reader: &mut R) -> Result<ProjectConfig> {
|
|
||||||
Ok(serde_json::from_reader(reader)?)
|
Ok(serde_json::from_reader(reader)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_globset(vec: &[Glob]) -> std::result::Result<GlobSet, globset::Error> {
|
pub fn build_globset(vec: &[Glob]) -> Result<GlobSet, globset::Error> {
|
||||||
let mut builder = GlobSetBuilder::new();
|
let mut builder = GlobSetBuilder::new();
|
||||||
for glob in vec {
|
for glob in vec {
|
||||||
builder.add(glob.clone());
|
builder.add(glob.clone());
|
||||||
|
|||||||
57
objdiff-core/src/config/path.rs
Normal file
57
objdiff-core/src/config/path.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// For argp::FromArgs
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub fn platform_path(value: &str) -> Result<typed_path::Utf8PlatformPathBuf, String> {
|
||||||
|
Ok(typed_path::Utf8PlatformPathBuf::from(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the path is valid UTF-8 and returns it as a [`Utf8PlatformPath`].
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub fn check_path(
|
||||||
|
path: &std::path::Path,
|
||||||
|
) -> Result<&typed_path::Utf8PlatformPath, core::str::Utf8Error> {
|
||||||
|
typed_path::Utf8PlatformPath::from_bytes_path(typed_path::PlatformPath::new(
|
||||||
|
path.as_os_str().as_encoded_bytes(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the path is valid UTF-8 and returns it as a [`Utf8NativePathBuf`].
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub fn check_path_buf(
|
||||||
|
path: std::path::PathBuf,
|
||||||
|
) -> Result<typed_path::Utf8PlatformPathBuf, alloc::string::FromUtf8Error> {
|
||||||
|
typed_path::Utf8PlatformPathBuf::from_bytes_path_buf(typed_path::PlatformPathBuf::from(
|
||||||
|
path.into_os_string().into_encoded_bytes(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde")]
|
||||||
|
pub mod unix_path_serde_option {
|
||||||
|
use serde::{Deserialize, Deserializer, Serializer};
|
||||||
|
use typed_path::Utf8UnixPathBuf;
|
||||||
|
|
||||||
|
pub fn serialize<S>(path: &Option<Utf8UnixPathBuf>, s: S) -> Result<S::Ok, S::Error>
|
||||||
|
where S: Serializer {
|
||||||
|
if let Some(path) = path { s.serialize_some(path.as_str()) } else { s.serialize_none() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Utf8UnixPathBuf>, D::Error>
|
||||||
|
where D: Deserializer<'de> {
|
||||||
|
Ok(Option::<String>::deserialize(deserializer)?.map(Utf8UnixPathBuf::from))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "serde", feature = "std"))]
|
||||||
|
pub mod platform_path_serde_option {
|
||||||
|
use serde::{Deserialize, Deserializer, Serializer};
|
||||||
|
use typed_path::Utf8PlatformPathBuf;
|
||||||
|
|
||||||
|
pub fn serialize<S>(path: &Option<Utf8PlatformPathBuf>, s: S) -> Result<S::Ok, S::Error>
|
||||||
|
where S: Serializer {
|
||||||
|
if let Some(path) = path { s.serialize_some(path.as_str()) } else { s.serialize_none() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Utf8PlatformPathBuf>, D::Error>
|
||||||
|
where D: Deserializer<'de> {
|
||||||
|
Ok(Option::<String>::deserialize(deserializer)?.map(Utf8PlatformPathBuf::from))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,371 +1,503 @@
|
|||||||
use std::{cmp::max, collections::BTreeMap};
|
use alloc::{
|
||||||
|
collections::{BTreeMap, btree_map},
|
||||||
use anyhow::{anyhow, Result};
|
string::{String, ToString},
|
||||||
use similar::{capture_diff_slices_deadline, Algorithm};
|
vec,
|
||||||
|
vec::Vec,
|
||||||
use crate::{
|
|
||||||
arch::ProcessCodeResult,
|
|
||||||
diff::{
|
|
||||||
DiffObjConfig, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind,
|
|
||||||
ObjSymbolDiff,
|
|
||||||
},
|
|
||||||
obj::{ObjInfo, ObjInsArg, ObjReloc, ObjSymbolFlags, SymbolRef},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn process_code_symbol(
|
use anyhow::{Context, Result, anyhow, ensure};
|
||||||
obj: &ObjInfo,
|
|
||||||
symbol_ref: SymbolRef,
|
use super::{
|
||||||
config: &DiffObjConfig,
|
DiffObjConfig, FunctionRelocDiffs, InstructionArgDiffIndex, InstructionBranchFrom,
|
||||||
) -> Result<ProcessCodeResult> {
|
InstructionBranchTo, InstructionDiffKind, InstructionDiffRow, SymbolDiff,
|
||||||
let (section, symbol) = obj.section_symbol(symbol_ref);
|
display::display_ins_data_literals,
|
||||||
let section = section.ok_or_else(|| anyhow!("Code symbol section not found"))?;
|
};
|
||||||
let code = §ion.data
|
use crate::obj::{
|
||||||
[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
|
InstructionArg, InstructionArgValue, InstructionRef, Object, ResolvedInstructionRef,
|
||||||
obj.arch.process_code(
|
ResolvedRelocation, ResolvedSymbol, SymbolFlag, SymbolKind,
|
||||||
symbol.address,
|
};
|
||||||
code,
|
|
||||||
section.orig_index,
|
pub fn no_diff_code(
|
||||||
§ion.relocations,
|
obj: &Object,
|
||||||
§ion.line_info,
|
symbol_index: usize,
|
||||||
config,
|
diff_config: &DiffObjConfig,
|
||||||
)
|
) -> Result<SymbolDiff> {
|
||||||
|
let symbol = &obj.symbols[symbol_index];
|
||||||
|
let section_index = symbol.section.ok_or_else(|| anyhow!("Missing section for symbol"))?;
|
||||||
|
let section = &obj.sections[section_index];
|
||||||
|
let data = section.data_range(symbol.address, symbol.size as usize).ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"Symbol data out of bounds: {:#x}..{:#x}",
|
||||||
|
symbol.address,
|
||||||
|
symbol.address + symbol.size
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let ops = obj.arch.scan_instructions(
|
||||||
|
ResolvedSymbol { obj, symbol_index, symbol, section_index, section, data },
|
||||||
|
diff_config,
|
||||||
|
)?;
|
||||||
|
let mut instruction_rows = Vec::<InstructionDiffRow>::new();
|
||||||
|
for i in &ops {
|
||||||
|
instruction_rows.push(InstructionDiffRow { ins_ref: Some(*i), ..Default::default() });
|
||||||
|
}
|
||||||
|
resolve_branches(&ops, &mut instruction_rows);
|
||||||
|
Ok(SymbolDiff { target_symbol: None, match_percent: None, diff_score: None, instruction_rows })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn no_diff_code(out: &ProcessCodeResult, symbol_ref: SymbolRef) -> Result<ObjSymbolDiff> {
|
const PENALTY_IMM_DIFF: u64 = 1;
|
||||||
let mut diff = Vec::<ObjInsDiff>::new();
|
const PENALTY_REG_DIFF: u64 = 5;
|
||||||
for i in &out.insts {
|
const PENALTY_REPLACE: u64 = 60;
|
||||||
diff.push(ObjInsDiff {
|
const PENALTY_INSERT_DELETE: u64 = 100;
|
||||||
ins: Some(i.clone()),
|
|
||||||
kind: ObjInsDiffKind::None,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
resolve_branches(&mut diff);
|
|
||||||
Ok(ObjSymbolDiff { symbol_ref, target_symbol: None, instructions: diff, match_percent: None })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn diff_code(
|
pub fn diff_code(
|
||||||
left_obj: &ObjInfo,
|
left_obj: &Object,
|
||||||
right_obj: &ObjInfo,
|
right_obj: &Object,
|
||||||
left_out: &ProcessCodeResult,
|
left_symbol_idx: usize,
|
||||||
right_out: &ProcessCodeResult,
|
right_symbol_idx: usize,
|
||||||
left_symbol_ref: SymbolRef,
|
diff_config: &DiffObjConfig,
|
||||||
right_symbol_ref: SymbolRef,
|
) -> Result<(SymbolDiff, SymbolDiff)> {
|
||||||
config: &DiffObjConfig,
|
let left_symbol = &left_obj.symbols[left_symbol_idx];
|
||||||
) -> Result<(ObjSymbolDiff, ObjSymbolDiff)> {
|
let right_symbol = &right_obj.symbols[right_symbol_idx];
|
||||||
let mut left_diff = Vec::<ObjInsDiff>::new();
|
let left_section = left_symbol
|
||||||
let mut right_diff = Vec::<ObjInsDiff>::new();
|
.section
|
||||||
diff_instructions(&mut left_diff, &mut right_diff, left_out, right_out)?;
|
.and_then(|i| left_obj.sections.get(i))
|
||||||
|
.ok_or_else(|| anyhow!("Missing section for symbol"))?;
|
||||||
|
let right_section = right_symbol
|
||||||
|
.section
|
||||||
|
.and_then(|i| right_obj.sections.get(i))
|
||||||
|
.ok_or_else(|| anyhow!("Missing section for symbol"))?;
|
||||||
|
let left_data = left_section
|
||||||
|
.data_range(left_symbol.address, left_symbol.size as usize)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"Symbol data out of bounds: {:#x}..{:#x}",
|
||||||
|
left_symbol.address,
|
||||||
|
left_symbol.address + left_symbol.size
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let right_data = right_section
|
||||||
|
.data_range(right_symbol.address, right_symbol.size as usize)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"Symbol data out of bounds: {:#x}..{:#x}",
|
||||||
|
right_symbol.address,
|
||||||
|
right_symbol.address + right_symbol.size
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
resolve_branches(&mut left_diff);
|
let left_section_idx = left_symbol.section.unwrap();
|
||||||
resolve_branches(&mut right_diff);
|
let right_section_idx = right_symbol.section.unwrap();
|
||||||
|
let left_ops = left_obj.arch.scan_instructions(
|
||||||
|
ResolvedSymbol {
|
||||||
|
obj: left_obj,
|
||||||
|
symbol_index: left_symbol_idx,
|
||||||
|
symbol: left_symbol,
|
||||||
|
section_index: left_section_idx,
|
||||||
|
section: left_section,
|
||||||
|
data: left_data,
|
||||||
|
},
|
||||||
|
diff_config,
|
||||||
|
)?;
|
||||||
|
let right_ops = right_obj.arch.scan_instructions(
|
||||||
|
ResolvedSymbol {
|
||||||
|
obj: right_obj,
|
||||||
|
symbol_index: right_symbol_idx,
|
||||||
|
symbol: right_symbol,
|
||||||
|
section_index: right_section_idx,
|
||||||
|
section: right_section,
|
||||||
|
data: right_data,
|
||||||
|
},
|
||||||
|
diff_config,
|
||||||
|
)?;
|
||||||
|
let (mut left_rows, mut right_rows) = diff_instructions(&left_ops, &right_ops)?;
|
||||||
|
resolve_branches(&left_ops, &mut left_rows);
|
||||||
|
resolve_branches(&right_ops, &mut right_rows);
|
||||||
|
|
||||||
let mut diff_state = InsDiffState::default();
|
let mut diff_state = InstructionDiffState::default();
|
||||||
for (left, right) in left_diff.iter_mut().zip(right_diff.iter_mut()) {
|
for (left_row, right_row) in left_rows.iter_mut().zip(right_rows.iter_mut()) {
|
||||||
let result = compare_ins(config, left_obj, right_obj, left, right, &mut diff_state)?;
|
let result = diff_instruction(
|
||||||
left.kind = result.kind;
|
left_obj,
|
||||||
right.kind = result.kind;
|
right_obj,
|
||||||
left.arg_diff = result.left_args_diff;
|
left_symbol_idx,
|
||||||
right.arg_diff = result.right_args_diff;
|
right_symbol_idx,
|
||||||
|
left_row.ins_ref,
|
||||||
|
right_row.ins_ref,
|
||||||
|
left_row,
|
||||||
|
right_row,
|
||||||
|
diff_config,
|
||||||
|
&mut diff_state,
|
||||||
|
)?;
|
||||||
|
left_row.kind = result.kind;
|
||||||
|
right_row.kind = result.kind;
|
||||||
|
left_row.arg_diff = result.left_args_diff;
|
||||||
|
right_row.arg_diff = result.right_args_diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
let total = left_out.insts.len().max(right_out.insts.len());
|
let max_score = left_ops.len() as u64 * PENALTY_INSERT_DELETE;
|
||||||
let percent = if diff_state.diff_count >= total {
|
let diff_score = diff_state.diff_score.min(max_score);
|
||||||
0.0
|
let match_percent = if max_score == 0 {
|
||||||
|
100.0
|
||||||
} else {
|
} else {
|
||||||
((total - diff_state.diff_count) as f32 / total as f32) * 100.0
|
((1.0 - (diff_score as f64 / max_score as f64)) * 100.0) as f32
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
ObjSymbolDiff {
|
SymbolDiff {
|
||||||
symbol_ref: left_symbol_ref,
|
target_symbol: Some(right_symbol_idx),
|
||||||
target_symbol: Some(right_symbol_ref),
|
match_percent: Some(match_percent),
|
||||||
instructions: left_diff,
|
diff_score: Some((diff_score, max_score)),
|
||||||
match_percent: Some(percent),
|
instruction_rows: left_rows,
|
||||||
},
|
},
|
||||||
ObjSymbolDiff {
|
SymbolDiff {
|
||||||
symbol_ref: right_symbol_ref,
|
target_symbol: Some(left_symbol_idx),
|
||||||
target_symbol: Some(left_symbol_ref),
|
match_percent: Some(match_percent),
|
||||||
instructions: right_diff,
|
diff_score: Some((diff_score, max_score)),
|
||||||
match_percent: Some(percent),
|
instruction_rows: right_rows,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn diff_instructions(
|
fn diff_instructions(
|
||||||
left_diff: &mut Vec<ObjInsDiff>,
|
left_insts: &[InstructionRef],
|
||||||
right_diff: &mut Vec<ObjInsDiff>,
|
right_insts: &[InstructionRef],
|
||||||
left_code: &ProcessCodeResult,
|
) -> Result<(Vec<InstructionDiffRow>, Vec<InstructionDiffRow>)> {
|
||||||
right_code: &ProcessCodeResult,
|
let left_ops = left_insts.iter().map(|i| i.opcode).collect::<Vec<_>>();
|
||||||
) -> Result<()> {
|
let right_ops = right_insts.iter().map(|i| i.opcode).collect::<Vec<_>>();
|
||||||
let ops =
|
let ops = similar::capture_diff_slices(similar::Algorithm::Patience, &left_ops, &right_ops);
|
||||||
capture_diff_slices_deadline(Algorithm::Patience, &left_code.ops, &right_code.ops, None);
|
|
||||||
if ops.is_empty() {
|
if ops.is_empty() {
|
||||||
left_diff.extend(
|
ensure!(left_insts.len() == right_insts.len());
|
||||||
left_code
|
let left_diff = left_insts
|
||||||
.insts
|
.iter()
|
||||||
.iter()
|
.map(|i| InstructionDiffRow { ins_ref: Some(*i), ..Default::default() })
|
||||||
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }),
|
.collect();
|
||||||
);
|
let right_diff = right_insts
|
||||||
right_diff.extend(
|
.iter()
|
||||||
right_code
|
.map(|i| InstructionDiffRow { ins_ref: Some(*i), ..Default::default() })
|
||||||
.insts
|
.collect();
|
||||||
.iter()
|
return Ok((left_diff, right_diff));
|
||||||
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }),
|
|
||||||
);
|
|
||||||
return Ok(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let row_count = ops
|
||||||
|
.iter()
|
||||||
|
.map(|op| match *op {
|
||||||
|
similar::DiffOp::Equal { len, .. } => len,
|
||||||
|
similar::DiffOp::Delete { old_len, .. } => old_len,
|
||||||
|
similar::DiffOp::Insert { new_len, .. } => new_len,
|
||||||
|
similar::DiffOp::Replace { old_len, new_len, .. } => old_len.max(new_len),
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
let mut left_diff = Vec::<InstructionDiffRow>::with_capacity(row_count);
|
||||||
|
let mut right_diff = Vec::<InstructionDiffRow>::with_capacity(row_count);
|
||||||
for op in ops {
|
for op in ops {
|
||||||
let (_tag, left_range, right_range) = op.as_tag_tuple();
|
let (_tag, left_range, right_range) = op.as_tag_tuple();
|
||||||
let len = max(left_range.len(), right_range.len());
|
let len = left_range.len().max(right_range.len());
|
||||||
left_diff.extend(
|
left_diff.extend(
|
||||||
left_code.insts[left_range.clone()]
|
left_range
|
||||||
.iter()
|
.clone()
|
||||||
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }),
|
.map(|i| InstructionDiffRow { ins_ref: Some(left_insts[i]), ..Default::default() }),
|
||||||
);
|
);
|
||||||
right_diff.extend(
|
right_diff.extend(
|
||||||
right_code.insts[right_range.clone()]
|
right_range.clone().map(|i| InstructionDiffRow {
|
||||||
.iter()
|
ins_ref: Some(right_insts[i]),
|
||||||
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }),
|
..Default::default()
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
if left_range.len() < len {
|
if left_range.len() < len {
|
||||||
left_diff.extend((left_range.len()..len).map(|_| ObjInsDiff::default()));
|
left_diff.extend((left_range.len()..len).map(|_| InstructionDiffRow::default()));
|
||||||
}
|
}
|
||||||
if right_range.len() < len {
|
if right_range.len() < len {
|
||||||
right_diff.extend((right_range.len()..len).map(|_| ObjInsDiff::default()));
|
right_diff.extend((right_range.len()..len).map(|_| InstructionDiffRow::default()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok((left_diff, right_diff))
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_branches(vec: &mut [ObjInsDiff]) {
|
fn arg_to_string(arg: &InstructionArg, reloc: Option<ResolvedRelocation>) -> String {
|
||||||
let mut branch_idx = 0usize;
|
match arg {
|
||||||
|
InstructionArg::Value(arg) => arg.to_string(),
|
||||||
|
InstructionArg::Reloc => {
|
||||||
|
reloc.as_ref().map_or_else(|| "<unknown>".to_string(), |r| r.symbol.name.clone())
|
||||||
|
}
|
||||||
|
InstructionArg::BranchDest(arg) => arg.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_branches(ops: &[InstructionRef], rows: &mut [InstructionDiffRow]) {
|
||||||
|
let mut branch_idx = 0u32;
|
||||||
// Map addresses to indices
|
// Map addresses to indices
|
||||||
let mut addr_map = BTreeMap::<u64, usize>::new();
|
let mut addr_map = BTreeMap::<u64, u32>::new();
|
||||||
for (i, ins_diff) in vec.iter().enumerate() {
|
for (i, ins_diff) in rows.iter().enumerate() {
|
||||||
if let Some(ins) = &ins_diff.ins {
|
if let Some(ins) = ins_diff.ins_ref {
|
||||||
addr_map.insert(ins.address, i);
|
addr_map.insert(ins.address, i as u32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Generate branches
|
// Generate branches
|
||||||
let mut branches = BTreeMap::<usize, ObjInsBranchFrom>::new();
|
let mut branches = BTreeMap::<u32, InstructionBranchFrom>::new();
|
||||||
for (i, ins_diff) in vec.iter_mut().enumerate() {
|
for ((i, ins_diff), ins) in
|
||||||
if let Some(ins) = &ins_diff.ins {
|
rows.iter_mut().enumerate().filter(|(_, row)| row.ins_ref.is_some()).zip(ops)
|
||||||
if let Some(ins_idx) = ins.branch_dest.and_then(|a| addr_map.get(&a)) {
|
{
|
||||||
if let Some(branch) = branches.get_mut(ins_idx) {
|
if let Some(ins_idx) = ins.branch_dest.and_then(|a| addr_map.get(&a).copied()) {
|
||||||
ins_diff.branch_to =
|
match branches.entry(ins_idx) {
|
||||||
Some(ObjInsBranchTo { ins_idx: *ins_idx, branch_idx: branch.branch_idx });
|
btree_map::Entry::Vacant(e) => {
|
||||||
branch.ins_idx.push(i);
|
ins_diff.branch_to = Some(InstructionBranchTo { ins_idx, branch_idx });
|
||||||
} else {
|
e.insert(InstructionBranchFrom { ins_idx: vec![i as u32], branch_idx });
|
||||||
ins_diff.branch_to = Some(ObjInsBranchTo { ins_idx: *ins_idx, branch_idx });
|
|
||||||
branches.insert(*ins_idx, ObjInsBranchFrom { ins_idx: vec![i], branch_idx });
|
|
||||||
branch_idx += 1;
|
branch_idx += 1;
|
||||||
}
|
}
|
||||||
|
btree_map::Entry::Occupied(e) => {
|
||||||
|
let branch = e.into_mut();
|
||||||
|
ins_diff.branch_to =
|
||||||
|
Some(InstructionBranchTo { ins_idx, branch_idx: branch.branch_idx });
|
||||||
|
branch.ins_idx.push(i as u32);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Store branch from
|
// Store branch from
|
||||||
for (i, branch) in branches {
|
for (i, branch) in branches {
|
||||||
vec[i].branch_from = Some(branch);
|
rows[i as usize].branch_from = Some(branch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn address_eq(left: &ObjReloc, right: &ObjReloc) -> bool {
|
pub(crate) fn address_eq(left: ResolvedRelocation, right: ResolvedRelocation) -> bool {
|
||||||
left.target.address as i64 + left.addend == right.target.address as i64 + right.addend
|
if right.symbol.size == 0 && left.symbol.size != 0 {
|
||||||
|
// The base relocation is against a pool but the target relocation isn't.
|
||||||
|
// This can happen in rare cases where the compiler will generate a pool+addend relocation
|
||||||
|
// in the base's data, but the one detected in the target is direct with no addend.
|
||||||
|
// Just check that the final address is the same so these count as a match.
|
||||||
|
left.symbol.address as i64 + left.relocation.addend
|
||||||
|
== right.symbol.address as i64 + right.relocation.addend
|
||||||
|
} else {
|
||||||
|
// But otherwise, if the compiler isn't using a pool, we're more strict and check that the
|
||||||
|
// target symbol address and relocation addend both match exactly.
|
||||||
|
left.symbol.address == right.symbol.address
|
||||||
|
&& left.relocation.addend == right.relocation.addend
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn section_name_eq(
|
pub(crate) fn section_name_eq(
|
||||||
left_obj: &ObjInfo,
|
left_obj: &Object,
|
||||||
right_obj: &ObjInfo,
|
right_obj: &Object,
|
||||||
left_orig_section_index: usize,
|
left_section_index: usize,
|
||||||
right_orig_section_index: usize,
|
right_section_index: usize,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let Some(left_section) =
|
left_obj.sections.get(left_section_index).is_some_and(|left_section| {
|
||||||
left_obj.sections.iter().find(|s| s.orig_index == left_orig_section_index)
|
right_obj
|
||||||
else {
|
.sections
|
||||||
return false;
|
.get(right_section_index)
|
||||||
};
|
.is_some_and(|right_section| left_section.name == right_section.name)
|
||||||
let Some(right_section) =
|
})
|
||||||
right_obj.sections.iter().find(|s| s.orig_index == right_orig_section_index)
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
left_section.name == right_section.name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reloc_eq(
|
fn reloc_eq(
|
||||||
config: &DiffObjConfig,
|
left_obj: &Object,
|
||||||
left_obj: &ObjInfo,
|
right_obj: &Object,
|
||||||
right_obj: &ObjInfo,
|
left_ins: ResolvedInstructionRef,
|
||||||
left_reloc: Option<&ObjReloc>,
|
right_ins: ResolvedInstructionRef,
|
||||||
right_reloc: Option<&ObjReloc>,
|
diff_config: &DiffObjConfig,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let (Some(left), Some(right)) = (left_reloc, right_reloc) else {
|
let relax_reloc_diffs = diff_config.function_reloc_diffs == FunctionRelocDiffs::None;
|
||||||
return false;
|
let (left_reloc, right_reloc) = match (left_ins.relocation, right_ins.relocation) {
|
||||||
|
(Some(left_reloc), Some(right_reloc)) => (left_reloc, right_reloc),
|
||||||
|
// If relocations are relaxed, match if left is missing a reloc
|
||||||
|
(None, Some(_)) => return relax_reloc_diffs,
|
||||||
|
(None, None) => return true,
|
||||||
|
_ => return false,
|
||||||
};
|
};
|
||||||
if left.flags != right.flags {
|
if left_reloc.relocation.flags != right_reloc.relocation.flags {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if config.relax_reloc_diffs {
|
if relax_reloc_diffs {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let symbol_name_matches = left.target.name == right.target.name;
|
let symbol_name_addend_matches = left_reloc.symbol.name == right_reloc.symbol.name
|
||||||
match (&left.target.orig_section_index, &right.target.orig_section_index) {
|
&& left_reloc.relocation.addend == right_reloc.relocation.addend;
|
||||||
|
match (&left_reloc.symbol.section, &right_reloc.symbol.section) {
|
||||||
(Some(sl), Some(sr)) => {
|
(Some(sl), Some(sr)) => {
|
||||||
// Match if section and name or address match
|
// Match if section and name or address match
|
||||||
section_name_eq(left_obj, right_obj, *sl, *sr)
|
section_name_eq(left_obj, right_obj, *sl, *sr)
|
||||||
&& (symbol_name_matches || address_eq(left, right))
|
&& (diff_config.function_reloc_diffs == FunctionRelocDiffs::DataValue
|
||||||
|
|| symbol_name_addend_matches
|
||||||
|
|| address_eq(left_reloc, right_reloc))
|
||||||
|
&& (diff_config.function_reloc_diffs == FunctionRelocDiffs::NameAddress
|
||||||
|
|| left_reloc.symbol.kind != SymbolKind::Object
|
||||||
|
|| right_reloc.symbol.size == 0 // Likely a pool symbol like ...data, don't treat this as a diff
|
||||||
|
|| display_ins_data_literals(left_obj, left_ins)
|
||||||
|
== display_ins_data_literals(right_obj, right_ins))
|
||||||
}
|
}
|
||||||
(Some(_), None) => false,
|
|
||||||
(None, Some(_)) => {
|
(None, Some(_)) => {
|
||||||
// Match if possibly stripped weak symbol
|
// Match if possibly stripped weak symbol
|
||||||
symbol_name_matches && right.target.flags.0.contains(ObjSymbolFlags::Weak)
|
symbol_name_addend_matches && right_reloc.symbol.flags.contains(SymbolFlag::Weak)
|
||||||
}
|
}
|
||||||
(None, None) => symbol_name_matches,
|
(Some(_), None) | (None, None) => symbol_name_addend_matches,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn arg_eq(
|
fn arg_eq(
|
||||||
config: &DiffObjConfig,
|
left_obj: &Object,
|
||||||
left_obj: &ObjInfo,
|
right_obj: &Object,
|
||||||
right_obj: &ObjInfo,
|
left_row: &InstructionDiffRow,
|
||||||
left: &ObjInsArg,
|
right_row: &InstructionDiffRow,
|
||||||
right: &ObjInsArg,
|
left_arg: &InstructionArg,
|
||||||
left_diff: &ObjInsDiff,
|
right_arg: &InstructionArg,
|
||||||
right_diff: &ObjInsDiff,
|
left_ins: ResolvedInstructionRef,
|
||||||
|
right_ins: ResolvedInstructionRef,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
match left {
|
match left_arg {
|
||||||
ObjInsArg::PlainText(l) => match right {
|
InstructionArg::Value(l) => match right_arg {
|
||||||
ObjInsArg::PlainText(r) => l == r,
|
InstructionArg::Value(r) => l.loose_eq(r),
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
ObjInsArg::Arg(l) => match right {
|
|
||||||
ObjInsArg::Arg(r) => l == r,
|
|
||||||
// If relocations are relaxed, match if left is a constant and right is a reloc
|
// If relocations are relaxed, match if left is a constant and right is a reloc
|
||||||
// Useful for instances where the target object is created without relocations
|
// Useful for instances where the target object is created without relocations
|
||||||
ObjInsArg::Reloc => config.relax_reloc_diffs,
|
InstructionArg::Reloc => diff_config.function_reloc_diffs == FunctionRelocDiffs::None,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
ObjInsArg::Reloc => {
|
InstructionArg::Reloc => {
|
||||||
matches!(right, ObjInsArg::Reloc)
|
matches!(right_arg, InstructionArg::Reloc)
|
||||||
&& reloc_eq(
|
&& reloc_eq(left_obj, right_obj, left_ins, right_ins, diff_config)
|
||||||
config,
|
|
||||||
left_obj,
|
|
||||||
right_obj,
|
|
||||||
left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
|
||||||
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
ObjInsArg::BranchDest(_) => match right {
|
InstructionArg::BranchDest(_) => match right_arg {
|
||||||
// Compare dest instruction idx after diffing
|
// Compare dest instruction idx after diffing
|
||||||
ObjInsArg::BranchDest(_) => {
|
InstructionArg::BranchDest(_) => {
|
||||||
left_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
left_row.branch_to.as_ref().map(|b| b.ins_idx)
|
||||||
== right_diff.branch_to.as_ref().map(|b| b.ins_idx)
|
== right_row.branch_to.as_ref().map(|b| b.ins_idx)
|
||||||
}
|
}
|
||||||
// If relocations are relaxed, match if left is a constant and right is a reloc
|
// If relocations are relaxed, match if left is a constant and right is a reloc
|
||||||
// Useful for instances where the target object is created without relocations
|
// Useful for instances where the target object is created without relocations
|
||||||
ObjInsArg::Reloc => config.relax_reloc_diffs,
|
InstructionArg::Reloc => diff_config.function_reloc_diffs == FunctionRelocDiffs::None,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct InsDiffState {
|
struct InstructionDiffState {
|
||||||
diff_count: usize,
|
diff_score: u64,
|
||||||
left_arg_idx: usize,
|
left_arg_idx: u32,
|
||||||
right_arg_idx: usize,
|
right_arg_idx: u32,
|
||||||
left_args_idx: BTreeMap<String, usize>,
|
left_args_idx: BTreeMap<String, u32>,
|
||||||
right_args_idx: BTreeMap<String, usize>,
|
right_args_idx: BTreeMap<String, u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct InsDiffResult {
|
struct InstructionDiffResult {
|
||||||
kind: ObjInsDiffKind,
|
kind: InstructionDiffKind,
|
||||||
left_args_diff: Vec<Option<ObjInsArgDiff>>,
|
left_args_diff: Vec<InstructionArgDiffIndex>,
|
||||||
right_args_diff: Vec<Option<ObjInsArgDiff>>,
|
right_args_diff: Vec<InstructionArgDiffIndex>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compare_ins(
|
impl InstructionDiffResult {
|
||||||
config: &DiffObjConfig,
|
#[inline]
|
||||||
left_obj: &ObjInfo,
|
const fn new(kind: InstructionDiffKind) -> Self {
|
||||||
right_obj: &ObjInfo,
|
Self { kind, left_args_diff: Vec::new(), right_args_diff: Vec::new() }
|
||||||
left: &ObjInsDiff,
|
}
|
||||||
right: &ObjInsDiff,
|
}
|
||||||
state: &mut InsDiffState,
|
|
||||||
) -> Result<InsDiffResult> {
|
fn diff_instruction(
|
||||||
let mut result = InsDiffResult::default();
|
left_obj: &Object,
|
||||||
if let (Some(left_ins), Some(right_ins)) = (&left.ins, &right.ins) {
|
right_obj: &Object,
|
||||||
// Count only non-PlainText args
|
left_symbol_idx: usize,
|
||||||
let left_args_count = left_ins.iter_args().count();
|
right_symbol_idx: usize,
|
||||||
let right_args_count = right_ins.iter_args().count();
|
l: Option<InstructionRef>,
|
||||||
if left_args_count != right_args_count || left_ins.op != right_ins.op {
|
r: Option<InstructionRef>,
|
||||||
// Totally different op
|
left_row: &InstructionDiffRow,
|
||||||
result.kind = ObjInsDiffKind::Replace;
|
right_row: &InstructionDiffRow,
|
||||||
state.diff_count += 1;
|
diff_config: &DiffObjConfig,
|
||||||
return Ok(result);
|
state: &mut InstructionDiffState,
|
||||||
|
) -> Result<InstructionDiffResult> {
|
||||||
|
let (l, r) = match (l, r) {
|
||||||
|
(Some(l), Some(r)) => (l, r),
|
||||||
|
(Some(_), None) => {
|
||||||
|
state.diff_score += PENALTY_INSERT_DELETE;
|
||||||
|
return Ok(InstructionDiffResult::new(InstructionDiffKind::Delete));
|
||||||
}
|
}
|
||||||
|
(None, Some(_)) => {
|
||||||
|
state.diff_score += PENALTY_INSERT_DELETE;
|
||||||
|
return Ok(InstructionDiffResult::new(InstructionDiffKind::Insert));
|
||||||
|
}
|
||||||
|
(None, None) => return Ok(InstructionDiffResult::new(InstructionDiffKind::None)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// If opcodes don't match, replace
|
||||||
|
if l.opcode != r.opcode {
|
||||||
|
state.diff_score += PENALTY_REPLACE;
|
||||||
|
return Ok(InstructionDiffResult::new(InstructionDiffKind::Replace));
|
||||||
|
}
|
||||||
|
|
||||||
|
let left_resolved = left_obj
|
||||||
|
.resolve_instruction_ref(left_symbol_idx, l)
|
||||||
|
.context("Failed to resolve left instruction")?;
|
||||||
|
let right_resolved = right_obj
|
||||||
|
.resolve_instruction_ref(right_symbol_idx, r)
|
||||||
|
.context("Failed to resolve right instruction")?;
|
||||||
|
|
||||||
|
if left_resolved.code != right_resolved.code
|
||||||
|
|| !reloc_eq(left_obj, right_obj, left_resolved, right_resolved, diff_config)
|
||||||
|
{
|
||||||
|
// If either the raw code bytes or relocations don't match, process instructions and compare args
|
||||||
|
let left_ins = left_obj.arch.process_instruction(left_resolved, diff_config)?;
|
||||||
|
let right_ins = right_obj.arch.process_instruction(right_resolved, diff_config)?;
|
||||||
|
if left_ins.args.len() != right_ins.args.len() {
|
||||||
|
state.diff_score += PENALTY_REPLACE;
|
||||||
|
return Ok(InstructionDiffResult::new(InstructionDiffKind::Replace));
|
||||||
|
}
|
||||||
|
let mut result = InstructionDiffResult::new(InstructionDiffKind::None);
|
||||||
if left_ins.mnemonic != right_ins.mnemonic {
|
if left_ins.mnemonic != right_ins.mnemonic {
|
||||||
// Same op but different mnemonic, still cmp args
|
state.diff_score += PENALTY_REG_DIFF;
|
||||||
result.kind = ObjInsDiffKind::OpMismatch;
|
result.kind = InstructionDiffKind::OpMismatch;
|
||||||
state.diff_count += 1;
|
|
||||||
}
|
}
|
||||||
for (a, b) in left_ins.iter_args().zip(right_ins.iter_args()) {
|
for (a, b) in left_ins.args.iter().zip(right_ins.args.iter()) {
|
||||||
if arg_eq(config, left_obj, right_obj, a, b, left, right) {
|
if arg_eq(
|
||||||
result.left_args_diff.push(None);
|
left_obj,
|
||||||
result.right_args_diff.push(None);
|
right_obj,
|
||||||
|
left_row,
|
||||||
|
right_row,
|
||||||
|
a,
|
||||||
|
b,
|
||||||
|
left_resolved,
|
||||||
|
right_resolved,
|
||||||
|
diff_config,
|
||||||
|
) {
|
||||||
|
result.left_args_diff.push(InstructionArgDiffIndex::NONE);
|
||||||
|
result.right_args_diff.push(InstructionArgDiffIndex::NONE);
|
||||||
} else {
|
} else {
|
||||||
if result.kind == ObjInsDiffKind::None {
|
state.diff_score += if let InstructionArg::Value(
|
||||||
result.kind = ObjInsDiffKind::ArgMismatch;
|
InstructionArgValue::Signed(_) | InstructionArgValue::Unsigned(_),
|
||||||
state.diff_count += 1;
|
) = a
|
||||||
|
{
|
||||||
|
PENALTY_IMM_DIFF
|
||||||
|
} else {
|
||||||
|
PENALTY_REG_DIFF
|
||||||
|
};
|
||||||
|
if result.kind == InstructionDiffKind::None {
|
||||||
|
result.kind = InstructionDiffKind::ArgMismatch;
|
||||||
}
|
}
|
||||||
let a_str = match a {
|
let a_str = arg_to_string(a, left_resolved.relocation);
|
||||||
ObjInsArg::PlainText(arg) => arg.to_string(),
|
let a_diff = match state.left_args_idx.entry(a_str) {
|
||||||
ObjInsArg::Arg(arg) => arg.to_string(),
|
btree_map::Entry::Vacant(e) => {
|
||||||
ObjInsArg::Reloc => left_ins
|
let idx = state.left_arg_idx;
|
||||||
.reloc
|
state.left_arg_idx = idx + 1;
|
||||||
.as_ref()
|
e.insert(idx);
|
||||||
.map_or_else(|| "<unknown>".to_string(), |r| r.target.name.clone()),
|
idx
|
||||||
ObjInsArg::BranchDest(arg) => arg.to_string(),
|
}
|
||||||
|
btree_map::Entry::Occupied(e) => *e.get(),
|
||||||
};
|
};
|
||||||
let a_diff = if let Some(idx) = state.left_args_idx.get(&a_str) {
|
let b_str = arg_to_string(b, right_resolved.relocation);
|
||||||
ObjInsArgDiff { idx: *idx }
|
let b_diff = match state.right_args_idx.entry(b_str) {
|
||||||
} else {
|
btree_map::Entry::Vacant(e) => {
|
||||||
let idx = state.left_arg_idx;
|
let idx = state.right_arg_idx;
|
||||||
state.left_args_idx.insert(a_str, idx);
|
state.right_arg_idx = idx + 1;
|
||||||
state.left_arg_idx += 1;
|
e.insert(idx);
|
||||||
ObjInsArgDiff { idx }
|
idx
|
||||||
|
}
|
||||||
|
btree_map::Entry::Occupied(e) => *e.get(),
|
||||||
};
|
};
|
||||||
let b_str = match b {
|
result.left_args_diff.push(InstructionArgDiffIndex::new(a_diff));
|
||||||
ObjInsArg::PlainText(arg) => arg.to_string(),
|
result.right_args_diff.push(InstructionArgDiffIndex::new(b_diff));
|
||||||
ObjInsArg::Arg(arg) => arg.to_string(),
|
|
||||||
ObjInsArg::Reloc => right_ins
|
|
||||||
.reloc
|
|
||||||
.as_ref()
|
|
||||||
.map_or_else(|| "<unknown>".to_string(), |r| r.target.name.clone()),
|
|
||||||
ObjInsArg::BranchDest(arg) => arg.to_string(),
|
|
||||||
};
|
|
||||||
let b_diff = if let Some(idx) = state.right_args_idx.get(&b_str) {
|
|
||||||
ObjInsArgDiff { idx: *idx }
|
|
||||||
} else {
|
|
||||||
let idx = state.right_arg_idx;
|
|
||||||
state.right_args_idx.insert(b_str, idx);
|
|
||||||
state.right_arg_idx += 1;
|
|
||||||
ObjInsArgDiff { idx }
|
|
||||||
};
|
|
||||||
result.left_args_diff.push(Some(a_diff));
|
|
||||||
result.right_args_diff.push(Some(b_diff));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if left.ins.is_some() {
|
return Ok(result);
|
||||||
result.kind = ObjInsDiffKind::Delete;
|
|
||||||
state.diff_count += 1;
|
|
||||||
} else {
|
|
||||||
result.kind = ObjInsDiffKind::Insert;
|
|
||||||
state.diff_count += 1;
|
|
||||||
}
|
}
|
||||||
Ok(result)
|
|
||||||
|
Ok(InstructionDiffResult::new(InstructionDiffKind::None))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,117 +1,239 @@
|
|||||||
use std::cmp::{max, min, Ordering};
|
use alloc::{vec, vec::Vec};
|
||||||
|
use core::{cmp::Ordering, ops::Range};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{Result, anyhow};
|
||||||
use similar::{capture_diff_slices_deadline, get_diff_ratio, Algorithm};
|
use similar::{Algorithm, capture_diff_slices, get_diff_ratio};
|
||||||
|
|
||||||
use crate::{
|
use super::{
|
||||||
diff::{ObjDataDiff, ObjDataDiffKind, ObjSectionDiff, ObjSymbolDiff},
|
DataDiff, DataDiffKind, DataRelocationDiff, ObjectDiff, SectionDiff, SymbolDiff,
|
||||||
obj::{ObjInfo, ObjSection, SymbolRef},
|
code::{address_eq, section_name_eq},
|
||||||
};
|
};
|
||||||
|
use crate::obj::{Object, Relocation, ResolvedRelocation, Symbol, SymbolFlag, SymbolKind};
|
||||||
|
|
||||||
pub fn diff_bss_symbol(
|
pub fn diff_bss_symbol(
|
||||||
left_obj: &ObjInfo,
|
left_obj: &Object,
|
||||||
right_obj: &ObjInfo,
|
right_obj: &Object,
|
||||||
left_symbol_ref: SymbolRef,
|
left_symbol_ref: usize,
|
||||||
right_symbol_ref: SymbolRef,
|
right_symbol_ref: usize,
|
||||||
) -> Result<(ObjSymbolDiff, ObjSymbolDiff)> {
|
) -> Result<(SymbolDiff, SymbolDiff)> {
|
||||||
let (_, left_symbol) = left_obj.section_symbol(left_symbol_ref);
|
let left_symbol = &left_obj.symbols[left_symbol_ref];
|
||||||
let (_, right_symbol) = right_obj.section_symbol(right_symbol_ref);
|
let right_symbol = &right_obj.symbols[right_symbol_ref];
|
||||||
let percent = if left_symbol.size == right_symbol.size { 100.0 } else { 50.0 };
|
let percent = if left_symbol.size == right_symbol.size { 100.0 } else { 50.0 };
|
||||||
Ok((
|
Ok((
|
||||||
ObjSymbolDiff {
|
SymbolDiff {
|
||||||
symbol_ref: left_symbol_ref,
|
|
||||||
target_symbol: Some(right_symbol_ref),
|
target_symbol: Some(right_symbol_ref),
|
||||||
instructions: vec![],
|
|
||||||
match_percent: Some(percent),
|
match_percent: Some(percent),
|
||||||
|
diff_score: None,
|
||||||
|
instruction_rows: vec![],
|
||||||
},
|
},
|
||||||
ObjSymbolDiff {
|
SymbolDiff {
|
||||||
symbol_ref: right_symbol_ref,
|
|
||||||
target_symbol: Some(left_symbol_ref),
|
target_symbol: Some(left_symbol_ref),
|
||||||
instructions: vec![],
|
|
||||||
match_percent: Some(percent),
|
match_percent: Some(percent),
|
||||||
|
diff_score: None,
|
||||||
|
instruction_rows: vec![],
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn no_diff_symbol(_obj: &ObjInfo, symbol_ref: SymbolRef) -> ObjSymbolDiff {
|
fn reloc_eq(
|
||||||
ObjSymbolDiff { symbol_ref, target_symbol: None, instructions: vec![], match_percent: None }
|
left_obj: &Object,
|
||||||
|
right_obj: &Object,
|
||||||
|
left: ResolvedRelocation,
|
||||||
|
right: ResolvedRelocation,
|
||||||
|
) -> bool {
|
||||||
|
if left.relocation.flags != right.relocation.flags {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let symbol_name_addend_matches =
|
||||||
|
left.symbol.name == right.symbol.name && left.relocation.addend == right.relocation.addend;
|
||||||
|
match (left.symbol.section, right.symbol.section) {
|
||||||
|
(Some(sl), Some(sr)) => {
|
||||||
|
// Match if section and name+addend or address match
|
||||||
|
section_name_eq(left_obj, right_obj, sl, sr)
|
||||||
|
&& (symbol_name_addend_matches || address_eq(left, right))
|
||||||
|
}
|
||||||
|
(None, Some(_)) => {
|
||||||
|
// Match if possibly stripped weak symbol
|
||||||
|
symbol_name_addend_matches && right.symbol.flags.contains(SymbolFlag::Weak)
|
||||||
|
}
|
||||||
|
(Some(_), None) | (None, None) => symbol_name_addend_matches,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn resolve_relocation<'obj>(
|
||||||
|
symbols: &'obj [Symbol],
|
||||||
|
reloc: &'obj Relocation,
|
||||||
|
) -> ResolvedRelocation<'obj> {
|
||||||
|
let symbol = &symbols[reloc.target_symbol];
|
||||||
|
ResolvedRelocation { relocation: reloc, symbol }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compares relocations contained with a certain data range.
|
||||||
|
fn diff_data_relocs_for_range<'left, 'right>(
|
||||||
|
left_obj: &'left Object,
|
||||||
|
right_obj: &'right Object,
|
||||||
|
left_section_idx: usize,
|
||||||
|
right_section_idx: usize,
|
||||||
|
left_range: Range<usize>,
|
||||||
|
right_range: Range<usize>,
|
||||||
|
) -> Vec<(DataDiffKind, Option<ResolvedRelocation<'left>>, Option<ResolvedRelocation<'right>>)> {
|
||||||
|
let left_section = &left_obj.sections[left_section_idx];
|
||||||
|
let right_section = &right_obj.sections[right_section_idx];
|
||||||
|
let mut diffs = Vec::new();
|
||||||
|
for left_reloc in left_section.relocations.iter() {
|
||||||
|
if !left_range.contains(&(left_reloc.address as usize)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let left_offset = left_reloc.address as usize - left_range.start;
|
||||||
|
let left_reloc = resolve_relocation(&left_obj.symbols, left_reloc);
|
||||||
|
let Some(right_reloc) = right_section.relocations.iter().find(|r| {
|
||||||
|
if !right_range.contains(&(r.address as usize)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let right_offset = r.address as usize - right_range.start;
|
||||||
|
right_offset == left_offset
|
||||||
|
}) else {
|
||||||
|
diffs.push((DataDiffKind::Delete, Some(left_reloc), None));
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let right_reloc = resolve_relocation(&right_obj.symbols, right_reloc);
|
||||||
|
if reloc_eq(left_obj, right_obj, left_reloc, right_reloc) {
|
||||||
|
diffs.push((DataDiffKind::None, Some(left_reloc), Some(right_reloc)));
|
||||||
|
} else {
|
||||||
|
diffs.push((DataDiffKind::Replace, Some(left_reloc), Some(right_reloc)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for right_reloc in right_section.relocations.iter() {
|
||||||
|
if !right_range.contains(&(right_reloc.address as usize)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let right_offset = right_reloc.address as usize - right_range.start;
|
||||||
|
let right_reloc = resolve_relocation(&right_obj.symbols, right_reloc);
|
||||||
|
let Some(_) = left_section.relocations.iter().find(|r| {
|
||||||
|
if !left_range.contains(&(r.address as usize)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let left_offset = r.address as usize - left_range.start;
|
||||||
|
left_offset == right_offset
|
||||||
|
}) else {
|
||||||
|
diffs.push((DataDiffKind::Insert, None, Some(right_reloc)));
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
// No need to check the cases for relocations being deleted or matching again.
|
||||||
|
// They were already handled in the loop over the left relocs.
|
||||||
|
}
|
||||||
|
diffs
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn no_diff_data_section(obj: &Object, section_idx: usize) -> Result<SectionDiff> {
|
||||||
|
let section = &obj.sections[section_idx];
|
||||||
|
let len = section.data.len();
|
||||||
|
let data = §ion.data[0..len];
|
||||||
|
|
||||||
|
let data_diff =
|
||||||
|
vec![DataDiff { data: data.to_vec(), kind: DataDiffKind::None, len, ..Default::default() }];
|
||||||
|
|
||||||
|
let mut reloc_diffs = Vec::new();
|
||||||
|
for reloc in section.relocations.iter() {
|
||||||
|
let reloc_len = obj.arch.data_reloc_size(reloc.flags);
|
||||||
|
let range = reloc.address as usize..reloc.address as usize + reloc_len;
|
||||||
|
reloc_diffs.push(DataRelocationDiff {
|
||||||
|
reloc: reloc.clone(),
|
||||||
|
kind: DataDiffKind::None,
|
||||||
|
range,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(SectionDiff { match_percent: Some(0.0), data_diff, reloc_diff: reloc_diffs })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compare the data sections of two object files.
|
/// Compare the data sections of two object files.
|
||||||
pub fn diff_data_section(
|
pub fn diff_data_section(
|
||||||
left: &ObjSection,
|
left_obj: &Object,
|
||||||
right: &ObjSection,
|
right_obj: &Object,
|
||||||
left_section_diff: &ObjSectionDiff,
|
left_diff: &ObjectDiff,
|
||||||
right_section_diff: &ObjSectionDiff,
|
right_diff: &ObjectDiff,
|
||||||
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
|
left_section_idx: usize,
|
||||||
let left_max =
|
right_section_idx: usize,
|
||||||
left.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0).min(left.size);
|
) -> Result<(SectionDiff, SectionDiff)> {
|
||||||
let right_max =
|
let left_section = &left_obj.sections[left_section_idx];
|
||||||
right.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0).min(right.size);
|
let right_section = &right_obj.sections[right_section_idx];
|
||||||
let left_data = &left.data[..left_max as usize];
|
let left_max = symbols_matching_section(&left_obj.symbols, left_section_idx)
|
||||||
let right_data = &right.data[..right_max as usize];
|
.filter_map(|(_, s)| s.address.checked_sub(left_section.address).map(|a| a + s.size))
|
||||||
let ops = capture_diff_slices_deadline(Algorithm::Patience, left_data, right_data, None);
|
.max()
|
||||||
|
.unwrap_or(0)
|
||||||
|
.min(left_section.size);
|
||||||
|
let right_max = symbols_matching_section(&right_obj.symbols, right_section_idx)
|
||||||
|
.filter_map(|(_, s)| s.address.checked_sub(right_section.address).map(|a| a + s.size))
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0)
|
||||||
|
.min(right_section.size);
|
||||||
|
let left_data = &left_section.data[..left_max as usize];
|
||||||
|
let right_data = &right_section.data[..right_max as usize];
|
||||||
|
let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data);
|
||||||
let match_percent = get_diff_ratio(&ops, left_data.len(), right_data.len()) * 100.0;
|
let match_percent = get_diff_ratio(&ops, left_data.len(), right_data.len()) * 100.0;
|
||||||
|
|
||||||
let mut left_diff = Vec::<ObjDataDiff>::new();
|
let mut left_data_diff = Vec::<DataDiff>::new();
|
||||||
let mut right_diff = Vec::<ObjDataDiff>::new();
|
let mut right_data_diff = Vec::<DataDiff>::new();
|
||||||
for op in ops {
|
for op in ops {
|
||||||
let (tag, left_range, right_range) = op.as_tag_tuple();
|
let (tag, left_range, right_range) = op.as_tag_tuple();
|
||||||
let left_len = left_range.len();
|
let left_len = left_range.len();
|
||||||
let right_len = right_range.len();
|
let right_len = right_range.len();
|
||||||
let mut len = max(left_len, right_len);
|
let mut len = left_len.max(right_len);
|
||||||
let kind = match tag {
|
let kind = match tag {
|
||||||
similar::DiffTag::Equal => ObjDataDiffKind::None,
|
similar::DiffTag::Equal => DataDiffKind::None,
|
||||||
similar::DiffTag::Delete => ObjDataDiffKind::Delete,
|
similar::DiffTag::Delete => DataDiffKind::Delete,
|
||||||
similar::DiffTag::Insert => ObjDataDiffKind::Insert,
|
similar::DiffTag::Insert => DataDiffKind::Insert,
|
||||||
similar::DiffTag::Replace => {
|
similar::DiffTag::Replace => {
|
||||||
// Ensure replacements are equal length
|
// Ensure replacements are equal length
|
||||||
len = min(left_len, right_len);
|
len = left_len.min(right_len);
|
||||||
ObjDataDiffKind::Replace
|
DataDiffKind::Replace
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let left_data = &left.data[left_range];
|
let left_data = &left_section.data[left_range];
|
||||||
let right_data = &right.data[right_range];
|
let right_data = &right_section.data[right_range];
|
||||||
left_diff.push(ObjDataDiff {
|
left_data_diff.push(DataDiff {
|
||||||
data: left_data[..min(len, left_data.len())].to_vec(),
|
data: left_data[..len.min(left_data.len())].to_vec(),
|
||||||
kind,
|
kind,
|
||||||
len,
|
len,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
right_diff.push(ObjDataDiff {
|
right_data_diff.push(DataDiff {
|
||||||
data: right_data[..min(len, right_data.len())].to_vec(),
|
data: right_data[..len.min(right_data.len())].to_vec(),
|
||||||
kind,
|
kind,
|
||||||
len,
|
len,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
if kind == ObjDataDiffKind::Replace {
|
if kind == DataDiffKind::Replace {
|
||||||
match left_len.cmp(&right_len) {
|
match left_len.cmp(&right_len) {
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
let len = right_len - left_len;
|
let len = right_len - left_len;
|
||||||
left_diff.push(ObjDataDiff {
|
left_data_diff.push(DataDiff {
|
||||||
data: vec![],
|
data: vec![],
|
||||||
kind: ObjDataDiffKind::Insert,
|
kind: DataDiffKind::Insert,
|
||||||
len,
|
len,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
right_diff.push(ObjDataDiff {
|
right_data_diff.push(DataDiff {
|
||||||
data: right_data[left_len..right_len].to_vec(),
|
data: right_data[left_len..right_len].to_vec(),
|
||||||
kind: ObjDataDiffKind::Insert,
|
kind: DataDiffKind::Insert,
|
||||||
len,
|
len,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Ordering::Greater => {
|
Ordering::Greater => {
|
||||||
let len = left_len - right_len;
|
let len = left_len - right_len;
|
||||||
left_diff.push(ObjDataDiff {
|
left_data_diff.push(DataDiff {
|
||||||
data: left_data[right_len..left_len].to_vec(),
|
data: left_data[right_len..left_len].to_vec(),
|
||||||
kind: ObjDataDiffKind::Delete,
|
kind: DataDiffKind::Delete,
|
||||||
len,
|
len,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
right_diff.push(ObjDataDiff {
|
right_data_diff.push(DataDiff {
|
||||||
data: vec![],
|
data: vec![],
|
||||||
kind: ObjDataDiffKind::Delete,
|
kind: DataDiffKind::Delete,
|
||||||
len,
|
len,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
@@ -121,52 +243,167 @@ pub fn diff_data_section(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (mut left_section_diff, mut right_section_diff) =
|
let mut left_reloc_diffs = Vec::new();
|
||||||
diff_generic_section(left, right, left_section_diff, right_section_diff)?;
|
let mut right_reloc_diffs = Vec::new();
|
||||||
left_section_diff.data_diff = left_diff;
|
for (diff_kind, left_reloc, right_reloc) in diff_data_relocs_for_range(
|
||||||
right_section_diff.data_diff = right_diff;
|
left_obj,
|
||||||
// Use the highest match percent between two options:
|
right_obj,
|
||||||
// - Left symbols matching right symbols by name
|
left_section_idx,
|
||||||
// - Diff of the data itself
|
right_section_idx,
|
||||||
if left_section_diff.match_percent.unwrap_or(-1.0) < match_percent {
|
0..left_max as usize,
|
||||||
left_section_diff.match_percent = Some(match_percent);
|
0..right_max as usize,
|
||||||
right_section_diff.match_percent = Some(match_percent);
|
) {
|
||||||
|
if let Some(left_reloc) = left_reloc {
|
||||||
|
let len = left_obj.arch.data_reloc_size(left_reloc.relocation.flags);
|
||||||
|
let range = left_reloc.relocation.address as usize
|
||||||
|
..left_reloc.relocation.address as usize + len;
|
||||||
|
left_reloc_diffs.push(DataRelocationDiff {
|
||||||
|
reloc: left_reloc.relocation.clone(),
|
||||||
|
kind: diff_kind,
|
||||||
|
range,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(right_reloc) = right_reloc {
|
||||||
|
let len = right_obj.arch.data_reloc_size(right_reloc.relocation.flags);
|
||||||
|
let range = right_reloc.relocation.address as usize
|
||||||
|
..right_reloc.relocation.address as usize + len;
|
||||||
|
right_reloc_diffs.push(DataRelocationDiff {
|
||||||
|
reloc: right_reloc.relocation.clone(),
|
||||||
|
kind: diff_kind,
|
||||||
|
range,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (mut left_section_diff, mut right_section_diff) = diff_generic_section(
|
||||||
|
left_obj,
|
||||||
|
right_obj,
|
||||||
|
left_diff,
|
||||||
|
right_diff,
|
||||||
|
left_section_idx,
|
||||||
|
right_section_idx,
|
||||||
|
)?;
|
||||||
|
let all_left_relocs_match = left_reloc_diffs.iter().all(|d| d.kind == DataDiffKind::None);
|
||||||
|
left_section_diff.data_diff = left_data_diff;
|
||||||
|
right_section_diff.data_diff = right_data_diff;
|
||||||
|
left_section_diff.reloc_diff = left_reloc_diffs;
|
||||||
|
right_section_diff.reloc_diff = right_reloc_diffs;
|
||||||
|
if all_left_relocs_match {
|
||||||
|
// Use the highest match percent between two options:
|
||||||
|
// - Left symbols matching right symbols by name
|
||||||
|
// - Diff of the data itself
|
||||||
|
// We only do this when all relocations on the left side match.
|
||||||
|
if left_section_diff.match_percent.unwrap_or(-1.0) < match_percent {
|
||||||
|
left_section_diff.match_percent = Some(match_percent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok((left_section_diff, right_section_diff))
|
Ok((left_section_diff, right_section_diff))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diff_data_symbol(
|
pub fn diff_data_symbol(
|
||||||
left_obj: &ObjInfo,
|
left_obj: &Object,
|
||||||
right_obj: &ObjInfo,
|
right_obj: &Object,
|
||||||
left_symbol_ref: SymbolRef,
|
left_symbol_idx: usize,
|
||||||
right_symbol_ref: SymbolRef,
|
right_symbol_idx: usize,
|
||||||
) -> Result<(ObjSymbolDiff, ObjSymbolDiff)> {
|
) -> Result<(SymbolDiff, SymbolDiff)> {
|
||||||
let (left_section, left_symbol) = left_obj.section_symbol(left_symbol_ref);
|
let left_symbol = &left_obj.symbols[left_symbol_idx];
|
||||||
let (right_section, right_symbol) = right_obj.section_symbol(right_symbol_ref);
|
let right_symbol = &right_obj.symbols[right_symbol_idx];
|
||||||
|
|
||||||
let left_section = left_section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
|
let left_section_idx =
|
||||||
let right_section = right_section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
|
left_symbol.section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
|
||||||
|
let right_section_idx =
|
||||||
|
right_symbol.section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
|
||||||
|
|
||||||
let left_data = &left_section.data[left_symbol.section_address as usize
|
let left_section = &left_obj.sections[left_section_idx];
|
||||||
..(left_symbol.section_address + left_symbol.size) as usize];
|
let right_section = &right_obj.sections[right_section_idx];
|
||||||
let right_data = &right_section.data[right_symbol.section_address as usize
|
|
||||||
..(right_symbol.section_address + right_symbol.size) as usize];
|
|
||||||
|
|
||||||
let ops = capture_diff_slices_deadline(Algorithm::Patience, left_data, right_data, None);
|
let left_start = left_symbol
|
||||||
let match_percent = get_diff_ratio(&ops, left_data.len(), right_data.len()) * 100.0;
|
.address
|
||||||
|
.checked_sub(left_section.address)
|
||||||
|
.ok_or_else(|| anyhow!("Symbol address out of section bounds"))?;
|
||||||
|
let right_start = right_symbol
|
||||||
|
.address
|
||||||
|
.checked_sub(right_section.address)
|
||||||
|
.ok_or_else(|| anyhow!("Symbol address out of section bounds"))?;
|
||||||
|
let left_end = left_start + left_symbol.size;
|
||||||
|
if left_end > left_section.size {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Symbol {} size out of section bounds ({} > {})",
|
||||||
|
left_symbol.name,
|
||||||
|
left_end,
|
||||||
|
left_section.size
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let right_end = right_start + right_symbol.size;
|
||||||
|
if right_end > right_section.size {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"Symbol {} size out of section bounds ({} > {})",
|
||||||
|
right_symbol.name,
|
||||||
|
right_end,
|
||||||
|
right_section.size
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let left_range = left_start as usize..left_end as usize;
|
||||||
|
let right_range = right_start as usize..right_end as usize;
|
||||||
|
let left_data = &left_section.data[left_range.clone()];
|
||||||
|
let right_data = &right_section.data[right_range.clone()];
|
||||||
|
|
||||||
|
let reloc_diffs = diff_data_relocs_for_range(
|
||||||
|
left_obj,
|
||||||
|
right_obj,
|
||||||
|
left_section_idx,
|
||||||
|
right_section_idx,
|
||||||
|
left_range,
|
||||||
|
right_range,
|
||||||
|
);
|
||||||
|
|
||||||
|
let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data);
|
||||||
|
let bytes_match_ratio = get_diff_ratio(&ops, left_data.len(), right_data.len());
|
||||||
|
|
||||||
|
let mut match_ratio = bytes_match_ratio;
|
||||||
|
if !reloc_diffs.is_empty() {
|
||||||
|
let mut total_reloc_bytes = 0;
|
||||||
|
let mut matching_reloc_bytes = 0;
|
||||||
|
for (diff_kind, left_reloc, right_reloc) in reloc_diffs {
|
||||||
|
let reloc_diff_len = match (left_reloc, right_reloc) {
|
||||||
|
(None, None) => unreachable!(),
|
||||||
|
(None, Some(right_reloc)) => {
|
||||||
|
right_obj.arch.data_reloc_size(right_reloc.relocation.flags)
|
||||||
|
}
|
||||||
|
(Some(left_reloc), _) => left_obj.arch.data_reloc_size(left_reloc.relocation.flags),
|
||||||
|
};
|
||||||
|
total_reloc_bytes += reloc_diff_len;
|
||||||
|
if diff_kind == DataDiffKind::None {
|
||||||
|
matching_reloc_bytes += reloc_diff_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if total_reloc_bytes > 0 {
|
||||||
|
let relocs_match_ratio = matching_reloc_bytes as f32 / total_reloc_bytes as f32;
|
||||||
|
// Adjust the overall match ratio to include relocation differences.
|
||||||
|
// We calculate it so that bytes that contain a relocation are counted twice: once for the
|
||||||
|
// byte's raw value, and once for its relocation.
|
||||||
|
// e.g. An 8 byte symbol that has 8 matching raw bytes and a single 4 byte relocation that
|
||||||
|
// doesn't match would show as 66% (weighted average of 100% and 0%).
|
||||||
|
match_ratio = ((bytes_match_ratio * (left_data.len() as f32))
|
||||||
|
+ (relocs_match_ratio * total_reloc_bytes as f32))
|
||||||
|
/ (left_data.len() + total_reloc_bytes) as f32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let match_percent = match_ratio * 100.0;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
ObjSymbolDiff {
|
SymbolDiff {
|
||||||
symbol_ref: left_symbol_ref,
|
target_symbol: Some(right_symbol_idx),
|
||||||
target_symbol: Some(right_symbol_ref),
|
|
||||||
instructions: vec![],
|
|
||||||
match_percent: Some(match_percent),
|
match_percent: Some(match_percent),
|
||||||
|
diff_score: None,
|
||||||
|
instruction_rows: vec![],
|
||||||
},
|
},
|
||||||
ObjSymbolDiff {
|
SymbolDiff {
|
||||||
symbol_ref: right_symbol_ref,
|
target_symbol: Some(left_symbol_idx),
|
||||||
target_symbol: Some(left_symbol_ref),
|
|
||||||
instructions: vec![],
|
|
||||||
match_percent: Some(match_percent),
|
match_percent: Some(match_percent),
|
||||||
|
diff_score: None,
|
||||||
|
instruction_rows: vec![],
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -174,49 +411,85 @@ pub fn diff_data_symbol(
|
|||||||
/// Compares a section of two object files.
|
/// Compares a section of two object files.
|
||||||
/// This essentially adds up the match percentage of each symbol in the section.
|
/// This essentially adds up the match percentage of each symbol in the section.
|
||||||
pub fn diff_generic_section(
|
pub fn diff_generic_section(
|
||||||
left: &ObjSection,
|
left_obj: &Object,
|
||||||
_right: &ObjSection,
|
_right_obj: &Object,
|
||||||
left_diff: &ObjSectionDiff,
|
left_diff: &ObjectDiff,
|
||||||
_right_diff: &ObjSectionDiff,
|
_right_diff: &ObjectDiff,
|
||||||
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
|
left_section_idx: usize,
|
||||||
let match_percent = if left_diff.symbols.iter().all(|d| d.match_percent == Some(100.0)) {
|
_right_section_idx: usize,
|
||||||
|
) -> Result<(SectionDiff, SectionDiff)> {
|
||||||
|
let match_percent = if symbols_matching_section(&left_obj.symbols, left_section_idx)
|
||||||
|
.map(|(i, _)| &left_diff.symbols[i])
|
||||||
|
.all(|d| d.match_percent == Some(100.0))
|
||||||
|
{
|
||||||
100.0 // Avoid fp precision issues
|
100.0 // Avoid fp precision issues
|
||||||
} else {
|
} else {
|
||||||
left.symbols
|
let (matched, total) = symbols_matching_section(&left_obj.symbols, left_section_idx)
|
||||||
.iter()
|
.map(|(i, s)| (s, &left_diff.symbols[i]))
|
||||||
.zip(left_diff.symbols.iter())
|
.fold((0.0, 0.0), |(matched, total), (s, d)| {
|
||||||
.map(|(s, d)| d.match_percent.unwrap_or(0.0) * s.size as f32)
|
(matched + d.match_percent.unwrap_or(0.0) * s.size as f32, total + s.size as f32)
|
||||||
.sum::<f32>()
|
});
|
||||||
/ left.size as f32
|
if total == 0.0 { 100.0 } else { matched / total }
|
||||||
};
|
};
|
||||||
Ok((
|
Ok((
|
||||||
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
SectionDiff { match_percent: Some(match_percent), data_diff: vec![], reloc_diff: vec![] },
|
||||||
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
SectionDiff { match_percent: None, data_diff: vec![], reloc_diff: vec![] },
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn no_diff_bss_section() -> Result<SectionDiff> {
|
||||||
|
Ok(SectionDiff { match_percent: Some(0.0), data_diff: vec![], reloc_diff: vec![] })
|
||||||
|
}
|
||||||
|
|
||||||
/// Compare the addresses and sizes of each symbol in the BSS sections.
|
/// Compare the addresses and sizes of each symbol in the BSS sections.
|
||||||
pub fn diff_bss_section(
|
pub fn diff_bss_section(
|
||||||
left: &ObjSection,
|
left_obj: &Object,
|
||||||
right: &ObjSection,
|
right_obj: &Object,
|
||||||
left_diff: &ObjSectionDiff,
|
left_diff: &ObjectDiff,
|
||||||
right_diff: &ObjSectionDiff,
|
right_diff: &ObjectDiff,
|
||||||
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
|
left_section_idx: usize,
|
||||||
let left_sizes = left.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
|
right_section_idx: usize,
|
||||||
let right_sizes = right.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
|
) -> Result<(SectionDiff, SectionDiff)> {
|
||||||
let ops = capture_diff_slices_deadline(Algorithm::Patience, &left_sizes, &right_sizes, None);
|
let left_section = &left_obj.sections[left_section_idx];
|
||||||
|
let left_sizes = symbols_matching_section(&left_obj.symbols, left_section_idx)
|
||||||
|
.filter_map(|(_, s)| s.address.checked_sub(left_section.address).map(|a| (a, s.size)))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let right_section = &right_obj.sections[right_section_idx];
|
||||||
|
let right_sizes = symbols_matching_section(&right_obj.symbols, right_section_idx)
|
||||||
|
.filter_map(|(_, s)| s.address.checked_sub(right_section.address).map(|a| (a, s.size)))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let ops = capture_diff_slices(Algorithm::Patience, &left_sizes, &right_sizes);
|
||||||
let mut match_percent = get_diff_ratio(&ops, left_sizes.len(), right_sizes.len()) * 100.0;
|
let mut match_percent = get_diff_ratio(&ops, left_sizes.len(), right_sizes.len()) * 100.0;
|
||||||
|
|
||||||
// Use the highest match percent between two options:
|
// Use the highest match percent between two options:
|
||||||
// - Left symbols matching right symbols by name
|
// - Left symbols matching right symbols by name
|
||||||
// - Diff of the addresses and sizes of each symbol
|
// - Diff of the addresses and sizes of each symbol
|
||||||
let (generic_diff, _) = diff_generic_section(left, right, left_diff, right_diff)?;
|
let (generic_diff, _) = diff_generic_section(
|
||||||
|
left_obj,
|
||||||
|
right_obj,
|
||||||
|
left_diff,
|
||||||
|
right_diff,
|
||||||
|
left_section_idx,
|
||||||
|
right_section_idx,
|
||||||
|
)?;
|
||||||
if generic_diff.match_percent.unwrap_or(-1.0) > match_percent {
|
if generic_diff.match_percent.unwrap_or(-1.0) > match_percent {
|
||||||
match_percent = generic_diff.match_percent.unwrap();
|
match_percent = generic_diff.match_percent.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
SectionDiff { match_percent: Some(match_percent), data_diff: vec![], reloc_diff: vec![] },
|
||||||
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
|
SectionDiff { match_percent: None, data_diff: vec![], reloc_diff: vec![] },
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn symbols_matching_section(
|
||||||
|
symbols: &[Symbol],
|
||||||
|
section_idx: usize,
|
||||||
|
) -> impl Iterator<Item = (usize, &Symbol)> + '_ {
|
||||||
|
symbols.iter().enumerate().filter(move |(_, s)| {
|
||||||
|
s.section == Some(section_idx)
|
||||||
|
&& s.kind != SymbolKind::Section
|
||||||
|
&& s.size > 0
|
||||||
|
&& !s.flags.contains(SymbolFlag::Ignored)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,16 +1,29 @@
|
|||||||
use std::cmp::Ordering;
|
use alloc::{
|
||||||
|
borrow::Cow,
|
||||||
|
collections::BTreeSet,
|
||||||
|
format,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
use core::cmp::Ordering;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diff::{ObjInsArgDiff, ObjInsDiff},
|
diff::{DiffObjConfig, InstructionDiffKind, InstructionDiffRow, ObjectDiff, SymbolDiff},
|
||||||
obj::{ObjInsArg, ObjInsArgValue, ObjReloc, ObjSymbol},
|
obj::{
|
||||||
|
FlowAnalysisValue, InstructionArg, InstructionArgValue, Object, ParsedInstruction,
|
||||||
|
ResolvedInstructionRef, ResolvedRelocation, SectionFlag, SectionKind, Symbol, SymbolFlag,
|
||||||
|
SymbolKind,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum DiffText<'a> {
|
pub enum DiffText<'a> {
|
||||||
/// Basic text
|
/// Basic text
|
||||||
Basic(&'a str),
|
Basic(&'a str),
|
||||||
/// Colored text
|
|
||||||
BasicColor(&'a str, usize),
|
|
||||||
/// Line number
|
/// Line number
|
||||||
Line(u32),
|
Line(u32),
|
||||||
/// Instruction address
|
/// Instruction address
|
||||||
@@ -18,91 +31,294 @@ pub enum DiffText<'a> {
|
|||||||
/// Instruction mnemonic
|
/// Instruction mnemonic
|
||||||
Opcode(&'a str, u16),
|
Opcode(&'a str, u16),
|
||||||
/// Instruction argument
|
/// Instruction argument
|
||||||
Argument(&'a ObjInsArgValue, Option<&'a ObjInsArgDiff>),
|
Argument(InstructionArgValue<'a>),
|
||||||
/// Branch destination
|
/// Branch destination
|
||||||
BranchDest(u64, Option<&'a ObjInsArgDiff>),
|
BranchDest(u64),
|
||||||
/// Symbol name
|
/// Symbol name
|
||||||
Symbol(&'a ObjSymbol, Option<&'a ObjInsArgDiff>),
|
Symbol(&'a Symbol),
|
||||||
|
/// Relocation addend
|
||||||
|
Addend(i64),
|
||||||
/// Number of spaces
|
/// Number of spaces
|
||||||
Spacing(usize),
|
Spacing(u8),
|
||||||
/// End of line
|
/// End of line
|
||||||
Eol,
|
Eol,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
|
||||||
|
pub enum DiffTextColor {
|
||||||
|
#[default]
|
||||||
|
Normal, // Grey
|
||||||
|
Dim, // Dark grey
|
||||||
|
Bright, // White
|
||||||
|
DataFlow, // Light blue
|
||||||
|
Replace, // Blue
|
||||||
|
Delete, // Red
|
||||||
|
Insert, // Green
|
||||||
|
Rotating(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DiffTextSegment<'a> {
|
||||||
|
pub text: DiffText<'a>,
|
||||||
|
pub color: DiffTextColor,
|
||||||
|
pub pad_to: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DiffTextSegment<'a> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn basic(text: &'a str, color: DiffTextColor) -> Self {
|
||||||
|
Self { text: DiffText::Basic(text), color, pad_to: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn spacing(spaces: u8) -> Self {
|
||||||
|
Self { text: DiffText::Spacing(spaces), color: DiffTextColor::Normal, pad_to: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const EOL_SEGMENT: DiffTextSegment<'static> =
|
||||||
|
DiffTextSegment { text: DiffText::Eol, color: DiffTextColor::Normal, pad_to: 0 };
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
pub enum HighlightKind {
|
pub enum HighlightKind {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
Opcode(u16),
|
Opcode(u16),
|
||||||
Arg(ObjInsArgValue),
|
Argument(InstructionArgValue<'static>),
|
||||||
Symbol(String),
|
Symbol(String),
|
||||||
Address(u64),
|
Address(u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_diff<E>(
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
ins_diff: &ObjInsDiff,
|
pub enum InstructionPart<'a> {
|
||||||
base_addr: u64,
|
Basic(Cow<'a, str>),
|
||||||
mut cb: impl FnMut(DiffText) -> Result<(), E>,
|
Opcode(Cow<'a, str>, u16),
|
||||||
) -> Result<(), E> {
|
Arg(InstructionArg<'a>),
|
||||||
let Some(ins) = &ins_diff.ins else {
|
Separator,
|
||||||
cb(DiffText::Eol)?;
|
}
|
||||||
|
|
||||||
|
impl<'a> InstructionPart<'a> {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn basic<T>(s: T) -> Self
|
||||||
|
where T: Into<Cow<'a, str>> {
|
||||||
|
InstructionPart::Basic(s.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn opcode<T>(s: T, o: u16) -> Self
|
||||||
|
where T: Into<Cow<'a, str>> {
|
||||||
|
InstructionPart::Opcode(s.into(), o)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn opaque<T>(s: T) -> Self
|
||||||
|
where T: Into<Cow<'a, str>> {
|
||||||
|
InstructionPart::Arg(InstructionArg::Value(InstructionArgValue::Opaque(s.into())))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn signed<T>(v: T) -> InstructionPart<'static>
|
||||||
|
where T: Into<i64> {
|
||||||
|
InstructionPart::Arg(InstructionArg::Value(InstructionArgValue::Signed(v.into())))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn unsigned<T>(v: T) -> InstructionPart<'static>
|
||||||
|
where T: Into<u64> {
|
||||||
|
InstructionPart::Arg(InstructionArg::Value(InstructionArgValue::Unsigned(v.into())))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn branch_dest<T>(v: T) -> InstructionPart<'static>
|
||||||
|
where T: Into<u64> {
|
||||||
|
InstructionPart::Arg(InstructionArg::BranchDest(v.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn reloc() -> InstructionPart<'static> { InstructionPart::Arg(InstructionArg::Reloc) }
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn separator() -> InstructionPart<'static> { InstructionPart::Separator }
|
||||||
|
|
||||||
|
pub fn into_static(self) -> InstructionPart<'static> {
|
||||||
|
match self {
|
||||||
|
InstructionPart::Basic(s) => InstructionPart::Basic(Cow::Owned(s.into_owned())),
|
||||||
|
InstructionPart::Opcode(s, o) => InstructionPart::Opcode(Cow::Owned(s.into_owned()), o),
|
||||||
|
InstructionPart::Arg(a) => InstructionPart::Arg(a.into_static()),
|
||||||
|
InstructionPart::Separator => InstructionPart::Separator,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_row(
|
||||||
|
obj: &Object,
|
||||||
|
symbol_index: usize,
|
||||||
|
ins_row: &InstructionDiffRow,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
mut cb: impl FnMut(DiffTextSegment) -> Result<()>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let Some(ins_ref) = ins_row.ins_ref else {
|
||||||
|
cb(EOL_SEGMENT)?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
if let Some(line) = ins.line {
|
let Some(resolved) = obj.resolve_instruction_ref(symbol_index, ins_ref) else {
|
||||||
cb(DiffText::Line(line))?;
|
cb(DiffTextSegment::basic("<invalid>", DiffTextColor::Delete))?;
|
||||||
|
cb(EOL_SEGMENT)?;
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
let base_color = match ins_row.kind {
|
||||||
|
InstructionDiffKind::Replace => DiffTextColor::Replace,
|
||||||
|
InstructionDiffKind::Delete => DiffTextColor::Delete,
|
||||||
|
InstructionDiffKind::Insert => DiffTextColor::Insert,
|
||||||
|
_ => DiffTextColor::Normal,
|
||||||
|
};
|
||||||
|
if let Some(line) = resolved.section.line_info.range(..=ins_ref.address).last().map(|(_, &b)| b)
|
||||||
|
{
|
||||||
|
cb(DiffTextSegment { text: DiffText::Line(line), color: DiffTextColor::Dim, pad_to: 5 })?;
|
||||||
}
|
}
|
||||||
cb(DiffText::Address(ins.address - base_addr))?;
|
cb(DiffTextSegment {
|
||||||
if let Some(branch) = &ins_diff.branch_from {
|
text: DiffText::Address(ins_ref.address.saturating_sub(resolved.symbol.address)),
|
||||||
cb(DiffText::BasicColor(" ~> ", branch.branch_idx))?;
|
color: base_color,
|
||||||
|
pad_to: 5,
|
||||||
|
})?;
|
||||||
|
if let Some(branch) = &ins_row.branch_from {
|
||||||
|
cb(DiffTextSegment::basic(" ~> ", DiffTextColor::Rotating(branch.branch_idx as u8)))?;
|
||||||
} else {
|
} else {
|
||||||
cb(DiffText::Spacing(4))?;
|
cb(DiffTextSegment::spacing(4))?;
|
||||||
}
|
}
|
||||||
cb(DiffText::Opcode(&ins.mnemonic, ins.op))?;
|
let mut arg_idx = 0;
|
||||||
let mut arg_diff_idx = 0; // non-PlainText index
|
let mut displayed_relocation = false;
|
||||||
for (i, arg) in ins.args.iter().enumerate() {
|
let analysis_result = if diff_config.show_data_flow {
|
||||||
if i == 0 {
|
obj.get_flow_analysis_result(resolved.symbol)
|
||||||
cb(DiffText::Spacing(1))?;
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
obj.arch.display_instruction(resolved, diff_config, &mut |part| match part {
|
||||||
|
InstructionPart::Basic(text) => {
|
||||||
|
if text.chars().all(|c| c == ' ') {
|
||||||
|
cb(DiffTextSegment::spacing(text.len() as u8))
|
||||||
|
} else {
|
||||||
|
cb(DiffTextSegment::basic(&text, base_color))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let diff = ins_diff.arg_diff.get(arg_diff_idx).and_then(|o| o.as_ref());
|
InstructionPart::Opcode(mnemonic, opcode) => cb(DiffTextSegment {
|
||||||
match arg {
|
text: DiffText::Opcode(mnemonic.as_ref(), opcode),
|
||||||
ObjInsArg::PlainText(s) => {
|
color: match ins_row.kind {
|
||||||
cb(DiffText::Basic(s))?;
|
InstructionDiffKind::OpMismatch => DiffTextColor::Replace,
|
||||||
|
_ => base_color,
|
||||||
|
},
|
||||||
|
pad_to: 10,
|
||||||
|
}),
|
||||||
|
InstructionPart::Arg(arg) => {
|
||||||
|
let diff_index = ins_row.arg_diff.get(arg_idx).copied().unwrap_or_default();
|
||||||
|
arg_idx += 1;
|
||||||
|
if arg == InstructionArg::Reloc {
|
||||||
|
displayed_relocation = true;
|
||||||
}
|
}
|
||||||
ObjInsArg::Arg(v) => {
|
let data_flow_value =
|
||||||
cb(DiffText::Argument(v, diff))?;
|
analysis_result.and_then(|result|
|
||||||
arg_diff_idx += 1;
|
result.get_argument_value_at_address(
|
||||||
}
|
ins_ref.address, (arg_idx - 1) as u8));
|
||||||
ObjInsArg::Reloc => {
|
match (arg, data_flow_value, resolved.ins_ref.branch_dest) {
|
||||||
display_reloc_name(ins.reloc.as_ref().unwrap(), &mut cb, diff)?;
|
// If we have a flow analysis result, always use that over anything else.
|
||||||
arg_diff_idx += 1;
|
(InstructionArg::Value(_) | InstructionArg::Reloc, Some(FlowAnalysisValue::Text(text)), _) => {
|
||||||
}
|
cb(DiffTextSegment {
|
||||||
ObjInsArg::BranchDest(dest) => {
|
text: DiffText::Argument(InstructionArgValue::Opaque(Cow::Borrowed(text))),
|
||||||
if let Some(dest) = dest.checked_sub(base_addr) {
|
color: DiffTextColor::DataFlow,
|
||||||
cb(DiffText::BranchDest(dest, diff))?;
|
pad_to: 0,
|
||||||
} else {
|
})
|
||||||
cb(DiffText::Basic("<unknown>"))?;
|
},
|
||||||
|
(InstructionArg::Value(value), None, _) => {
|
||||||
|
let color = diff_index
|
||||||
|
.get()
|
||||||
|
.map_or(base_color, |i| DiffTextColor::Rotating(i as u8));
|
||||||
|
cb(DiffTextSegment {
|
||||||
|
text: DiffText::Argument(value),
|
||||||
|
color,
|
||||||
|
pad_to: 0,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
(InstructionArg::Reloc, _, None) => {
|
||||||
|
let resolved = resolved.relocation.unwrap();
|
||||||
|
let color = diff_index
|
||||||
|
.get()
|
||||||
|
.map_or(DiffTextColor::Bright, |i| DiffTextColor::Rotating(i as u8));
|
||||||
|
cb(DiffTextSegment {
|
||||||
|
text: DiffText::Symbol(resolved.symbol),
|
||||||
|
color,
|
||||||
|
pad_to: 0,
|
||||||
|
})?;
|
||||||
|
if resolved.relocation.addend != 0 {
|
||||||
|
cb(DiffTextSegment {
|
||||||
|
text: DiffText::Addend(resolved.relocation.addend),
|
||||||
|
color,
|
||||||
|
pad_to: 0,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
(InstructionArg::BranchDest(dest), _, _) |
|
||||||
|
// If the relocation was resolved to a branch destination, emit that instead.
|
||||||
|
(InstructionArg::Reloc, _, Some(dest)) => {
|
||||||
|
if let Some(addr) = dest.checked_sub(resolved.symbol.address) {
|
||||||
|
cb(DiffTextSegment {
|
||||||
|
text: DiffText::BranchDest(addr),
|
||||||
|
color: diff_index
|
||||||
|
.get()
|
||||||
|
.map_or(base_color, |i| DiffTextColor::Rotating(i as u8)),
|
||||||
|
pad_to: 0,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
cb(DiffTextSegment {
|
||||||
|
text: DiffText::Argument(InstructionArgValue::Opaque(Cow::Borrowed(
|
||||||
|
"<invalid>",
|
||||||
|
))),
|
||||||
|
color: diff_index
|
||||||
|
.get()
|
||||||
|
.map_or(base_color, |i| DiffTextColor::Rotating(i as u8)),
|
||||||
|
pad_to: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
arg_diff_idx += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
InstructionPart::Separator => {
|
||||||
|
cb(DiffTextSegment::basic(diff_config.separator(), base_color))
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
// Fallback for relocation that wasn't displayed
|
||||||
|
if resolved.relocation.is_some() && !displayed_relocation {
|
||||||
|
cb(DiffTextSegment::basic(" <", base_color))?;
|
||||||
|
let resolved = resolved.relocation.unwrap();
|
||||||
|
let diff_index = ins_row.arg_diff.get(arg_idx).copied().unwrap_or_default();
|
||||||
|
let color =
|
||||||
|
diff_index.get().map_or(DiffTextColor::Bright, |i| DiffTextColor::Rotating(i as u8));
|
||||||
|
cb(DiffTextSegment { text: DiffText::Symbol(resolved.symbol), color, pad_to: 0 })?;
|
||||||
|
if resolved.relocation.addend != 0 {
|
||||||
|
cb(DiffTextSegment {
|
||||||
|
text: DiffText::Addend(resolved.relocation.addend),
|
||||||
|
color,
|
||||||
|
pad_to: 0,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
cb(DiffTextSegment::basic(">", base_color))?;
|
||||||
}
|
}
|
||||||
if let Some(branch) = &ins_diff.branch_to {
|
if let Some(branch) = &ins_row.branch_to {
|
||||||
cb(DiffText::BasicColor(" ~>", branch.branch_idx))?;
|
cb(DiffTextSegment::basic(" ~>", DiffTextColor::Rotating(branch.branch_idx as u8)))?;
|
||||||
}
|
}
|
||||||
cb(DiffText::Eol)?;
|
cb(EOL_SEGMENT)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_reloc_name<E>(
|
impl PartialEq<HighlightKind> for HighlightKind {
|
||||||
reloc: &ObjReloc,
|
fn eq(&self, other: &HighlightKind) -> bool {
|
||||||
mut cb: impl FnMut(DiffText) -> Result<(), E>,
|
match (self, other) {
|
||||||
diff: Option<&ObjInsArgDiff>,
|
(HighlightKind::Opcode(a), HighlightKind::Opcode(b)) => a == b,
|
||||||
) -> Result<(), E> {
|
(HighlightKind::Argument(a), HighlightKind::Argument(b)) => a.loose_eq(b),
|
||||||
cb(DiffText::Symbol(&reloc.target, diff))?;
|
(HighlightKind::Symbol(a), HighlightKind::Symbol(b)) => a == b,
|
||||||
match reloc.addend.cmp(&0i64) {
|
(HighlightKind::Address(a), HighlightKind::Address(b)) => a == b,
|
||||||
Ordering::Greater => cb(DiffText::Basic(&format!("+{:#x}", reloc.addend))),
|
_ => false,
|
||||||
Ordering::Less => cb(DiffText::Basic(&format!("-{:#x}", -reloc.addend))),
|
}
|
||||||
_ => Ok(()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,11 +326,9 @@ impl PartialEq<DiffText<'_>> for HighlightKind {
|
|||||||
fn eq(&self, other: &DiffText) -> bool {
|
fn eq(&self, other: &DiffText) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(HighlightKind::Opcode(a), DiffText::Opcode(_, b)) => a == b,
|
(HighlightKind::Opcode(a), DiffText::Opcode(_, b)) => a == b,
|
||||||
(HighlightKind::Arg(a), DiffText::Argument(b, _)) => a.loose_eq(b),
|
(HighlightKind::Argument(a), DiffText::Argument(b)) => a.loose_eq(b),
|
||||||
(HighlightKind::Symbol(a), DiffText::Symbol(b, _)) => a == &b.name,
|
(HighlightKind::Symbol(a), DiffText::Symbol(b)) => a == &b.name,
|
||||||
(HighlightKind::Address(a), DiffText::Address(b) | DiffText::BranchDest(b, _)) => {
|
(HighlightKind::Address(a), DiffText::Address(b) | DiffText::BranchDest(b)) => a == b,
|
||||||
a == b
|
|
||||||
}
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,14 +338,479 @@ impl PartialEq<HighlightKind> for DiffText<'_> {
|
|||||||
fn eq(&self, other: &HighlightKind) -> bool { other.eq(self) }
|
fn eq(&self, other: &HighlightKind) -> bool { other.eq(self) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<DiffText<'_>> for HighlightKind {
|
impl From<&DiffText<'_>> for HighlightKind {
|
||||||
fn from(value: DiffText<'_>) -> Self {
|
fn from(value: &DiffText<'_>) -> Self {
|
||||||
match value {
|
match value {
|
||||||
DiffText::Opcode(_, op) => HighlightKind::Opcode(op),
|
DiffText::Opcode(_, op) => HighlightKind::Opcode(*op),
|
||||||
DiffText::Argument(arg, _) => HighlightKind::Arg(arg.clone()),
|
DiffText::Argument(arg) => HighlightKind::Argument(arg.to_static()),
|
||||||
DiffText::Symbol(sym, _) => HighlightKind::Symbol(sym.name.to_string()),
|
DiffText::Symbol(sym) => HighlightKind::Symbol(sym.name.to_string()),
|
||||||
DiffText::Address(addr) | DiffText::BranchDest(addr, _) => HighlightKind::Address(addr),
|
DiffText::Address(addr) | DiffText::BranchDest(addr) => HighlightKind::Address(*addr),
|
||||||
_ => HighlightKind::None,
|
_ => HighlightKind::None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum ContextItem {
|
||||||
|
Copy { value: String, label: Option<String> },
|
||||||
|
Navigate { label: String, symbol_index: usize, kind: SymbolNavigationKind },
|
||||||
|
Separator,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, Eq, PartialEq)]
|
||||||
|
pub enum SymbolNavigationKind {
|
||||||
|
#[default]
|
||||||
|
Normal,
|
||||||
|
Extab,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, Eq, PartialEq)]
|
||||||
|
pub enum HoverItemColor {
|
||||||
|
#[default]
|
||||||
|
Normal, // Gray
|
||||||
|
Emphasized, // White
|
||||||
|
Special, // Blue
|
||||||
|
Delete, // Red
|
||||||
|
Insert, // Green
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum HoverItem {
|
||||||
|
Text { label: String, value: String, color: HoverItemColor },
|
||||||
|
Separator,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn symbol_context(obj: &Object, symbol_index: usize) -> Vec<ContextItem> {
|
||||||
|
let symbol = &obj.symbols[symbol_index];
|
||||||
|
let mut out = Vec::new();
|
||||||
|
out.push(ContextItem::Copy { value: symbol.name.clone(), label: None });
|
||||||
|
if let Some(name) = &symbol.demangled_name {
|
||||||
|
out.push(ContextItem::Copy { value: name.clone(), label: None });
|
||||||
|
}
|
||||||
|
if symbol.section.is_some()
|
||||||
|
&& let Some(address) = symbol.virtual_address
|
||||||
|
{
|
||||||
|
out.push(ContextItem::Copy {
|
||||||
|
value: format!("{address:x}"),
|
||||||
|
label: Some("virtual address".to_string()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
out.append(&mut obj.arch.symbol_context(obj, symbol_index));
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn symbol_hover(
|
||||||
|
obj: &Object,
|
||||||
|
symbol_index: usize,
|
||||||
|
addend: i64,
|
||||||
|
override_color: Option<HoverItemColor>,
|
||||||
|
) -> Vec<HoverItem> {
|
||||||
|
let symbol = &obj.symbols[symbol_index];
|
||||||
|
let addend_str = match addend.cmp(&0i64) {
|
||||||
|
Ordering::Greater => format!("+{addend:x}"),
|
||||||
|
Ordering::Less => format!("-{:x}", -addend),
|
||||||
|
_ => String::new(),
|
||||||
|
};
|
||||||
|
let mut out = Vec::new();
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Name".into(),
|
||||||
|
value: format!("{}{}", symbol.name, addend_str),
|
||||||
|
color: override_color.clone().unwrap_or_default(),
|
||||||
|
});
|
||||||
|
if let Some(demangled_name) = &symbol.demangled_name {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Demangled".into(),
|
||||||
|
value: demangled_name.into(),
|
||||||
|
color: override_color.clone().unwrap_or_default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(section) = symbol.section {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Section".into(),
|
||||||
|
value: obj.sections[section].name.clone(),
|
||||||
|
color: override_color.clone().unwrap_or_default(),
|
||||||
|
});
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Address".into(),
|
||||||
|
value: format!("{:x}{}", symbol.address, addend_str),
|
||||||
|
color: override_color.clone().unwrap_or_default(),
|
||||||
|
});
|
||||||
|
if symbol.flags.contains(SymbolFlag::SizeInferred) {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Size".into(),
|
||||||
|
value: format!("{:x} (inferred)", symbol.size),
|
||||||
|
color: override_color.clone().unwrap_or_default(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Size".into(),
|
||||||
|
value: format!("{:x}", symbol.size),
|
||||||
|
color: override_color.clone().unwrap_or_default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(align) = symbol.align {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Alignment".into(),
|
||||||
|
value: align.get().to_string(),
|
||||||
|
color: override_color.clone().unwrap_or_default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(address) = symbol.virtual_address {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Virtual address".into(),
|
||||||
|
value: format!("{address:x}"),
|
||||||
|
color: override_color.clone().unwrap_or(HoverItemColor::Special),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: Default::default(),
|
||||||
|
value: "Extern".into(),
|
||||||
|
color: HoverItemColor::Emphasized,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
out.append(&mut obj.arch.symbol_hover(obj, symbol_index));
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn relocation_context(
|
||||||
|
obj: &Object,
|
||||||
|
reloc: ResolvedRelocation,
|
||||||
|
ins: Option<ResolvedInstructionRef>,
|
||||||
|
) -> Vec<ContextItem> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
out.append(&mut symbol_context(obj, reloc.relocation.target_symbol));
|
||||||
|
if let Some(ins) = ins {
|
||||||
|
let literals = display_ins_data_literals(obj, ins);
|
||||||
|
if !literals.is_empty() {
|
||||||
|
out.push(ContextItem::Separator);
|
||||||
|
for (literal, label_override) in literals {
|
||||||
|
out.push(ContextItem::Copy { value: literal, label: label_override });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn relocation_hover(
|
||||||
|
obj: &Object,
|
||||||
|
reloc: ResolvedRelocation,
|
||||||
|
override_color: Option<HoverItemColor>,
|
||||||
|
) -> Vec<HoverItem> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
if let Some(name) = obj.arch.reloc_name(reloc.relocation.flags) {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Relocation".into(),
|
||||||
|
value: name.to_string(),
|
||||||
|
color: override_color.clone().unwrap_or_default(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Relocation".into(),
|
||||||
|
value: format!("<{:?}>", reloc.relocation.flags),
|
||||||
|
color: override_color.clone().unwrap_or_default(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
out.append(&mut symbol_hover(
|
||||||
|
obj,
|
||||||
|
reloc.relocation.target_symbol,
|
||||||
|
reloc.relocation.addend,
|
||||||
|
override_color,
|
||||||
|
));
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn instruction_context(
|
||||||
|
obj: &Object,
|
||||||
|
resolved: ResolvedInstructionRef,
|
||||||
|
ins: &ParsedInstruction,
|
||||||
|
) -> Vec<ContextItem> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let mut hex_string = String::new();
|
||||||
|
for byte in resolved.code {
|
||||||
|
hex_string.push_str(&format!("{byte:02x}"));
|
||||||
|
}
|
||||||
|
out.push(ContextItem::Copy { value: hex_string, label: Some("instruction bytes".to_string()) });
|
||||||
|
out.append(&mut obj.arch.instruction_context(obj, resolved));
|
||||||
|
if let Some(virtual_address) = resolved.symbol.virtual_address {
|
||||||
|
let offset = resolved.ins_ref.address - resolved.symbol.address;
|
||||||
|
out.push(ContextItem::Copy {
|
||||||
|
value: format!("{:x}", virtual_address + offset),
|
||||||
|
label: Some("virtual address".to_string()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for arg in &ins.args {
|
||||||
|
if let InstructionArg::Value(arg) = arg {
|
||||||
|
out.push(ContextItem::Copy { value: arg.to_string(), label: None });
|
||||||
|
match arg {
|
||||||
|
InstructionArgValue::Signed(v) => {
|
||||||
|
out.push(ContextItem::Copy { value: v.to_string(), label: None });
|
||||||
|
}
|
||||||
|
InstructionArgValue::Unsigned(v) => {
|
||||||
|
out.push(ContextItem::Copy { value: v.to_string(), label: None });
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(reloc) = resolved.relocation {
|
||||||
|
out.push(ContextItem::Separator);
|
||||||
|
out.append(&mut relocation_context(obj, reloc, Some(resolved)));
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn instruction_hover(
|
||||||
|
obj: &Object,
|
||||||
|
resolved: ResolvedInstructionRef,
|
||||||
|
ins: &ParsedInstruction,
|
||||||
|
) -> Vec<HoverItem> {
|
||||||
|
let mut out = Vec::new();
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: Default::default(),
|
||||||
|
value: format!("{:02x?}", resolved.code),
|
||||||
|
color: HoverItemColor::Normal,
|
||||||
|
});
|
||||||
|
out.append(&mut obj.arch.instruction_hover(obj, resolved));
|
||||||
|
if let Some(virtual_address) = resolved.symbol.virtual_address {
|
||||||
|
let offset = resolved.ins_ref.address - resolved.symbol.address;
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: "Virtual address".into(),
|
||||||
|
value: format!("{:x}", virtual_address + offset),
|
||||||
|
color: HoverItemColor::Special,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for arg in &ins.args {
|
||||||
|
if let InstructionArg::Value(arg) = arg {
|
||||||
|
match arg {
|
||||||
|
InstructionArgValue::Signed(v) => {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: Default::default(),
|
||||||
|
value: format!("{arg} == {v}"),
|
||||||
|
color: HoverItemColor::Normal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
InstructionArgValue::Unsigned(v) => {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: Default::default(),
|
||||||
|
value: format!("{arg} == {v}"),
|
||||||
|
color: HoverItemColor::Normal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(reloc) = resolved.relocation {
|
||||||
|
out.push(HoverItem::Separator);
|
||||||
|
out.append(&mut relocation_hover(obj, reloc, None));
|
||||||
|
let bytes = obj.symbol_data(reloc.relocation.target_symbol).unwrap_or(&[]);
|
||||||
|
if let Some(ty) = obj.arch.guess_data_type(resolved, bytes) {
|
||||||
|
let literals = display_ins_data_literals(obj, resolved);
|
||||||
|
if !literals.is_empty() {
|
||||||
|
out.push(HoverItem::Separator);
|
||||||
|
for (literal, label_override) in literals {
|
||||||
|
out.push(HoverItem::Text {
|
||||||
|
label: label_override.unwrap_or_else(|| ty.to_string()),
|
||||||
|
value: literal,
|
||||||
|
color: HoverItemColor::Normal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub enum SymbolFilter<'a> {
|
||||||
|
None,
|
||||||
|
Search(&'a Regex),
|
||||||
|
Mapping(usize, Option<&'a Regex>),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn symbol_matches_filter(
|
||||||
|
symbol: &Symbol,
|
||||||
|
diff: &SymbolDiff,
|
||||||
|
filter: SymbolFilter<'_>,
|
||||||
|
show_hidden_symbols: bool,
|
||||||
|
) -> bool {
|
||||||
|
// Ignore absolute symbols
|
||||||
|
if symbol.section.is_none() && !symbol.flags.contains(SymbolFlag::Common) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if !show_hidden_symbols
|
||||||
|
&& (symbol.size == 0
|
||||||
|
|| symbol.flags.contains(SymbolFlag::Hidden)
|
||||||
|
|| symbol.flags.contains(SymbolFlag::Ignored))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
match filter {
|
||||||
|
SymbolFilter::None => true,
|
||||||
|
SymbolFilter::Search(regex) => {
|
||||||
|
regex.is_match(&symbol.name)
|
||||||
|
|| symbol.demangled_name.as_deref().is_some_and(|s| regex.is_match(s))
|
||||||
|
}
|
||||||
|
SymbolFilter::Mapping(symbol_ref, regex) => {
|
||||||
|
diff.target_symbol == Some(symbol_ref)
|
||||||
|
&& regex.is_none_or(|r| {
|
||||||
|
r.is_match(&symbol.name)
|
||||||
|
|| symbol.demangled_name.as_deref().is_some_and(|s| r.is_match(s))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
|
pub struct SectionDisplaySymbol {
|
||||||
|
pub symbol: usize,
|
||||||
|
pub is_mapping_symbol: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SectionDisplay {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
pub size: u64,
|
||||||
|
pub match_percent: Option<f32>,
|
||||||
|
pub symbols: Vec<SectionDisplaySymbol>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_sections(
|
||||||
|
obj: &Object,
|
||||||
|
diff: &ObjectDiff,
|
||||||
|
filter: SymbolFilter<'_>,
|
||||||
|
show_hidden_symbols: bool,
|
||||||
|
show_mapped_symbols: bool,
|
||||||
|
reverse_fn_order: bool,
|
||||||
|
) -> Vec<SectionDisplay> {
|
||||||
|
let mut mapping = BTreeSet::new();
|
||||||
|
let is_mapping_symbol = if let SymbolFilter::Mapping(_, _) = filter {
|
||||||
|
for mapping_diff in &diff.mapping_symbols {
|
||||||
|
let symbol = &obj.symbols[mapping_diff.symbol_index];
|
||||||
|
if !symbol_matches_filter(
|
||||||
|
symbol,
|
||||||
|
&mapping_diff.symbol_diff,
|
||||||
|
filter,
|
||||||
|
show_hidden_symbols,
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !show_mapped_symbols {
|
||||||
|
let symbol_diff = &diff.symbols[mapping_diff.symbol_index];
|
||||||
|
if symbol_diff.target_symbol.is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mapping.insert((symbol.section, mapping_diff.symbol_index));
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
for (symbol_idx, (symbol, symbol_diff)) in obj.symbols.iter().zip(&diff.symbols).enumerate()
|
||||||
|
{
|
||||||
|
if !symbol_matches_filter(symbol, symbol_diff, filter, show_hidden_symbols) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
mapping.insert((symbol.section, symbol_idx));
|
||||||
|
}
|
||||||
|
false
|
||||||
|
};
|
||||||
|
let num_sections = mapping.iter().map(|(section_idx, _)| *section_idx).dedup().count();
|
||||||
|
let mut sections = Vec::with_capacity(num_sections);
|
||||||
|
for (section_idx, group) in &mapping.iter().chunk_by(|(section_idx, _)| *section_idx) {
|
||||||
|
let mut symbols = group
|
||||||
|
.map(|&(_, symbol)| SectionDisplaySymbol { symbol, is_mapping_symbol })
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if let Some(section_idx) = section_idx {
|
||||||
|
let section = &obj.sections[section_idx];
|
||||||
|
if section.kind == SectionKind::Unknown {
|
||||||
|
// Skip unknown and hidden sections
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let section_diff = &diff.sections[section_idx];
|
||||||
|
let reverse_fn_order = section.kind == SectionKind::Code && reverse_fn_order;
|
||||||
|
symbols.sort_by(|a, b| {
|
||||||
|
let a = &obj.symbols[a.symbol];
|
||||||
|
let b = &obj.symbols[b.symbol];
|
||||||
|
section_symbol_sort(a, b)
|
||||||
|
.then_with(|| {
|
||||||
|
if reverse_fn_order {
|
||||||
|
b.address.cmp(&a.address)
|
||||||
|
} else {
|
||||||
|
a.address.cmp(&b.address)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then_with(|| a.size.cmp(&b.size))
|
||||||
|
});
|
||||||
|
sections.push(SectionDisplay {
|
||||||
|
id: section.id.clone(),
|
||||||
|
name: if section.flags.contains(SectionFlag::Combined) {
|
||||||
|
format!("{} [combined]", section.name)
|
||||||
|
} else {
|
||||||
|
section.name.clone()
|
||||||
|
},
|
||||||
|
size: section.size,
|
||||||
|
match_percent: section_diff.match_percent,
|
||||||
|
symbols,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Don't sort, preserve order of absolute symbols
|
||||||
|
sections.push(SectionDisplay {
|
||||||
|
id: ".comm".to_string(),
|
||||||
|
name: ".comm".to_string(),
|
||||||
|
size: 0,
|
||||||
|
match_percent: None,
|
||||||
|
symbols,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sections.sort_by(|a, b| a.name.cmp(&b.name));
|
||||||
|
sections
|
||||||
|
}
|
||||||
|
|
||||||
|
fn section_symbol_sort(a: &Symbol, b: &Symbol) -> Ordering {
|
||||||
|
if a.kind == SymbolKind::Section {
|
||||||
|
if b.kind != SymbolKind::Section {
|
||||||
|
return Ordering::Less;
|
||||||
|
}
|
||||||
|
} else if b.kind == SymbolKind::Section {
|
||||||
|
return Ordering::Greater;
|
||||||
|
}
|
||||||
|
Ordering::Equal
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_ins_data_labels(obj: &Object, resolved: ResolvedInstructionRef) -> Vec<String> {
|
||||||
|
let Some(reloc) = resolved.relocation else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
if reloc.relocation.addend < 0 || reloc.relocation.addend as u64 >= reloc.symbol.size {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
let Some(data) = obj.symbol_data(reloc.relocation.target_symbol) else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
let bytes = &data[reloc.relocation.addend as usize..];
|
||||||
|
obj.arch
|
||||||
|
.guess_data_type(resolved, bytes)
|
||||||
|
.map(|ty| ty.display_labels(obj.endianness, bytes))
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn display_ins_data_literals(
|
||||||
|
obj: &Object,
|
||||||
|
resolved: ResolvedInstructionRef,
|
||||||
|
) -> Vec<(String, Option<String>)> {
|
||||||
|
let Some(reloc) = resolved.relocation else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
if reloc.relocation.addend < 0 || reloc.relocation.addend as u64 >= reloc.symbol.size {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
let Some(data) = obj.symbol_data(reloc.relocation.target_symbol) else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
let bytes = &data[reloc.relocation.addend as usize..];
|
||||||
|
obj.arch
|
||||||
|
.guess_data_type(resolved, bytes)
|
||||||
|
.map(|ty| ty.display_literals(obj.endianness, bytes))
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
50
objdiff-core/src/jobs/check_update.rs
Normal file
50
objdiff-core/src/jobs/check_update.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
use std::{sync::mpsc::Receiver, task::Waker};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use self_update::{
|
||||||
|
cargo_crate_version,
|
||||||
|
update::{Release, ReleaseUpdate},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::jobs::{Job, JobContext, JobResult, JobState, start_job, update_status};
|
||||||
|
|
||||||
|
pub struct CheckUpdateConfig {
|
||||||
|
pub build_updater: fn() -> Result<Box<dyn ReleaseUpdate>>,
|
||||||
|
pub bin_names: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CheckUpdateResult {
|
||||||
|
pub update_available: bool,
|
||||||
|
pub latest_release: Release,
|
||||||
|
pub found_binary: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_check_update(
|
||||||
|
context: &JobContext,
|
||||||
|
cancel: Receiver<()>,
|
||||||
|
config: CheckUpdateConfig,
|
||||||
|
) -> Result<Box<CheckUpdateResult>> {
|
||||||
|
update_status(context, "Fetching latest release".to_string(), 0, 1, &cancel)?;
|
||||||
|
let updater = (config.build_updater)().context("Failed to create release updater")?;
|
||||||
|
let latest_release = updater.get_latest_release()?;
|
||||||
|
let update_available =
|
||||||
|
self_update::version::bump_is_greater(cargo_crate_version!(), &latest_release.version)?;
|
||||||
|
// Find the binary name in the release assets
|
||||||
|
let mut found_binary = None;
|
||||||
|
for bin_name in &config.bin_names {
|
||||||
|
if latest_release.assets.iter().any(|a| &a.name == bin_name) {
|
||||||
|
found_binary = Some(bin_name.clone());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update_status(context, "Complete".to_string(), 1, 1, &cancel)?;
|
||||||
|
Ok(Box::new(CheckUpdateResult { update_available, latest_release, found_binary }))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_check_update(waker: Waker, config: CheckUpdateConfig) -> JobState {
|
||||||
|
start_job(waker, "Check for updates", Job::CheckUpdate, move |context, cancel| {
|
||||||
|
run_check_update(&context, cancel, config)
|
||||||
|
.map(|result| JobResult::CheckUpdate(Some(result)))
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,20 +1,17 @@
|
|||||||
use std::{fs, path::PathBuf, sync::mpsc::Receiver};
|
use std::{fs, sync::mpsc::Receiver, task::Waker};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{Context, Result, anyhow, bail};
|
||||||
use const_format::formatcp;
|
use typed_path::{Utf8PlatformPathBuf, Utf8UnixPathBuf};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::AppConfig,
|
build::{BuildConfig, BuildStatus, run_make},
|
||||||
jobs::{
|
jobs::{Job, JobContext, JobResult, JobState, start_job, update_status},
|
||||||
objdiff::{run_make, BuildConfig, BuildStatus},
|
|
||||||
start_job, update_status, Job, JobContext, JobResult, JobState,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CreateScratchConfig {
|
pub struct CreateScratchConfig {
|
||||||
pub build_config: BuildConfig,
|
pub build_config: BuildConfig,
|
||||||
pub context_path: Option<PathBuf>,
|
pub context_path: Option<Utf8UnixPathBuf>,
|
||||||
pub build_context: bool,
|
pub build_context: bool,
|
||||||
|
|
||||||
// Scratch fields
|
// Scratch fields
|
||||||
@@ -22,42 +19,10 @@ pub struct CreateScratchConfig {
|
|||||||
pub platform: String,
|
pub platform: String,
|
||||||
pub compiler_flags: String,
|
pub compiler_flags: String,
|
||||||
pub function_name: String,
|
pub function_name: String,
|
||||||
pub target_obj: PathBuf,
|
pub target_obj: Utf8PlatformPathBuf,
|
||||||
pub preset_id: Option<u32>,
|
pub preset_id: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CreateScratchConfig {
|
|
||||||
pub(crate) fn from_config(config: &AppConfig, function_name: String) -> Result<Self> {
|
|
||||||
let Some(selected_obj) = &config.selected_obj else {
|
|
||||||
bail!("No object selected");
|
|
||||||
};
|
|
||||||
let Some(target_path) = &selected_obj.target_path else {
|
|
||||||
bail!("No target path for {}", selected_obj.name);
|
|
||||||
};
|
|
||||||
let Some(scratch_config) = &selected_obj.scratch else {
|
|
||||||
bail!("No scratch configuration for {}", selected_obj.name);
|
|
||||||
};
|
|
||||||
Ok(Self {
|
|
||||||
build_config: BuildConfig::from_config(config),
|
|
||||||
context_path: scratch_config.ctx_path.clone(),
|
|
||||||
build_context: scratch_config.build_ctx.unwrap_or(false),
|
|
||||||
compiler: scratch_config.compiler.clone().unwrap_or_default(),
|
|
||||||
platform: scratch_config.platform.clone().unwrap_or_default(),
|
|
||||||
compiler_flags: scratch_config.c_flags.clone().unwrap_or_default(),
|
|
||||||
function_name,
|
|
||||||
target_obj: target_path.to_path_buf(),
|
|
||||||
preset_id: scratch_config.preset_id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_available(config: &AppConfig) -> bool {
|
|
||||||
let Some(selected_obj) = &config.selected_obj else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
selected_obj.target_path.is_some() && selected_obj.scratch.is_some()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
pub struct CreateScratchResult {
|
pub struct CreateScratchResult {
|
||||||
pub scratch_url: String,
|
pub scratch_url: String,
|
||||||
@@ -83,26 +48,25 @@ fn run_create_scratch(
|
|||||||
if let Some(context_path) = &config.context_path {
|
if let Some(context_path) = &config.context_path {
|
||||||
if config.build_context {
|
if config.build_context {
|
||||||
update_status(status, "Building context".to_string(), 0, 2, &cancel)?;
|
update_status(status, "Building context".to_string(), 0, 2, &cancel)?;
|
||||||
match run_make(&config.build_config, context_path) {
|
match run_make(&config.build_config, context_path.as_ref()) {
|
||||||
BuildStatus { success: true, .. } => {}
|
BuildStatus { success: true, .. } => {}
|
||||||
BuildStatus { success: false, stdout, stderr, .. } => {
|
BuildStatus { success: false, stdout, stderr, .. } => {
|
||||||
bail!("Failed to build context:\n{stdout}\n{stderr}")
|
bail!("Failed to build context:\n{stdout}\n{stderr}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let context_path = project_dir.join(context_path);
|
let context_path = project_dir.join(context_path.with_platform_encoding());
|
||||||
context = Some(
|
context = Some(
|
||||||
fs::read_to_string(&context_path)
|
fs::read_to_string(&context_path)
|
||||||
.map_err(|e| anyhow!("Failed to read {}: {}", context_path.display(), e))?,
|
.map_err(|e| anyhow!("Failed to read {}: {}", context_path, e))?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
update_status(status, "Creating scratch".to_string(), 1, 2, &cancel)?;
|
update_status(status, "Creating scratch".to_string(), 1, 2, &cancel)?;
|
||||||
let diff_flags = [format!("--disassemble={}", config.function_name)];
|
let diff_flags = [format!("--disassemble={}", config.function_name)];
|
||||||
let diff_flags = serde_json::to_string(&diff_flags).unwrap();
|
let diff_flags = serde_json::to_string(&diff_flags)?;
|
||||||
let obj_path = project_dir.join(&config.target_obj);
|
let file = reqwest::blocking::multipart::Part::file(&config.target_obj)
|
||||||
let file = reqwest::blocking::multipart::Part::file(&obj_path)
|
.with_context(|| format!("Failed to open {}", config.target_obj))?;
|
||||||
.with_context(|| format!("Failed to open {}", obj_path.display()))?;
|
|
||||||
let mut form = reqwest::blocking::multipart::Form::new()
|
let mut form = reqwest::blocking::multipart::Form::new()
|
||||||
.text("compiler", config.compiler.clone())
|
.text("compiler", config.compiler.clone())
|
||||||
.text("platform", config.platform.clone())
|
.text("platform", config.platform.clone())
|
||||||
@@ -117,7 +81,7 @@ fn run_create_scratch(
|
|||||||
form = form.part("target_obj", file);
|
form = form.part("target_obj", file);
|
||||||
let client = reqwest::blocking::Client::new();
|
let client = reqwest::blocking::Client::new();
|
||||||
let response = client
|
let response = client
|
||||||
.post(formatcp!("{API_HOST}/api/scratch"))
|
.post(format!("{API_HOST}/api/scratch"))
|
||||||
.multipart(form)
|
.multipart(form)
|
||||||
.send()
|
.send()
|
||||||
.map_err(|e| anyhow!("Failed to send request: {}", e))?;
|
.map_err(|e| anyhow!("Failed to send request: {}", e))?;
|
||||||
@@ -131,8 +95,8 @@ fn run_create_scratch(
|
|||||||
Ok(Box::from(CreateScratchResult { scratch_url }))
|
Ok(Box::from(CreateScratchResult { scratch_url }))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_create_scratch(ctx: &egui::Context, config: CreateScratchConfig) -> JobState {
|
pub fn start_create_scratch(waker: Waker, config: CreateScratchConfig) -> JobState {
|
||||||
start_job(ctx, "Create scratch", Job::CreateScratch, move |context, cancel| {
|
start_job(waker, "Create scratch", Job::CreateScratch, move |context, cancel| {
|
||||||
run_create_scratch(&context, cancel, config)
|
run_create_scratch(&context, cancel, config)
|
||||||
.map(|result| JobResult::CreateScratch(Some(result)))
|
.map(|result| JobResult::CreateScratch(Some(result)))
|
||||||
})
|
})
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
use std::{
|
use std::{
|
||||||
sync::{
|
sync::{
|
||||||
|
Arc, RwLock,
|
||||||
atomic::{AtomicUsize, Ordering},
|
atomic::{AtomicUsize, Ordering},
|
||||||
mpsc::{Receiver, Sender, TryRecvError},
|
mpsc::{Receiver, Sender, TryRecvError},
|
||||||
Arc, RwLock,
|
|
||||||
},
|
},
|
||||||
|
task::Waker,
|
||||||
thread::JoinHandle,
|
thread::JoinHandle,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,7 +54,6 @@ impl JobQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether any job is running.
|
/// Returns whether any job is running.
|
||||||
#[expect(dead_code)]
|
|
||||||
pub fn any_running(&self) -> bool {
|
pub fn any_running(&self) -> bool {
|
||||||
self.jobs.iter().any(|job| {
|
self.jobs.iter().any(|job| {
|
||||||
if let Some(handle) = &job.handle {
|
if let Some(handle) = &job.handle {
|
||||||
@@ -96,12 +96,53 @@ impl JobQueue {
|
|||||||
|
|
||||||
/// Removes a job from the queue given its ID.
|
/// Removes a job from the queue given its ID.
|
||||||
pub fn remove(&mut self, id: usize) { self.jobs.retain(|job| job.id != id); }
|
pub fn remove(&mut self, id: usize) { self.jobs.retain(|job| job.id != id); }
|
||||||
|
|
||||||
|
/// Collects the results of all finished jobs and handles any errors.
|
||||||
|
pub fn collect_results(&mut self) {
|
||||||
|
let mut results = vec![];
|
||||||
|
for (job, result) in self.iter_finished() {
|
||||||
|
match result {
|
||||||
|
Ok(result) => {
|
||||||
|
match result {
|
||||||
|
JobResult::None => {
|
||||||
|
// Job context contains the error
|
||||||
|
}
|
||||||
|
_ => results.push(result),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
let err = if let Some(msg) = err.downcast_ref::<&'static str>() {
|
||||||
|
anyhow::Error::msg(*msg)
|
||||||
|
} else if let Some(msg) = err.downcast_ref::<String>() {
|
||||||
|
anyhow::Error::msg(msg.clone())
|
||||||
|
} else {
|
||||||
|
anyhow::Error::msg("Thread panicked")
|
||||||
|
};
|
||||||
|
let result = job.context.status.write();
|
||||||
|
if let Ok(mut guard) = result {
|
||||||
|
guard.error = Some(err);
|
||||||
|
} else {
|
||||||
|
drop(result);
|
||||||
|
job.context.status = Arc::new(RwLock::new(JobStatus {
|
||||||
|
title: "Error".to_string(),
|
||||||
|
progress_percent: 0.0,
|
||||||
|
progress_items: None,
|
||||||
|
status: String::new(),
|
||||||
|
error: Some(err),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.results.append(&mut results);
|
||||||
|
self.clear_finished();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct JobContext {
|
pub struct JobContext {
|
||||||
pub status: Arc<RwLock<JobStatus>>,
|
pub status: Arc<RwLock<JobStatus>>,
|
||||||
pub egui: egui::Context,
|
pub waker: Waker,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct JobState {
|
pub struct JobState {
|
||||||
@@ -137,7 +178,7 @@ fn should_cancel(rx: &Receiver<()>) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn start_job(
|
fn start_job(
|
||||||
ctx: &egui::Context,
|
waker: Waker,
|
||||||
title: &str,
|
title: &str,
|
||||||
kind: Job,
|
kind: Job,
|
||||||
run: impl FnOnce(JobContext, Receiver<()>) -> Result<JobResult> + Send + 'static,
|
run: impl FnOnce(JobContext, Receiver<()>) -> Result<JobResult> + Send + 'static,
|
||||||
@@ -149,8 +190,8 @@ fn start_job(
|
|||||||
status: String::new(),
|
status: String::new(),
|
||||||
error: None,
|
error: None,
|
||||||
}));
|
}));
|
||||||
let context = JobContext { status: status.clone(), egui: ctx.clone() };
|
let context = JobContext { status: status.clone(), waker: waker.clone() };
|
||||||
let context_inner = JobContext { status: status.clone(), egui: ctx.clone() };
|
let context_inner = JobContext { status: status.clone(), waker };
|
||||||
let (tx, rx) = std::sync::mpsc::channel();
|
let (tx, rx) = std::sync::mpsc::channel();
|
||||||
let handle = std::thread::spawn(move || match run(context_inner, rx) {
|
let handle = std::thread::spawn(move || match run(context_inner, rx) {
|
||||||
Ok(state) => state,
|
Ok(state) => state,
|
||||||
@@ -162,7 +203,7 @@ fn start_job(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
let id = JOB_ID.fetch_add(1, Ordering::Relaxed);
|
let id = JOB_ID.fetch_add(1, Ordering::Relaxed);
|
||||||
log::info!("Started job {}", id);
|
// log::info!("Started job {}", id); TODO
|
||||||
JobState { id, kind, handle: Some(handle), context, cancel: tx }
|
JobState { id, kind, handle: Some(handle), context, cancel: tx }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,6 +225,6 @@ fn update_status(
|
|||||||
w.status = str;
|
w.status = str;
|
||||||
}
|
}
|
||||||
drop(w);
|
drop(w);
|
||||||
context.egui.request_repaint();
|
context.waker.wake_by_ref();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
188
objdiff-core/src/jobs/objdiff.rs
Normal file
188
objdiff-core/src/jobs/objdiff.rs
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
use std::{sync::mpsc::Receiver, task::Waker};
|
||||||
|
|
||||||
|
use anyhow::{Error, Result, bail};
|
||||||
|
use time::OffsetDateTime;
|
||||||
|
use typed_path::Utf8PlatformPathBuf;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
build::{BuildConfig, BuildStatus, run_make},
|
||||||
|
diff::{DiffObjConfig, MappingConfig, ObjectDiff, diff_objs},
|
||||||
|
jobs::{Job, JobContext, JobResult, JobState, start_job, update_status},
|
||||||
|
obj::{Object, read},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct ObjDiffConfig {
|
||||||
|
pub build_config: BuildConfig,
|
||||||
|
pub build_base: bool,
|
||||||
|
pub build_target: bool,
|
||||||
|
pub target_path: Option<Utf8PlatformPathBuf>,
|
||||||
|
pub base_path: Option<Utf8PlatformPathBuf>,
|
||||||
|
pub diff_obj_config: DiffObjConfig,
|
||||||
|
pub mapping_config: MappingConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ObjDiffResult {
|
||||||
|
pub first_status: BuildStatus,
|
||||||
|
pub second_status: BuildStatus,
|
||||||
|
pub first_obj: Option<(Object, ObjectDiff)>,
|
||||||
|
pub second_obj: Option<(Object, ObjectDiff)>,
|
||||||
|
pub time: OffsetDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_build(
|
||||||
|
context: &JobContext,
|
||||||
|
cancel: Receiver<()>,
|
||||||
|
config: ObjDiffConfig,
|
||||||
|
) -> Result<Box<ObjDiffResult>> {
|
||||||
|
let mut target_path_rel = None;
|
||||||
|
let mut base_path_rel = None;
|
||||||
|
if config.build_target || config.build_base {
|
||||||
|
let project_dir = config
|
||||||
|
.build_config
|
||||||
|
.project_dir
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| Error::msg("Missing project dir"))?;
|
||||||
|
if let Some(target_path) = &config.target_path {
|
||||||
|
target_path_rel = match target_path.strip_prefix(project_dir) {
|
||||||
|
Ok(p) => Some(p.with_unix_encoding()),
|
||||||
|
Err(_) => {
|
||||||
|
bail!("Target path '{}' doesn't begin with '{}'", target_path, project_dir);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if let Some(base_path) = &config.base_path {
|
||||||
|
base_path_rel = match base_path.strip_prefix(project_dir) {
|
||||||
|
Ok(p) => Some(p.with_unix_encoding()),
|
||||||
|
Err(_) => {
|
||||||
|
bail!("Base path '{}' doesn't begin with '{}'", base_path, project_dir);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut total = 1;
|
||||||
|
if config.build_target && target_path_rel.is_some() {
|
||||||
|
total += 1;
|
||||||
|
}
|
||||||
|
if config.build_base && base_path_rel.is_some() {
|
||||||
|
total += 1;
|
||||||
|
}
|
||||||
|
if config.target_path.is_some() {
|
||||||
|
total += 1;
|
||||||
|
}
|
||||||
|
if config.base_path.is_some() {
|
||||||
|
total += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut step_idx = 0;
|
||||||
|
let mut first_status = match target_path_rel {
|
||||||
|
Some(target_path_rel) if config.build_target => {
|
||||||
|
update_status(
|
||||||
|
context,
|
||||||
|
format!("Building target {target_path_rel}"),
|
||||||
|
step_idx,
|
||||||
|
total,
|
||||||
|
&cancel,
|
||||||
|
)?;
|
||||||
|
step_idx += 1;
|
||||||
|
run_make(&config.build_config, target_path_rel.as_ref())
|
||||||
|
}
|
||||||
|
_ => BuildStatus::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut second_status = match base_path_rel {
|
||||||
|
Some(base_path_rel) if config.build_base => {
|
||||||
|
update_status(
|
||||||
|
context,
|
||||||
|
format!("Building base {base_path_rel}"),
|
||||||
|
step_idx,
|
||||||
|
total,
|
||||||
|
&cancel,
|
||||||
|
)?;
|
||||||
|
step_idx += 1;
|
||||||
|
run_make(&config.build_config, base_path_rel.as_ref())
|
||||||
|
}
|
||||||
|
_ => BuildStatus::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let time = OffsetDateTime::now_utc();
|
||||||
|
|
||||||
|
let first_obj = match &config.target_path {
|
||||||
|
Some(target_path) if first_status.success => {
|
||||||
|
update_status(
|
||||||
|
context,
|
||||||
|
format!("Loading target {target_path}"),
|
||||||
|
step_idx,
|
||||||
|
total,
|
||||||
|
&cancel,
|
||||||
|
)?;
|
||||||
|
step_idx += 1;
|
||||||
|
match read::read(target_path.as_ref(), &config.diff_obj_config) {
|
||||||
|
Ok(obj) => Some(obj),
|
||||||
|
Err(e) => {
|
||||||
|
first_status = BuildStatus {
|
||||||
|
success: false,
|
||||||
|
stdout: format!("Loading object '{target_path}'"),
|
||||||
|
stderr: format!("{e:#}"),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(_) => {
|
||||||
|
step_idx += 1;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let second_obj = match &config.base_path {
|
||||||
|
Some(base_path) if second_status.success => {
|
||||||
|
update_status(context, format!("Loading base {base_path}"), step_idx, total, &cancel)?;
|
||||||
|
step_idx += 1;
|
||||||
|
match read::read(base_path.as_ref(), &config.diff_obj_config) {
|
||||||
|
Ok(obj) => Some(obj),
|
||||||
|
Err(e) => {
|
||||||
|
second_status = BuildStatus {
|
||||||
|
success: false,
|
||||||
|
stdout: format!("Loading object '{base_path}'"),
|
||||||
|
stderr: format!("{e:#}"),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(_) => {
|
||||||
|
step_idx += 1;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
update_status(context, "Performing diff".to_string(), step_idx, total, &cancel)?;
|
||||||
|
step_idx += 1;
|
||||||
|
let result = diff_objs(
|
||||||
|
first_obj.as_ref(),
|
||||||
|
second_obj.as_ref(),
|
||||||
|
None,
|
||||||
|
&config.diff_obj_config,
|
||||||
|
&config.mapping_config,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
update_status(context, "Complete".to_string(), step_idx, total, &cancel)?;
|
||||||
|
Ok(Box::new(ObjDiffResult {
|
||||||
|
first_status,
|
||||||
|
second_status,
|
||||||
|
first_obj: first_obj.and_then(|o| result.left.map(|d| (o, d))),
|
||||||
|
second_obj: second_obj.and_then(|o| result.right.map(|d| (o, d))),
|
||||||
|
time,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_build(waker: Waker, config: ObjDiffConfig) -> JobState {
|
||||||
|
start_job(waker, "Build", Job::ObjDiff, move |context, cancel| {
|
||||||
|
run_build(&context, cancel, config).map(|result| JobResult::ObjDiff(Some(result)))
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -3,14 +3,19 @@ use std::{
|
|||||||
fs::File,
|
fs::File,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::mpsc::Receiver,
|
sync::mpsc::Receiver,
|
||||||
|
task::Waker,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
|
pub use self_update; // Re-export self_update crate
|
||||||
|
use self_update::update::ReleaseUpdate;
|
||||||
|
|
||||||
use crate::{
|
use crate::jobs::{Job, JobContext, JobResult, JobState, start_job, update_status};
|
||||||
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState},
|
|
||||||
update::build_updater,
|
pub struct UpdateConfig {
|
||||||
};
|
pub build_updater: fn() -> Result<Box<dyn ReleaseUpdate>>,
|
||||||
|
pub bin_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct UpdateResult {
|
pub struct UpdateResult {
|
||||||
pub exe_path: PathBuf,
|
pub exe_path: PathBuf,
|
||||||
@@ -19,16 +24,15 @@ pub struct UpdateResult {
|
|||||||
fn run_update(
|
fn run_update(
|
||||||
status: &JobContext,
|
status: &JobContext,
|
||||||
cancel: Receiver<()>,
|
cancel: Receiver<()>,
|
||||||
bin_name: String,
|
config: UpdateConfig,
|
||||||
) -> Result<Box<UpdateResult>> {
|
) -> Result<Box<UpdateResult>> {
|
||||||
update_status(status, "Fetching latest release".to_string(), 0, 3, &cancel)?;
|
update_status(status, "Fetching latest release".to_string(), 0, 3, &cancel)?;
|
||||||
let updater = build_updater().context("Failed to create release updater")?;
|
let updater = (config.build_updater)().context("Failed to create release updater")?;
|
||||||
let latest_release = updater.get_latest_release()?;
|
let latest_release = updater.get_latest_release()?;
|
||||||
let asset = latest_release
|
let asset =
|
||||||
.assets
|
latest_release.assets.iter().find(|a| a.name == config.bin_name).ok_or_else(|| {
|
||||||
.iter()
|
anyhow::Error::msg(format!("No release asset for {}", config.bin_name))
|
||||||
.find(|a| a.name == bin_name)
|
})?;
|
||||||
.ok_or_else(|| anyhow::Error::msg(format!("No release asset for {bin_name}")))?;
|
|
||||||
|
|
||||||
update_status(status, "Downloading release".to_string(), 1, 3, &cancel)?;
|
update_status(status, "Downloading release".to_string(), 1, 3, &cancel)?;
|
||||||
let tmp_dir = tempfile::Builder::new().prefix("update").tempdir_in(current_dir()?)?;
|
let tmp_dir = tempfile::Builder::new().prefix("update").tempdir_in(current_dir()?)?;
|
||||||
@@ -47,9 +51,7 @@ fn run_update(
|
|||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
use std::{fs, os::unix::fs::PermissionsExt};
|
use std::{fs, os::unix::fs::PermissionsExt};
|
||||||
let mut perms = fs::metadata(&target_file)?.permissions();
|
fs::set_permissions(&target_file, fs::Permissions::from_mode(0o755))?;
|
||||||
perms.set_mode(0o755);
|
|
||||||
fs::set_permissions(&target_file, perms)?;
|
|
||||||
}
|
}
|
||||||
tmp_dir.close()?;
|
tmp_dir.close()?;
|
||||||
|
|
||||||
@@ -57,8 +59,8 @@ fn run_update(
|
|||||||
Ok(Box::from(UpdateResult { exe_path: target_file }))
|
Ok(Box::from(UpdateResult { exe_path: target_file }))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_update(ctx: &egui::Context, bin_name: String) -> JobState {
|
pub fn start_update(waker: Waker, config: UpdateConfig) -> JobState {
|
||||||
start_job(ctx, "Update app", Job::Update, move |context, cancel| {
|
start_job(waker, "Update app", Job::Update, move |context, cancel| {
|
||||||
run_update(&context, cancel, bin_name).map(JobResult::Update)
|
run_update(&context, cancel, config).map(JobResult::Update)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,19 @@
|
|||||||
|
#![allow(clippy::too_many_arguments)]
|
||||||
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
extern crate alloc;
|
||||||
|
|
||||||
#[cfg(feature = "any-arch")]
|
#[cfg(feature = "any-arch")]
|
||||||
pub mod arch;
|
pub mod arch;
|
||||||
#[cfg(feature = "bindings")]
|
#[cfg(feature = "bindings")]
|
||||||
pub mod bindings;
|
pub mod bindings;
|
||||||
|
#[cfg(feature = "build")]
|
||||||
|
pub mod build;
|
||||||
#[cfg(feature = "config")]
|
#[cfg(feature = "config")]
|
||||||
pub mod config;
|
pub mod config;
|
||||||
#[cfg(feature = "any-arch")]
|
#[cfg(feature = "any-arch")]
|
||||||
pub mod diff;
|
pub mod diff;
|
||||||
|
#[cfg(feature = "build")]
|
||||||
|
pub mod jobs;
|
||||||
#[cfg(feature = "any-arch")]
|
#[cfg(feature = "any-arch")]
|
||||||
pub mod obj;
|
pub mod obj;
|
||||||
#[cfg(feature = "any-arch")]
|
#[cfg(feature = "any-arch")]
|
||||||
|
|||||||
109
objdiff-core/src/obj/dwarf2.rs
Normal file
109
objdiff-core/src/obj/dwarf2.rs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
use alloc::{borrow::Cow, vec::Vec};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use object::{Object as _, ObjectSection as _};
|
||||||
|
use typed_arena::Arena;
|
||||||
|
|
||||||
|
use crate::obj::{Section, SectionKind};
|
||||||
|
|
||||||
|
/// Parse line information from DWARF 2+ sections.
|
||||||
|
pub(crate) fn parse_line_info_dwarf2(
|
||||||
|
obj_file: &object::File,
|
||||||
|
sections: &mut [Section],
|
||||||
|
) -> Result<()> {
|
||||||
|
let arena_data = Arena::new();
|
||||||
|
let arena_relocations = Arena::new();
|
||||||
|
let endian = match obj_file.endianness() {
|
||||||
|
object::Endianness::Little => gimli::RunTimeEndian::Little,
|
||||||
|
object::Endianness::Big => gimli::RunTimeEndian::Big,
|
||||||
|
};
|
||||||
|
let dwarf = gimli::Dwarf::load(|id: gimli::SectionId| -> Result<_> {
|
||||||
|
load_file_section(id, obj_file, endian, &arena_data, &arena_relocations)
|
||||||
|
})
|
||||||
|
.context("loading DWARF sections")?;
|
||||||
|
|
||||||
|
let mut iter = dwarf.units();
|
||||||
|
if let Some(header) = iter.next().map_err(|e| gimli_error(e, "iterating over DWARF units"))? {
|
||||||
|
let unit = dwarf.unit(header).map_err(|e| gimli_error(e, "loading DWARF unit"))?;
|
||||||
|
if let Some(program) = unit.line_program.clone() {
|
||||||
|
let mut text_sections = sections.iter_mut().filter(|s| s.kind == SectionKind::Code);
|
||||||
|
let mut lines = text_sections.next().map(|section| &mut section.line_info);
|
||||||
|
|
||||||
|
let mut rows = program.rows();
|
||||||
|
while let Some((_header, row)) =
|
||||||
|
rows.next_row().map_err(|e| gimli_error(e, "loading program row"))?
|
||||||
|
{
|
||||||
|
if let (Some(line), Some(lines)) = (row.line(), &mut lines) {
|
||||||
|
lines.insert(row.address(), line.get() as u32);
|
||||||
|
}
|
||||||
|
if row.end_sequence() {
|
||||||
|
// The next row is the start of a new sequence, which means we must
|
||||||
|
// advance to the next .text section.
|
||||||
|
lines = text_sections.next().map(|section| &mut section.line_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if iter.next().map_err(|e| gimli_error(e, "checking for next unit"))?.is_some() {
|
||||||
|
log::warn!("Multiple units found in DWARF data, only processing the first");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
struct RelocationMap(object::read::RelocationMap);
|
||||||
|
|
||||||
|
impl RelocationMap {
|
||||||
|
fn add(&mut self, file: &object::File, section: &object::Section) {
|
||||||
|
for (offset, relocation) in section.relocations() {
|
||||||
|
if let Err(e) = self.0.add(file, offset, relocation) {
|
||||||
|
log::error!(
|
||||||
|
"Relocation error for section {} at offset 0x{:08x}: {}",
|
||||||
|
section.name().unwrap(),
|
||||||
|
offset,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl gimli::read::Relocate for &'_ RelocationMap {
|
||||||
|
fn relocate_address(&self, offset: usize, value: u64) -> gimli::Result<u64> {
|
||||||
|
Ok(self.0.relocate(offset as u64, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn relocate_offset(&self, offset: usize, value: usize) -> gimli::Result<usize> {
|
||||||
|
<usize as gimli::ReaderOffset>::from_u64(self.0.relocate(offset as u64, value as u64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Relocate<'a, R> = gimli::RelocateReader<R, &'a RelocationMap>;
|
||||||
|
|
||||||
|
fn load_file_section<'input, 'arena, Endian: gimli::Endianity>(
|
||||||
|
id: gimli::SectionId,
|
||||||
|
file: &object::File<'input>,
|
||||||
|
endian: Endian,
|
||||||
|
arena_data: &'arena Arena<Cow<'input, [u8]>>,
|
||||||
|
arena_relocations: &'arena Arena<RelocationMap>,
|
||||||
|
) -> Result<Relocate<'arena, gimli::EndianSlice<'arena, Endian>>> {
|
||||||
|
let mut relocations = RelocationMap::default();
|
||||||
|
let data = match file.section_by_name(id.name()) {
|
||||||
|
Some(ref section) => {
|
||||||
|
relocations.add(file, section);
|
||||||
|
section.uncompressed_data()?
|
||||||
|
}
|
||||||
|
// Use a non-zero capacity so that `ReaderOffsetId`s are unique.
|
||||||
|
None => Cow::Owned(Vec::with_capacity(1)),
|
||||||
|
};
|
||||||
|
let data_ref = arena_data.alloc(data);
|
||||||
|
let section = gimli::EndianSlice::new(data_ref, endian);
|
||||||
|
let relocations = arena_relocations.alloc(relocations);
|
||||||
|
Ok(Relocate::new(section, relocations))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn gimli_error(e: gimli::Error, context: &str) -> anyhow::Error {
|
||||||
|
anyhow::anyhow!("gimli error {context}: {e:?}")
|
||||||
|
}
|
||||||
@@ -1,23 +1,42 @@
|
|||||||
|
#[cfg(feature = "dwarf")]
|
||||||
|
mod dwarf2;
|
||||||
pub mod read;
|
pub mod read;
|
||||||
pub mod split_meta;
|
pub mod split_meta;
|
||||||
|
|
||||||
use std::{borrow::Cow, collections::BTreeMap, fmt, path::PathBuf};
|
use alloc::{
|
||||||
|
borrow::Cow,
|
||||||
|
boxed::Box,
|
||||||
|
collections::BTreeMap,
|
||||||
|
string::{String, ToString},
|
||||||
|
vec,
|
||||||
|
vec::Vec,
|
||||||
|
};
|
||||||
|
use core::{
|
||||||
|
fmt,
|
||||||
|
num::{NonZeroU32, NonZeroU64},
|
||||||
|
};
|
||||||
|
|
||||||
use filetime::FileTime;
|
use flagset::{FlagSet, flags};
|
||||||
use flagset::{flags, FlagSet};
|
|
||||||
use object::RelocationFlags;
|
|
||||||
use split_meta::SplitMeta;
|
|
||||||
|
|
||||||
use crate::{arch::ObjArch, util::ReallySigned};
|
use crate::{
|
||||||
|
arch::{Arch, ArchDummy},
|
||||||
|
obj::split_meta::SplitMeta,
|
||||||
|
util::ReallySigned,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
|
||||||
pub enum ObjSectionKind {
|
pub enum SectionKind {
|
||||||
|
#[default]
|
||||||
|
Unknown = -1,
|
||||||
Code,
|
Code,
|
||||||
Data,
|
Data,
|
||||||
Bss,
|
Bss,
|
||||||
|
Common,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags! {
|
flags! {
|
||||||
pub enum ObjSymbolFlags: u8 {
|
#[derive(Hash)]
|
||||||
|
pub enum SymbolFlag: u8 {
|
||||||
Global,
|
Global,
|
||||||
Local,
|
Local,
|
||||||
Weak,
|
Weak,
|
||||||
@@ -26,105 +45,199 @@ flags! {
|
|||||||
/// Has extra data associated with the symbol
|
/// Has extra data associated with the symbol
|
||||||
/// (e.g. exception table entry)
|
/// (e.g. exception table entry)
|
||||||
HasExtra,
|
HasExtra,
|
||||||
|
/// Symbol size was missing and was inferred
|
||||||
|
SizeInferred,
|
||||||
|
/// Symbol should be ignored by any diffing
|
||||||
|
Ignored,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Debug, Copy, Clone, Default)]
|
|
||||||
pub struct ObjSymbolFlagSet(pub FlagSet<ObjSymbolFlags>);
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
pub type SymbolFlagSet = FlagSet<SymbolFlag>;
|
||||||
pub struct ObjSection {
|
|
||||||
|
flags! {
|
||||||
|
#[derive(Hash)]
|
||||||
|
pub enum SectionFlag: u8 {
|
||||||
|
/// Section combined from multiple input sections
|
||||||
|
Combined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SectionFlagSet = FlagSet<SectionFlag>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct Section {
|
||||||
|
/// Unique section ID
|
||||||
|
pub id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub kind: ObjSectionKind,
|
|
||||||
pub address: u64,
|
pub address: u64,
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
pub data: Vec<u8>,
|
pub kind: SectionKind,
|
||||||
pub orig_index: usize,
|
pub data: SectionData,
|
||||||
pub symbols: Vec<ObjSymbol>,
|
pub flags: SectionFlagSet,
|
||||||
pub relocations: Vec<ObjReloc>,
|
pub align: Option<NonZeroU64>,
|
||||||
pub virtual_address: Option<u64>,
|
pub relocations: Vec<Relocation>,
|
||||||
/// Line number info (.line or .debug_line section)
|
/// Line number info (.line or .debug_line section)
|
||||||
pub line_info: BTreeMap<u64, u32>,
|
pub line_info: BTreeMap<u64, u32>,
|
||||||
|
/// Original virtual address (from .note.split section)
|
||||||
|
pub virtual_address: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct SectionData(pub Vec<u8>);
|
||||||
|
|
||||||
|
impl core::ops::Deref for SectionData {
|
||||||
|
type Target = Vec<u8>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for SectionData {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_tuple("SectionData").field(&self.0.len()).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Section {
|
||||||
|
pub fn data_range(&self, address: u64, size: usize) -> Option<&[u8]> {
|
||||||
|
let offset = address.checked_sub(self.address)?;
|
||||||
|
self.data.get(offset as usize..offset as usize + size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The alignment to use when "Combine data/text sections" is enabled.
|
||||||
|
pub fn combined_alignment(&self) -> u64 {
|
||||||
|
const MIN_ALIGNMENT: u64 = 4;
|
||||||
|
self.align.map(|align| align.get().max(MIN_ALIGNMENT)).unwrap_or(MIN_ALIGNMENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn relocation_at<'obj>(
|
||||||
|
&'obj self,
|
||||||
|
obj: &'obj Object,
|
||||||
|
ins_ref: InstructionRef,
|
||||||
|
) -> Option<ResolvedRelocation<'obj>> {
|
||||||
|
match self.relocations.binary_search_by_key(&ins_ref.address, |r| r.address) {
|
||||||
|
Ok(mut i) => {
|
||||||
|
// Find the first relocation at the address
|
||||||
|
while i
|
||||||
|
.checked_sub(1)
|
||||||
|
.and_then(|n| self.relocations.get(n))
|
||||||
|
.is_some_and(|r| r.address == ins_ref.address)
|
||||||
|
{
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
self.relocations.get(i)
|
||||||
|
}
|
||||||
|
Err(i) => self
|
||||||
|
.relocations
|
||||||
|
.get(i)
|
||||||
|
.filter(|r| r.address < ins_ref.address + ins_ref.size as u64),
|
||||||
|
}
|
||||||
|
.and_then(|relocation| {
|
||||||
|
let symbol = obj.symbols.get(relocation.target_symbol)?;
|
||||||
|
Some(ResolvedRelocation { relocation, symbol })
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum ObjInsArgValue {
|
pub enum InstructionArgValue<'a> {
|
||||||
Signed(i64),
|
Signed(i64),
|
||||||
Unsigned(u64),
|
Unsigned(u64),
|
||||||
Opaque(Cow<'static, str>),
|
Opaque(Cow<'a, str>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjInsArgValue {
|
impl InstructionArgValue<'_> {
|
||||||
pub fn loose_eq(&self, other: &ObjInsArgValue) -> bool {
|
pub fn loose_eq(&self, other: &InstructionArgValue) -> bool {
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(ObjInsArgValue::Signed(a), ObjInsArgValue::Signed(b)) => a == b,
|
(InstructionArgValue::Signed(a), InstructionArgValue::Signed(b)) => a == b,
|
||||||
(ObjInsArgValue::Unsigned(a), ObjInsArgValue::Unsigned(b)) => a == b,
|
(InstructionArgValue::Unsigned(a), InstructionArgValue::Unsigned(b)) => a == b,
|
||||||
(ObjInsArgValue::Signed(a), ObjInsArgValue::Unsigned(b))
|
(InstructionArgValue::Signed(a), InstructionArgValue::Unsigned(b))
|
||||||
| (ObjInsArgValue::Unsigned(b), ObjInsArgValue::Signed(a)) => *a as u64 == *b,
|
| (InstructionArgValue::Unsigned(b), InstructionArgValue::Signed(a)) => *a as u64 == *b,
|
||||||
(ObjInsArgValue::Opaque(a), ObjInsArgValue::Opaque(b)) => a == b,
|
(InstructionArgValue::Opaque(a), InstructionArgValue::Opaque(b)) => a == b,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_static(&self) -> InstructionArgValue<'static> {
|
||||||
|
match self {
|
||||||
|
InstructionArgValue::Signed(v) => InstructionArgValue::Signed(*v),
|
||||||
|
InstructionArgValue::Unsigned(v) => InstructionArgValue::Unsigned(*v),
|
||||||
|
InstructionArgValue::Opaque(v) => InstructionArgValue::Opaque(v.to_string().into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_static(self) -> InstructionArgValue<'static> {
|
||||||
|
match self {
|
||||||
|
InstructionArgValue::Signed(v) => InstructionArgValue::Signed(v),
|
||||||
|
InstructionArgValue::Unsigned(v) => InstructionArgValue::Unsigned(v),
|
||||||
|
InstructionArgValue::Opaque(v) => {
|
||||||
|
InstructionArgValue::Opaque(Cow::Owned(v.into_owned()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ObjInsArgValue {
|
impl fmt::Display for InstructionArgValue<'_> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
ObjInsArgValue::Signed(v) => write!(f, "{:#x}", ReallySigned(*v)),
|
InstructionArgValue::Signed(v) => write!(f, "{:#x}", ReallySigned(*v)),
|
||||||
ObjInsArgValue::Unsigned(v) => write!(f, "{:#x}", v),
|
InstructionArgValue::Unsigned(v) => write!(f, "{v:#x}"),
|
||||||
ObjInsArgValue::Opaque(v) => write!(f, "{}", v),
|
InstructionArgValue::Opaque(v) => write!(f, "{v}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum ObjInsArg {
|
pub enum InstructionArg<'a> {
|
||||||
PlainText(Cow<'static, str>),
|
Value(InstructionArgValue<'a>),
|
||||||
Arg(ObjInsArgValue),
|
|
||||||
Reloc,
|
Reloc,
|
||||||
BranchDest(u64),
|
BranchDest(u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjInsArg {
|
impl InstructionArg<'_> {
|
||||||
#[inline]
|
pub fn loose_eq(&self, other: &InstructionArg) -> bool {
|
||||||
pub fn is_plain_text(&self) -> bool { matches!(self, ObjInsArg::PlainText(_)) }
|
|
||||||
|
|
||||||
pub fn loose_eq(&self, other: &ObjInsArg) -> bool {
|
|
||||||
match (self, other) {
|
match (self, other) {
|
||||||
(ObjInsArg::Arg(a), ObjInsArg::Arg(b)) => a.loose_eq(b),
|
(InstructionArg::Value(a), InstructionArg::Value(b)) => a.loose_eq(b),
|
||||||
(ObjInsArg::Reloc, ObjInsArg::Reloc) => true,
|
(InstructionArg::Reloc, InstructionArg::Reloc) => true,
|
||||||
(ObjInsArg::BranchDest(a), ObjInsArg::BranchDest(b)) => a == b,
|
(InstructionArg::BranchDest(a), InstructionArg::BranchDest(b)) => a == b,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_static(&self) -> InstructionArg<'static> {
|
||||||
|
match self {
|
||||||
|
InstructionArg::Value(v) => InstructionArg::Value(v.to_static()),
|
||||||
|
InstructionArg::Reloc => InstructionArg::Reloc,
|
||||||
|
InstructionArg::BranchDest(v) => InstructionArg::BranchDest(*v),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_static(self) -> InstructionArg<'static> {
|
||||||
|
match self {
|
||||||
|
InstructionArg::Value(v) => InstructionArg::Value(v.into_static()),
|
||||||
|
InstructionArg::Reloc => InstructionArg::Reloc,
|
||||||
|
InstructionArg::BranchDest(v) => InstructionArg::BranchDest(v),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
pub struct ObjIns {
|
pub struct InstructionRef {
|
||||||
pub address: u64,
|
pub address: u64,
|
||||||
pub size: u8,
|
pub size: u8,
|
||||||
pub op: u16,
|
pub opcode: u16,
|
||||||
pub mnemonic: Cow<'static, str>,
|
|
||||||
pub args: Vec<ObjInsArg>,
|
|
||||||
pub reloc: Option<ObjReloc>,
|
|
||||||
pub branch_dest: Option<u64>,
|
pub branch_dest: Option<u64>,
|
||||||
/// Line number
|
|
||||||
pub line: Option<u32>,
|
|
||||||
/// Formatted instruction
|
|
||||||
pub formatted: String,
|
|
||||||
/// Original (unsimplified) instruction
|
|
||||||
pub orig: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjIns {
|
#[derive(Debug, Clone)]
|
||||||
/// Iterate over non-PlainText arguments.
|
pub struct ParsedInstruction {
|
||||||
#[inline]
|
pub ins_ref: InstructionRef,
|
||||||
pub fn iter_args(&self) -> impl DoubleEndedIterator<Item = &ObjInsArg> {
|
pub mnemonic: Cow<'static, str>,
|
||||||
self.args.iter().filter(|a| !a.is_plain_text())
|
pub args: Vec<InstructionArg<'static>>,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
|
||||||
pub enum ObjSymbolKind {
|
pub enum SymbolKind {
|
||||||
#[default]
|
#[default]
|
||||||
Unknown,
|
Unknown,
|
||||||
Function,
|
Function,
|
||||||
@@ -132,59 +245,195 @@ pub enum ObjSymbolKind {
|
|||||||
Section,
|
Section,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct ObjSymbol {
|
pub enum FlowAnalysisValue {
|
||||||
|
Text(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FlowAnalysisResult: core::fmt::Debug + Send {
|
||||||
|
fn get_argument_value_at_address(
|
||||||
|
&self,
|
||||||
|
address: u64,
|
||||||
|
argument: u8,
|
||||||
|
) -> Option<&FlowAnalysisValue>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
|
||||||
|
pub struct Symbol {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub demangled_name: Option<String>,
|
pub demangled_name: Option<String>,
|
||||||
pub address: u64,
|
pub address: u64,
|
||||||
pub section_address: u64,
|
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
pub size_known: bool,
|
pub kind: SymbolKind,
|
||||||
pub kind: ObjSymbolKind,
|
pub section: Option<usize>,
|
||||||
pub flags: ObjSymbolFlagSet,
|
pub flags: SymbolFlagSet,
|
||||||
pub orig_section_index: Option<usize>,
|
/// Alignment (from Metrowerks .comment section)
|
||||||
|
pub align: Option<NonZeroU32>,
|
||||||
/// Original virtual address (from .note.split section)
|
/// Original virtual address (from .note.split section)
|
||||||
pub virtual_address: Option<u64>,
|
pub virtual_address: Option<u64>,
|
||||||
/// Original index in object symbol table
|
|
||||||
pub original_index: Option<usize>,
|
|
||||||
pub bytes: Vec<u8>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ObjInfo {
|
#[derive(Debug)]
|
||||||
pub arch: Box<dyn ObjArch>,
|
pub struct Object {
|
||||||
pub path: Option<PathBuf>,
|
pub arch: Box<dyn Arch>,
|
||||||
pub timestamp: Option<FileTime>,
|
pub endianness: object::Endianness,
|
||||||
pub sections: Vec<ObjSection>,
|
pub symbols: Vec<Symbol>,
|
||||||
/// Common BSS symbols
|
pub sections: Vec<Section>,
|
||||||
pub common: Vec<ObjSymbol>,
|
|
||||||
/// Split object metadata (.note.split section)
|
/// Split object metadata (.note.split section)
|
||||||
pub split_meta: Option<SplitMeta>,
|
pub split_meta: Option<SplitMeta>,
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub path: Option<std::path::PathBuf>,
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub timestamp: Option<filetime::FileTime>,
|
||||||
|
pub flow_analysis_results: BTreeMap<u64, Box<dyn FlowAnalysisResult>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
impl Default for Object {
|
||||||
pub struct ObjReloc {
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
arch: ArchDummy::new(),
|
||||||
|
endianness: object::Endianness::Little,
|
||||||
|
symbols: vec![],
|
||||||
|
sections: vec![],
|
||||||
|
split_meta: None,
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
path: None,
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
timestamp: None,
|
||||||
|
flow_analysis_results: BTreeMap::<u64, Box<dyn FlowAnalysisResult>>::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object {
|
||||||
|
pub fn resolve_instruction_ref(
|
||||||
|
&self,
|
||||||
|
symbol_index: usize,
|
||||||
|
ins_ref: InstructionRef,
|
||||||
|
) -> Option<ResolvedInstructionRef<'_>> {
|
||||||
|
let symbol = self.symbols.get(symbol_index)?;
|
||||||
|
let section_index = symbol.section?;
|
||||||
|
let section = self.sections.get(section_index)?;
|
||||||
|
let offset = ins_ref.address.checked_sub(section.address)?;
|
||||||
|
let code = section.data.get(offset as usize..offset as usize + ins_ref.size as usize)?;
|
||||||
|
let relocation = section.relocation_at(self, ins_ref);
|
||||||
|
Some(ResolvedInstructionRef {
|
||||||
|
ins_ref,
|
||||||
|
symbol_index,
|
||||||
|
symbol,
|
||||||
|
section,
|
||||||
|
section_index,
|
||||||
|
code,
|
||||||
|
relocation,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn symbol_data(&self, symbol_index: usize) -> Option<&[u8]> {
|
||||||
|
let symbol = self.symbols.get(symbol_index)?;
|
||||||
|
let section_index = symbol.section?;
|
||||||
|
let section = self.sections.get(section_index)?;
|
||||||
|
let offset = symbol.address.checked_sub(section.address)?;
|
||||||
|
section.data.get(offset as usize..offset as usize + symbol.size as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn symbol_by_name(&self, name: &str) -> Option<usize> {
|
||||||
|
self.symbols.iter().position(|symbol| symbol.section.is_some() && symbol.name == name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_flow_analysis_result(&self, symbol: &Symbol) -> Option<&dyn FlowAnalysisResult> {
|
||||||
|
let key = symbol.section.unwrap_or_default() as u64 * 1024 * 1024 * 1024 + symbol.address;
|
||||||
|
self.flow_analysis_results.get(&key).map(|result| result.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_flow_analysis_result(
|
||||||
|
&mut self,
|
||||||
|
symbol: &Symbol,
|
||||||
|
result: Box<dyn FlowAnalysisResult>,
|
||||||
|
) {
|
||||||
|
let key = symbol.section.unwrap_or_default() as u64 * 1024 * 1024 * 1024 + symbol.address;
|
||||||
|
self.flow_analysis_results.insert(key, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_flow_analysis_result(&self) -> bool { !self.flow_analysis_results.is_empty() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub struct Relocation {
|
||||||
pub flags: RelocationFlags,
|
pub flags: RelocationFlags,
|
||||||
pub address: u64,
|
pub address: u64,
|
||||||
pub target: ObjSymbol,
|
pub target_symbol: usize,
|
||||||
pub addend: i64,
|
pub addend: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct SymbolRef {
|
pub enum RelocationFlags {
|
||||||
pub section_idx: usize,
|
Elf(u32),
|
||||||
pub symbol_idx: usize,
|
Coff(u16),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const SECTION_COMMON: usize = usize::MAX - 1;
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct ResolvedRelocation<'a> {
|
||||||
|
pub relocation: &'a Relocation,
|
||||||
|
pub symbol: &'a Symbol,
|
||||||
|
}
|
||||||
|
|
||||||
impl ObjInfo {
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub fn section_symbol(&self, symbol_ref: SymbolRef) -> (Option<&ObjSection>, &ObjSymbol) {
|
pub struct ResolvedSymbol<'obj> {
|
||||||
if symbol_ref.section_idx == SECTION_COMMON {
|
pub obj: &'obj Object,
|
||||||
let symbol = &self.common[symbol_ref.symbol_idx];
|
pub symbol_index: usize,
|
||||||
return (None, symbol);
|
pub symbol: &'obj Symbol,
|
||||||
|
pub section_index: usize,
|
||||||
|
pub section: &'obj Section,
|
||||||
|
pub data: &'obj [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct ResolvedInstructionRef<'obj> {
|
||||||
|
pub ins_ref: InstructionRef,
|
||||||
|
pub symbol_index: usize,
|
||||||
|
pub symbol: &'obj Symbol,
|
||||||
|
pub section_index: usize,
|
||||||
|
pub section: &'obj Section,
|
||||||
|
pub code: &'obj [u8],
|
||||||
|
pub relocation: Option<ResolvedRelocation<'obj>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
static DUMMY_SYMBOL: Symbol = Symbol {
|
||||||
|
name: String::new(),
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: SymbolKind::Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: SymbolFlagSet::empty(),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
static DUMMY_SECTION: Section = Section {
|
||||||
|
id: String::new(),
|
||||||
|
name: String::new(),
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: SectionKind::Unknown,
|
||||||
|
data: SectionData(Vec::new()),
|
||||||
|
flags: SectionFlagSet::empty(),
|
||||||
|
align: None,
|
||||||
|
relocations: Vec::new(),
|
||||||
|
line_info: BTreeMap::new(),
|
||||||
|
virtual_address: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Default for ResolvedInstructionRef<'_> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
ins_ref: InstructionRef::default(),
|
||||||
|
symbol_index: 0,
|
||||||
|
symbol: &DUMMY_SYMBOL,
|
||||||
|
section_index: 0,
|
||||||
|
section: &DUMMY_SECTION,
|
||||||
|
code: &[],
|
||||||
|
relocation: None,
|
||||||
}
|
}
|
||||||
let section = &self.sections[symbol_ref.section_idx];
|
|
||||||
let symbol = §ion.symbols[symbol_ref.symbol_idx];
|
|
||||||
(Some(section), symbol)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,165 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/src/obj/read.rs
|
||||||
|
expression: "(sections, symbols)"
|
||||||
|
---
|
||||||
|
(
|
||||||
|
[
|
||||||
|
Section {
|
||||||
|
id: ".text-0",
|
||||||
|
name: ".text",
|
||||||
|
address: 0,
|
||||||
|
size: 8,
|
||||||
|
kind: Code,
|
||||||
|
data: SectionData(
|
||||||
|
8,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
align: None,
|
||||||
|
relocations: [
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
address: 0,
|
||||||
|
target_symbol: 0,
|
||||||
|
addend: 4,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
address: 2,
|
||||||
|
target_symbol: 1,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
address: 4,
|
||||||
|
target_symbol: 0,
|
||||||
|
addend: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".data-combined",
|
||||||
|
name: ".data",
|
||||||
|
address: 0,
|
||||||
|
size: 12,
|
||||||
|
kind: Data,
|
||||||
|
data: SectionData(
|
||||||
|
12,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Combined),
|
||||||
|
align: None,
|
||||||
|
relocations: [
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
address: 0,
|
||||||
|
target_symbol: 2,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
Relocation {
|
||||||
|
flags: Elf(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
address: 4,
|
||||||
|
target_symbol: 2,
|
||||||
|
addend: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
line_info: {
|
||||||
|
0: 1,
|
||||||
|
8: 2,
|
||||||
|
},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".data-1",
|
||||||
|
name: ".data",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
align: None,
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Section {
|
||||||
|
id: ".data-2",
|
||||||
|
name: ".data",
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
data: SectionData(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
align: None,
|
||||||
|
relocations: [],
|
||||||
|
line_info: {},
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Symbol {
|
||||||
|
name: ".data",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Section,
|
||||||
|
section: Some(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "symbol",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 4,
|
||||||
|
size: 4,
|
||||||
|
kind: Object,
|
||||||
|
section: Some(
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "function",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 8,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: ".data",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
use std::{io, io::Write};
|
use alloc::{string::String, vec, vec::Vec};
|
||||||
|
|
||||||
use object::{elf::SHT_NOTE, Endian, ObjectSection};
|
use anyhow::{Result, anyhow};
|
||||||
|
use object::{Endian, ObjectSection, elf::SHT_NOTE};
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
use crate::util::align_data_to_4;
|
||||||
|
use crate::util::align_size_to_4;
|
||||||
|
|
||||||
pub const SPLITMETA_SECTION: &str = ".note.split";
|
pub const SPLITMETA_SECTION: &str = ".note.split";
|
||||||
pub const SHT_SPLITMETA: u32 = SHT_NOTE;
|
pub const SHT_SPLITMETA: u32 = SHT_NOTE;
|
||||||
@@ -27,10 +32,10 @@ const NT_SPLIT_MODULE_ID: u32 = u32::from_be_bytes(*b"MODI");
|
|||||||
const NT_SPLIT_VIRTUAL_ADDRESSES: u32 = u32::from_be_bytes(*b"VIRT");
|
const NT_SPLIT_VIRTUAL_ADDRESSES: u32 = u32::from_be_bytes(*b"VIRT");
|
||||||
|
|
||||||
impl SplitMeta {
|
impl SplitMeta {
|
||||||
pub fn from_section<E>(section: object::Section, e: E, is_64: bool) -> io::Result<Self>
|
pub fn from_section<E>(section: object::Section, e: E, is_64: bool) -> Result<Self>
|
||||||
where E: Endian {
|
where E: Endian {
|
||||||
let mut result = SplitMeta::default();
|
let mut result = SplitMeta::default();
|
||||||
let data = section.uncompressed_data().map_err(object_io_error)?;
|
let data = section.uncompressed_data().map_err(object_error)?;
|
||||||
let mut iter = NoteIterator::new(data.as_ref(), section.align(), e, is_64)?;
|
let mut iter = NoteIterator::new(data.as_ref(), section.align(), e, is_64)?;
|
||||||
while let Some(note) = iter.next(e)? {
|
while let Some(note) = iter.next(e)? {
|
||||||
if note.name != ELF_NOTE_SPLIT {
|
if note.name != ELF_NOTE_SPLIT {
|
||||||
@@ -38,20 +43,19 @@ impl SplitMeta {
|
|||||||
}
|
}
|
||||||
match note.n_type {
|
match note.n_type {
|
||||||
NT_SPLIT_GENERATOR => {
|
NT_SPLIT_GENERATOR => {
|
||||||
let string = String::from_utf8(note.desc.to_vec())
|
let string =
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
String::from_utf8(note.desc.to_vec()).map_err(anyhow::Error::new)?;
|
||||||
result.generator = Some(string);
|
result.generator = Some(string);
|
||||||
}
|
}
|
||||||
NT_SPLIT_MODULE_NAME => {
|
NT_SPLIT_MODULE_NAME => {
|
||||||
let string = String::from_utf8(note.desc.to_vec())
|
let string =
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
String::from_utf8(note.desc.to_vec()).map_err(anyhow::Error::new)?;
|
||||||
result.module_name = Some(string);
|
result.module_name = Some(string);
|
||||||
}
|
}
|
||||||
NT_SPLIT_MODULE_ID => {
|
NT_SPLIT_MODULE_ID => {
|
||||||
result.module_id =
|
result.module_id = Some(e.read_u32_bytes(
|
||||||
Some(e.read_u32_bytes(note.desc.try_into().map_err(|_| {
|
note.desc.try_into().map_err(|_| anyhow!("Invalid module ID size"))?,
|
||||||
io::Error::new(io::ErrorKind::InvalidData, "Invalid module ID size")
|
));
|
||||||
})?));
|
|
||||||
}
|
}
|
||||||
NT_SPLIT_VIRTUAL_ADDRESSES => {
|
NT_SPLIT_VIRTUAL_ADDRESSES => {
|
||||||
let vec = if is_64 {
|
let vec = if is_64 {
|
||||||
@@ -79,10 +83,11 @@ impl SplitMeta {
|
|||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_writer<E, W>(&self, writer: &mut W, e: E, is_64: bool) -> io::Result<()>
|
#[cfg(feature = "std")]
|
||||||
|
pub fn to_writer<E, W>(&self, writer: &mut W, e: E, is_64: bool) -> std::io::Result<()>
|
||||||
where
|
where
|
||||||
E: Endian,
|
E: Endian,
|
||||||
W: Write + ?Sized,
|
W: std::io::Write + ?Sized,
|
||||||
{
|
{
|
||||||
if let Some(generator) = &self.generator {
|
if let Some(generator) = &self.generator {
|
||||||
write_note_header(writer, e, NT_SPLIT_GENERATOR, generator.len())?;
|
write_note_header(writer, e, NT_SPLIT_GENERATOR, generator.len())?;
|
||||||
@@ -137,10 +142,9 @@ impl SplitMeta {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert an object::read::Error to an io::Error.
|
/// Convert an object::read::Error to a String.
|
||||||
fn object_io_error(err: object::read::Error) -> io::Error {
|
#[inline]
|
||||||
io::Error::new(io::ErrorKind::InvalidData, err)
|
fn object_error(err: object::read::Error) -> anyhow::Error { anyhow::Error::new(err) }
|
||||||
}
|
|
||||||
|
|
||||||
/// An ELF note entry.
|
/// An ELF note entry.
|
||||||
struct Note<'data> {
|
struct Note<'data> {
|
||||||
@@ -161,27 +165,27 @@ where E: Endian
|
|||||||
impl<'data, E> NoteIterator<'data, E>
|
impl<'data, E> NoteIterator<'data, E>
|
||||||
where E: Endian
|
where E: Endian
|
||||||
{
|
{
|
||||||
fn new(data: &'data [u8], align: u64, e: E, is_64: bool) -> io::Result<Self> {
|
fn new(data: &'data [u8], align: u64, e: E, is_64: bool) -> Result<Self> {
|
||||||
Ok(if is_64 {
|
Ok(if is_64 {
|
||||||
NoteIterator::B64(
|
NoteIterator::B64(
|
||||||
object::read::elf::NoteIterator::new(e, align, data).map_err(object_io_error)?,
|
object::read::elf::NoteIterator::new(e, align, data).map_err(object_error)?,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
NoteIterator::B32(
|
NoteIterator::B32(
|
||||||
object::read::elf::NoteIterator::new(e, align as u32, data)
|
object::read::elf::NoteIterator::new(e, align as u32, data)
|
||||||
.map_err(object_io_error)?,
|
.map_err(object_error)?,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next(&mut self, e: E) -> io::Result<Option<Note<'data>>> {
|
fn next(&mut self, e: E) -> Result<Option<Note<'data>>> {
|
||||||
match self {
|
match self {
|
||||||
NoteIterator::B32(iter) => Ok(iter.next().map_err(object_io_error)?.map(|note| Note {
|
NoteIterator::B32(iter) => Ok(iter.next().map_err(object_error)?.map(|note| Note {
|
||||||
n_type: note.n_type(e),
|
n_type: note.n_type(e),
|
||||||
name: note.name(),
|
name: note.name(),
|
||||||
desc: note.desc(),
|
desc: note.desc(),
|
||||||
})),
|
})),
|
||||||
NoteIterator::B64(iter) => Ok(iter.next().map_err(object_io_error)?.map(|note| Note {
|
NoteIterator::B64(iter) => Ok(iter.next().map_err(object_error)?.map(|note| Note {
|
||||||
n_type: note.n_type(e),
|
n_type: note.n_type(e),
|
||||||
name: note.name(),
|
name: note.name(),
|
||||||
desc: note.desc(),
|
desc: note.desc(),
|
||||||
@@ -190,16 +194,6 @@ where E: Endian
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn align_size_to_4(size: usize) -> usize { (size + 3) & !3 }
|
|
||||||
|
|
||||||
fn align_data_to_4<W: Write + ?Sized>(writer: &mut W, len: usize) -> io::Result<()> {
|
|
||||||
const ALIGN_BYTES: &[u8] = &[0; 4];
|
|
||||||
if len % 4 != 0 {
|
|
||||||
writer.write_all(&ALIGN_BYTES[..4 - len % 4])?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ELF note format:
|
// ELF note format:
|
||||||
// Name Size | 4 bytes (integer)
|
// Name Size | 4 bytes (integer)
|
||||||
// Desc Size | 4 bytes (integer)
|
// Desc Size | 4 bytes (integer)
|
||||||
@@ -208,10 +202,11 @@ fn align_data_to_4<W: Write + ?Sized>(writer: &mut W, len: usize) -> io::Result<
|
|||||||
// Desc | variable size, padded to a 4 byte boundary
|
// Desc | variable size, padded to a 4 byte boundary
|
||||||
const NOTE_HEADER_SIZE: usize = 12 + ((ELF_NOTE_SPLIT.len() + 4) & !3);
|
const NOTE_HEADER_SIZE: usize = 12 + ((ELF_NOTE_SPLIT.len() + 4) & !3);
|
||||||
|
|
||||||
fn write_note_header<E, W>(writer: &mut W, e: E, kind: u32, desc_len: usize) -> io::Result<()>
|
#[cfg(feature = "std")]
|
||||||
|
fn write_note_header<E, W>(writer: &mut W, e: E, kind: u32, desc_len: usize) -> std::io::Result<()>
|
||||||
where
|
where
|
||||||
E: Endian,
|
E: Endian,
|
||||||
W: Write + ?Sized,
|
W: std::io::Write + ?Sized,
|
||||||
{
|
{
|
||||||
writer.write_all(&e.write_u32_bytes(ELF_NOTE_SPLIT.len() as u32 + 1))?; // Name Size
|
writer.write_all(&e.write_u32_bytes(ELF_NOTE_SPLIT.len() as u32 + 1))?; // Name Size
|
||||||
writer.write_all(&e.write_u32_bytes(desc_len as u32))?; // Desc Size
|
writer.write_all(&e.write_u32_bytes(desc_len as u32))?; // Desc Size
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
use std::{
|
use alloc::{format, vec::Vec};
|
||||||
fmt::{LowerHex, UpperHex},
|
use core::fmt;
|
||||||
io::Read,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{Result, ensure};
|
||||||
use byteorder::{NativeEndian, ReadBytesExt};
|
|
||||||
use num_traits::PrimInt;
|
use num_traits::PrimInt;
|
||||||
use object::{Endian, Object};
|
use object::{Endian, Object};
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/44711012/how-do-i-format-a-signed-integer-to-a-sign-aware-hexadecimal-representation
|
// https://stackoverflow.com/questions/44711012/how-do-i-format-a-signed-integer-to-a-sign-aware-hexadecimal-representation
|
||||||
pub struct ReallySigned<N: PrimInt>(pub(crate) N);
|
pub struct ReallySigned<N: PrimInt>(pub N);
|
||||||
|
|
||||||
impl<N: PrimInt> LowerHex for ReallySigned<N> {
|
impl<N: PrimInt> fmt::LowerHex for ReallySigned<N> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let num = self.0.to_i64().unwrap();
|
let num = self.0.to_i64().unwrap();
|
||||||
let prefix = if f.alternate() { "0x" } else { "" };
|
let prefix = if f.alternate() { "0x" } else { "" };
|
||||||
let bare_hex = format!("{:x}", num.abs());
|
let bare_hex = format!("{:x}", num.abs());
|
||||||
@@ -20,8 +17,8 @@ impl<N: PrimInt> LowerHex for ReallySigned<N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<N: PrimInt> UpperHex for ReallySigned<N> {
|
impl<N: PrimInt> fmt::UpperHex for ReallySigned<N> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let num = self.0.to_i64().unwrap();
|
let num = self.0.to_i64().unwrap();
|
||||||
let prefix = if f.alternate() { "0x" } else { "" };
|
let prefix = if f.alternate() { "0x" } else { "" };
|
||||||
let bare_hex = format!("{:X}", num.abs());
|
let bare_hex = format!("{:X}", num.abs());
|
||||||
@@ -29,10 +26,66 @@ impl<N: PrimInt> UpperHex for ReallySigned<N> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_u32<R: Read>(obj_file: &object::File, reader: &mut R) -> Result<u32> {
|
pub fn read_u32(obj_file: &object::File, reader: &mut &[u8]) -> Result<u32> {
|
||||||
Ok(obj_file.endianness().read_u32(reader.read_u32::<NativeEndian>()?))
|
ensure!(reader.len() >= 4, "Not enough bytes to read u32");
|
||||||
|
let value = u32::from_ne_bytes(reader[..4].try_into()?);
|
||||||
|
*reader = &reader[4..];
|
||||||
|
Ok(obj_file.endianness().read_u32(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_u16<R: Read>(obj_file: &object::File, reader: &mut R) -> Result<u16> {
|
pub fn read_u16(obj_file: &object::File, reader: &mut &[u8]) -> Result<u16> {
|
||||||
Ok(obj_file.endianness().read_u16(reader.read_u16::<NativeEndian>()?))
|
ensure!(reader.len() >= 2, "Not enough bytes to read u16");
|
||||||
|
let value = u16::from_ne_bytes(reader[..2].try_into()?);
|
||||||
|
*reader = &reader[2..];
|
||||||
|
Ok(obj_file.endianness().read_u16(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn align_size_to_4(size: usize) -> usize { (size + 3) & !3 }
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub fn align_data_to_4<W: std::io::Write + ?Sized>(
|
||||||
|
writer: &mut W,
|
||||||
|
len: usize,
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
const ALIGN_BYTES: &[u8] = &[0; 4];
|
||||||
|
if !len.is_multiple_of(4) {
|
||||||
|
writer.write_all(&ALIGN_BYTES[..4 - len % 4])?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn align_u64_to(len: u64, align: u64) -> u64 { len + ((align - (len % align)) % align) }
|
||||||
|
|
||||||
|
pub fn align_data_slice_to(data: &mut Vec<u8>, align: u64) {
|
||||||
|
data.resize(align_u64_to(data.len() as u64, align) as usize, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float where we specifically care about comparing the raw bits rather than
|
||||||
|
// caring about IEEE semantics.
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct RawFloat(pub f32);
|
||||||
|
impl PartialEq for RawFloat {
|
||||||
|
fn eq(&self, other: &Self) -> bool { self.0.to_bits() == other.0.to_bits() }
|
||||||
|
}
|
||||||
|
impl Eq for RawFloat {}
|
||||||
|
impl Ord for RawFloat {
|
||||||
|
fn cmp(&self, other: &Self) -> core::cmp::Ordering { self.0.to_bits().cmp(&other.0.to_bits()) }
|
||||||
|
}
|
||||||
|
impl PartialOrd for RawFloat {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { Some(self.cmp(other)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Double where we specifically care about comparing the raw bits rather than
|
||||||
|
// caring about IEEE semantics.
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct RawDouble(pub f64);
|
||||||
|
impl PartialEq for RawDouble {
|
||||||
|
fn eq(&self, other: &Self) -> bool { self.0.to_bits() == other.0.to_bits() }
|
||||||
|
}
|
||||||
|
impl Eq for RawDouble {}
|
||||||
|
impl Ord for RawDouble {
|
||||||
|
fn cmp(&self, other: &Self) -> core::cmp::Ordering { self.0.to_bits().cmp(&other.0.to_bits()) }
|
||||||
|
}
|
||||||
|
impl PartialOrd for RawDouble {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { Some(self.cmp(other)) }
|
||||||
}
|
}
|
||||||
|
|||||||
46
objdiff-core/tests/arch_arm.rs
Normal file
46
objdiff-core/tests/arch_arm.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
use objdiff_core::{diff, obj};
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "arm")]
|
||||||
|
fn read_arm() {
|
||||||
|
let diff_config = diff::DiffObjConfig { ..Default::default() };
|
||||||
|
let obj = obj::read::parse(include_object!("data/arm/LinkStateItem.o"), &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(obj);
|
||||||
|
let symbol_idx =
|
||||||
|
obj.symbols.iter().position(|s| s.name == "_ZN13LinkStateItem12OnStateLeaveEi").unwrap();
|
||||||
|
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(diff.instruction_rows);
|
||||||
|
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "arm")]
|
||||||
|
fn read_thumb() {
|
||||||
|
let diff_config = diff::DiffObjConfig { ..Default::default() };
|
||||||
|
let obj = obj::read::parse(include_object!("data/arm/thumb.o"), &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(obj);
|
||||||
|
let symbol_idx = obj
|
||||||
|
.symbols
|
||||||
|
.iter()
|
||||||
|
.position(|s| s.name == "THUMB_BRANCH_ServerDisplay_UncategorizedMove")
|
||||||
|
.unwrap();
|
||||||
|
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(diff.instruction_rows);
|
||||||
|
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "arm")]
|
||||||
|
fn combine_text_sections() {
|
||||||
|
let diff_config = diff::DiffObjConfig { combine_text_sections: true, ..Default::default() };
|
||||||
|
let obj = obj::read::parse(include_object!("data/arm/enemy300.o"), &diff_config).unwrap();
|
||||||
|
let symbol_idx = obj.symbols.iter().position(|s| s.name == "Enemy300Draw").unwrap();
|
||||||
|
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(diff.instruction_rows);
|
||||||
|
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
47
objdiff-core/tests/arch_mips.rs
Normal file
47
objdiff-core/tests/arch_mips.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
use objdiff_core::{diff, obj};
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "mips")]
|
||||||
|
fn read_mips() {
|
||||||
|
let diff_config = diff::DiffObjConfig { mips_register_prefix: true, ..Default::default() };
|
||||||
|
let obj = obj::read::parse(include_object!("data/mips/main.c.o"), &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(obj);
|
||||||
|
let symbol_idx = obj.symbols.iter().position(|s| s.name == "ControlEntry").unwrap();
|
||||||
|
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(diff.instruction_rows);
|
||||||
|
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "mips")]
|
||||||
|
fn cross_endian_diff() {
|
||||||
|
let diff_config = diff::DiffObjConfig::default();
|
||||||
|
let obj_be = obj::read::parse(include_object!("data/mips/code_be.o"), &diff_config).unwrap();
|
||||||
|
assert_eq!(obj_be.endianness, object::Endianness::Big);
|
||||||
|
let obj_le = obj::read::parse(include_object!("data/mips/code_le.o"), &diff_config).unwrap();
|
||||||
|
assert_eq!(obj_le.endianness, object::Endianness::Little);
|
||||||
|
let left_symbol_idx = obj_be.symbols.iter().position(|s| s.name == "func_00000000").unwrap();
|
||||||
|
let right_symbol_idx =
|
||||||
|
obj_le.symbols.iter().position(|s| s.name == "func_00000000__FPcPc").unwrap();
|
||||||
|
let (left_diff, right_diff) =
|
||||||
|
diff::code::diff_code(&obj_be, &obj_le, left_symbol_idx, right_symbol_idx, &diff_config)
|
||||||
|
.unwrap();
|
||||||
|
// Although the objects differ in endianness, the instructions should match.
|
||||||
|
assert_eq!(left_diff.instruction_rows[0].kind, diff::InstructionDiffKind::None);
|
||||||
|
assert_eq!(right_diff.instruction_rows[0].kind, diff::InstructionDiffKind::None);
|
||||||
|
assert_eq!(left_diff.instruction_rows[1].kind, diff::InstructionDiffKind::None);
|
||||||
|
assert_eq!(right_diff.instruction_rows[1].kind, diff::InstructionDiffKind::None);
|
||||||
|
assert_eq!(left_diff.instruction_rows[2].kind, diff::InstructionDiffKind::None);
|
||||||
|
assert_eq!(right_diff.instruction_rows[2].kind, diff::InstructionDiffKind::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "mips")]
|
||||||
|
fn filter_non_matching() {
|
||||||
|
let diff_config = diff::DiffObjConfig::default();
|
||||||
|
let obj = obj::read::parse(include_object!("data/mips/vw_main.c.o"), &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(obj.symbols);
|
||||||
|
}
|
||||||
101
objdiff-core/tests/arch_ppc.rs
Normal file
101
objdiff-core/tests/arch_ppc.rs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
use objdiff_core::{
|
||||||
|
diff::{self, display},
|
||||||
|
obj,
|
||||||
|
obj::SectionKind,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "ppc")]
|
||||||
|
fn read_ppc() {
|
||||||
|
let diff_config = diff::DiffObjConfig::default();
|
||||||
|
let obj = obj::read::parse(include_object!("data/ppc/IObj.o"), &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(obj);
|
||||||
|
let symbol_idx =
|
||||||
|
obj.symbols.iter().position(|s| s.name == "Type2Text__10SObjectTagFUi").unwrap();
|
||||||
|
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(diff.instruction_rows);
|
||||||
|
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "ppc")]
|
||||||
|
fn read_dwarf1_line_info() {
|
||||||
|
let diff_config = diff::DiffObjConfig::default();
|
||||||
|
let obj = obj::read::parse(include_object!("data/ppc/m_Do_hostIO.o"), &diff_config).unwrap();
|
||||||
|
let line_infos = obj
|
||||||
|
.sections
|
||||||
|
.iter()
|
||||||
|
.filter(|s| s.kind == SectionKind::Code)
|
||||||
|
.map(|s| s.line_info.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
insta::assert_debug_snapshot!(line_infos);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "ppc")]
|
||||||
|
fn read_extab() {
|
||||||
|
let diff_config = diff::DiffObjConfig::default();
|
||||||
|
let obj = obj::read::parse(include_object!("data/ppc/NMWException.o"), &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "ppc")]
|
||||||
|
fn diff_ppc() {
|
||||||
|
let diff_config = diff::DiffObjConfig::default();
|
||||||
|
let mapping_config = diff::MappingConfig::default();
|
||||||
|
let target_obj =
|
||||||
|
obj::read::parse(include_object!("data/ppc/CDamageVulnerability_target.o"), &diff_config)
|
||||||
|
.unwrap();
|
||||||
|
let base_obj =
|
||||||
|
obj::read::parse(include_object!("data/ppc/CDamageVulnerability_base.o"), &diff_config)
|
||||||
|
.unwrap();
|
||||||
|
let diff =
|
||||||
|
diff::diff_objs(Some(&target_obj), Some(&base_obj), None, &diff_config, &mapping_config)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let target_diff = diff.left.as_ref().unwrap();
|
||||||
|
let base_diff = diff.right.as_ref().unwrap();
|
||||||
|
let sections_display = display::display_sections(
|
||||||
|
&target_obj,
|
||||||
|
target_diff,
|
||||||
|
display::SymbolFilter::None,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
insta::assert_debug_snapshot!(sections_display);
|
||||||
|
|
||||||
|
let target_symbol_idx = target_obj
|
||||||
|
.symbols
|
||||||
|
.iter()
|
||||||
|
.position(|s| s.name == "WeaponHurts__20CDamageVulnerabilityCFRC11CWeaponModei")
|
||||||
|
.unwrap();
|
||||||
|
let target_symbol_diff = &target_diff.symbols[target_symbol_idx];
|
||||||
|
let base_symbol_idx = base_obj
|
||||||
|
.symbols
|
||||||
|
.iter()
|
||||||
|
.position(|s| s.name == "WeaponHurts__20CDamageVulnerabilityCFRC11CWeaponModei")
|
||||||
|
.unwrap();
|
||||||
|
let base_symbol_diff = &base_diff.symbols[base_symbol_idx];
|
||||||
|
assert_eq!(target_symbol_diff.target_symbol, Some(base_symbol_idx));
|
||||||
|
assert_eq!(base_symbol_diff.target_symbol, Some(target_symbol_idx));
|
||||||
|
insta::assert_debug_snapshot!((target_symbol_diff, base_symbol_diff));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "ppc")]
|
||||||
|
fn read_vmx128_coff() {
|
||||||
|
let diff_config = diff::DiffObjConfig { combine_data_sections: true, ..Default::default() };
|
||||||
|
let obj = obj::read::parse(include_object!("data/ppc/vmx128.obj"), &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(obj);
|
||||||
|
let symbol_idx =
|
||||||
|
obj.symbols.iter().position(|s| s.name == "?FloatingPointExample@@YAXXZ").unwrap();
|
||||||
|
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(diff.instruction_rows);
|
||||||
|
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
79
objdiff-core/tests/arch_x86.rs
Normal file
79
objdiff-core/tests/arch_x86.rs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
use objdiff_core::{diff, diff::display::SymbolFilter, obj};
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "x86")]
|
||||||
|
fn read_x86() {
|
||||||
|
let diff_config = diff::DiffObjConfig::default();
|
||||||
|
let obj = obj::read::parse(include_object!("data/x86/staticdebug.obj"), &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(obj);
|
||||||
|
let symbol_idx = obj.symbols.iter().position(|s| s.name == "?PrintThing@@YAXXZ").unwrap();
|
||||||
|
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(diff.instruction_rows);
|
||||||
|
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "x86")]
|
||||||
|
fn read_x86_combine_sections() {
|
||||||
|
let diff_config = diff::DiffObjConfig {
|
||||||
|
combine_data_sections: true,
|
||||||
|
combine_text_sections: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let obj = obj::read::parse(include_object!("data/x86/rtest.obj"), &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(obj.sections);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "x86")]
|
||||||
|
fn read_x86_64() {
|
||||||
|
let diff_config = diff::DiffObjConfig::default();
|
||||||
|
let obj = obj::read::parse(include_object!("data/x86_64/vs2022.o"), &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(obj);
|
||||||
|
let symbol_idx =
|
||||||
|
obj.symbols.iter().position(|s| s.name == "?Dot@Vector@@QEAAMPEAU1@@Z").unwrap();
|
||||||
|
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(diff.instruction_rows);
|
||||||
|
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "x86")]
|
||||||
|
fn display_section_ordering() {
|
||||||
|
let diff_config = diff::DiffObjConfig::default();
|
||||||
|
let obj = obj::read::parse(include_object!("data/x86/basenode.obj"), &diff_config).unwrap();
|
||||||
|
let obj_diff =
|
||||||
|
diff::diff_objs(Some(&obj), None, None, &diff_config, &diff::MappingConfig::default())
|
||||||
|
.unwrap()
|
||||||
|
.left
|
||||||
|
.unwrap();
|
||||||
|
let section_display =
|
||||||
|
diff::display::display_sections(&obj, &obj_diff, SymbolFilter::None, false, false, false);
|
||||||
|
insta::assert_debug_snapshot!(section_display);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "x86")]
|
||||||
|
fn read_x86_jumptable() {
|
||||||
|
let diff_config = diff::DiffObjConfig::default();
|
||||||
|
let obj = obj::read::parse(include_object!("data/x86/jumptable.o"), &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(obj);
|
||||||
|
let symbol_idx = obj.symbols.iter().position(|s| s.name == "?test@@YAHH@Z").unwrap();
|
||||||
|
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(diff.instruction_rows);
|
||||||
|
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
|
||||||
|
insta::assert_snapshot!(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inferred size of functions should ignore symbols with specific prefixes
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "x86")]
|
||||||
|
fn read_x86_local_labels() {
|
||||||
|
let diff_config = diff::DiffObjConfig::default();
|
||||||
|
let obj = obj::read::parse(include_object!("data/x86/local_labels.obj"), &diff_config).unwrap();
|
||||||
|
insta::assert_debug_snapshot!(obj);
|
||||||
|
}
|
||||||
52
objdiff-core/tests/common.rs
Normal file
52
objdiff-core/tests/common.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
use objdiff_core::{
|
||||||
|
diff::{DiffObjConfig, SymbolDiff, display::DiffTextSegment},
|
||||||
|
obj::Object,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn display_diff(
|
||||||
|
obj: &Object,
|
||||||
|
diff: &SymbolDiff,
|
||||||
|
symbol_idx: usize,
|
||||||
|
diff_config: &DiffObjConfig,
|
||||||
|
) -> String {
|
||||||
|
let mut output = String::new();
|
||||||
|
for row in &diff.instruction_rows {
|
||||||
|
output.push('[');
|
||||||
|
let mut separator = false;
|
||||||
|
objdiff_core::diff::display::display_row(obj, symbol_idx, row, diff_config, |segment| {
|
||||||
|
if separator {
|
||||||
|
output.push_str(", ");
|
||||||
|
} else {
|
||||||
|
separator = true;
|
||||||
|
}
|
||||||
|
let DiffTextSegment { text, color, pad_to } = segment;
|
||||||
|
output.push_str(&format!("({text:?}, {color:?}, {pad_to:?})"));
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
output.push_str("]\n");
|
||||||
|
}
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct AlignedAs<Align, Bytes: ?Sized> {
|
||||||
|
pub _align: [Align; 0],
|
||||||
|
pub bytes: Bytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! include_bytes_align_as {
|
||||||
|
($align_ty:ty, $path:literal) => {{
|
||||||
|
static ALIGNED: &common::AlignedAs<$align_ty, [u8]> =
|
||||||
|
&common::AlignedAs { _align: [], bytes: *include_bytes!($path) };
|
||||||
|
&ALIGNED.bytes
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! include_object {
|
||||||
|
($path:literal) => {
|
||||||
|
include_bytes_align_as!(u64, $path)
|
||||||
|
};
|
||||||
|
}
|
||||||
BIN
objdiff-core/tests/data/arm/LinkStateItem.o
Normal file
BIN
objdiff-core/tests/data/arm/LinkStateItem.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/arm/enemy300.o
Normal file
BIN
objdiff-core/tests/data/arm/enemy300.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/arm/thumb.o
Normal file
BIN
objdiff-core/tests/data/arm/thumb.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/mips/code_be.o
Normal file
BIN
objdiff-core/tests/data/mips/code_be.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/mips/code_le.o
Normal file
BIN
objdiff-core/tests/data/mips/code_le.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/mips/main.c.o
Normal file
BIN
objdiff-core/tests/data/mips/main.c.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/mips/vw_main.c.o
Normal file
BIN
objdiff-core/tests/data/mips/vw_main.c.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/ppc/CDamageVulnerability_base.o
Normal file
BIN
objdiff-core/tests/data/ppc/CDamageVulnerability_base.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/ppc/CDamageVulnerability_target.o
Normal file
BIN
objdiff-core/tests/data/ppc/CDamageVulnerability_target.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/ppc/IObj.o
Normal file
BIN
objdiff-core/tests/data/ppc/IObj.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/ppc/NMWException.o
Normal file
BIN
objdiff-core/tests/data/ppc/NMWException.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/ppc/m_Do_hostIO.o
Normal file
BIN
objdiff-core/tests/data/ppc/m_Do_hostIO.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/ppc/vmx128.obj
Normal file
BIN
objdiff-core/tests/data/ppc/vmx128.obj
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/x86/basenode.obj
Normal file
BIN
objdiff-core/tests/data/x86/basenode.obj
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/x86/jumptable.o
Normal file
BIN
objdiff-core/tests/data/x86/jumptable.o
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/x86/local_labels.obj
Normal file
BIN
objdiff-core/tests/data/x86/local_labels.obj
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/x86/rtest.obj
Normal file
BIN
objdiff-core/tests/data/x86/rtest.obj
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/x86/staticdebug.obj
Normal file
BIN
objdiff-core/tests/data/x86/staticdebug.obj
Normal file
Binary file not shown.
BIN
objdiff-core/tests/data/x86_64/vs2022.o
Normal file
BIN
objdiff-core/tests/data/x86_64/vs2022.o
Normal file
Binary file not shown.
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_arm.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
[(Line(90), Dim, 5), (Address(0), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r12")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(8), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(90), Dim, 5), (Address(4), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bx", 32779), Normal, 10), (Argument(Opaque("r12")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(90), Dim, 5), (Address(8), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Symbol(Symbol { name: "esEnemyDraw", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_arm.rs
|
||||||
|
expression: diff.instruction_rows
|
||||||
|
---
|
||||||
|
[
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 76,
|
||||||
|
size: 4,
|
||||||
|
opcode: 32799,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 80,
|
||||||
|
size: 4,
|
||||||
|
opcode: 32779,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 84,
|
||||||
|
size: 4,
|
||||||
|
opcode: 65535,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
]
|
||||||
1876
objdiff-core/tests/snapshots/arch_arm__read_arm-2.snap
Normal file
1876
objdiff-core/tests/snapshots/arch_arm__read_arm-2.snap
Normal file
File diff suppressed because it is too large
Load Diff
112
objdiff-core/tests/snapshots/arch_arm__read_arm-3.snap
Normal file
112
objdiff-core/tests/snapshots/arch_arm__read_arm-3.snap
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_arm.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
[(Address(0), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stmdb", 32895), Normal, 10), (Argument(Opaque("sp")), Normal, 0), (Argument(Opaque("!")), Normal, 0), (Basic(", "), Normal, 0), (Basic("{"), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("lr")), Normal, 0), (Basic("}"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(4), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(8), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r1")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(12), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateBase12OnStateLeaveEi", demangled_name: Some("LinkStateBase::OnStateLeave(int)"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(16), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(20)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(20), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(10)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(24), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addls", 32770), Normal, 10), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("lsl")), Normal, 0), (Basic(" #"), Normal, 0), (Argument(Unsigned(2)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(28), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(32), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(36), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(40), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(44), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(192), Normal, 0), (Basic(" ~>"), Rotating(1), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(48), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(124), Normal, 0), (Basic(" ~>"), Rotating(2), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(52), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(56), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(140), Normal, 0), (Basic(" ~>"), Rotating(3), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(60), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(76), Normal, 0), (Basic(" ~>"), Rotating(4), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(64), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(152), Normal, 0), (Basic(" ~>"), Rotating(5), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(68), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(164), Normal, 0), (Basic(" ~>"), Rotating(6), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(72), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(164), Normal, 0), (Basic(" ~>"), Rotating(6), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(76), Normal, 5), (Basic(" ~> "), Rotating(4), 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(336)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(420), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(80), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(84), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN18UnkStruct_027e103c19func_ov000_020cf01cEv", demangled_name: Some("UnkStruct_027e103c::func_ov000_020cf01c()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(88), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldrb", 32800), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(224)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(92), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(96), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 32773), Normal, 10), (BranchDest(108), Normal, 0), (Basic(" ~>"), Rotating(7), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(100), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (BranchDest(424), Normal, 0), (Basic(" ~>"), Rotating(8), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(104), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN12EquipBombchu19func_ov014_0213ec64Ev", demangled_name: Some("EquipBombchu::func_ov014_0213ec64()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(108), Normal, 5), (Basic(" ~> "), Rotating(7), 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(308)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(424), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(112), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(116), Normal, 5), (Spacing(4), Normal, 0), (Opcode("blx", 32777), Normal, 10), (Symbol(Symbol { name: "_Z19func_ov014_0211fd04Pi", demangled_name: Some("func_ov014_0211fd04(int*)"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(120), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(124), Normal, 5), (Basic(" ~> "), Rotating(2), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(128), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(132), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateItem13StopUsingBombEi", demangled_name: Some("LinkStateItem::StopUsingBomb(int)"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(136), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(140), Normal, 5), (Basic(" ~> "), Rotating(3), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(144), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateItem13StopUsingRopeEv", demangled_name: Some("LinkStateItem::StopUsingRope()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(148), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(152), Normal, 5), (Basic(" ~> "), Rotating(5), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(156), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateItem15StopUsingHammerEv", demangled_name: Some("LinkStateItem::StopUsingHammer()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(160), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(164), Normal, 5), (Basic(" ~> "), Rotating(6), 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(248)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(420), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(168), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(172), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(176), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r1")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(180), Normal, 5), (Spacing(4), Normal, 0), (Opcode("strb", 32899), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(42)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(184), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN18UnkStruct_027e103c19func_ov000_020cf9dcEii", demangled_name: Some("UnkStruct_027e103c::func_ov000_020cf9dc(int, int)"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(188), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(192), Normal, 5), (Basic(" ~> "), Rotating(1), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(196), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateItem14StopUsingScoopEv", demangled_name: Some("LinkStateItem::StopUsingScoop()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(200), Normal, 5), (Basic(" ~> "), Rotating(0), 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(20)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(204), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mvn", 32829), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(208), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(212), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 32773), Normal, 10), (BranchDest(236), Normal, 0), (Basic(" ~>"), Rotating(9), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(216), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(220), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateBase12GetEquipItemEi", demangled_name: Some("LinkStateBase::GetEquipItem(int)"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(224), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(228), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(28)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(232), Normal, 5), (Spacing(4), Normal, 0), (Opcode("blx", 32778), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(236), Normal, 5), (Basic(" ~> "), Rotating(9), 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(20)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(240), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(9)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(244), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bgt", 32773), Normal, 10), (BranchDest(288), Normal, 0), (Basic(" ~>"), Rotating(10), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(248), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bge", 32773), Normal, 10), (BranchDest(296), Normal, 0), (Basic(" ~>"), Rotating(11), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(252), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(1)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(256), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bgt", 32773), Normal, 10), (BranchDest(308), Normal, 0), (Basic(" ~>"), Rotating(12), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(260), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mvn", 32829), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(264), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(268), Normal, 5), (Spacing(4), Normal, 0), (Opcode("blt", 32773), Normal, 10), (BranchDest(308), Normal, 0), (Basic(" ~>"), Rotating(12), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(272), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmpne", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(276), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmpne", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(1)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(280), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 32773), Normal, 10), (BranchDest(340), Normal, 0), (Basic(" ~>"), Rotating(13), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(284), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(308), Normal, 0), (Basic(" ~>"), Rotating(12), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(288), Normal, 5), (Basic(" ~> "), Rotating(10), 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(10)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(292), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 32773), Normal, 10), (BranchDest(308), Normal, 0), (Basic(" ~>"), Rotating(12), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(296), Normal, 5), (Basic(" ~> "), Rotating(11), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(300), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateBase18EquipItem_vfunc_28Ev", demangled_name: Some("LinkStateBase::EquipItem_vfunc_28()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(304), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(340), Normal, 0), (Basic(" ~>"), Rotating(13), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(308), Normal, 5), (Basic(" ~> "), Rotating(12), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(312), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateBase18EquipItem_vfunc_28Ev", demangled_name: Some("LinkStateBase::EquipItem_vfunc_28()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(316), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(4)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(320), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmpne", 32786), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(2)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(324), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 32773), Normal, 10), (BranchDest(340), Normal, 0), (Basic(" ~>"), Rotating(13), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(328), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateItem16GetLinkStateMoveEv", demangled_name: Some("LinkStateItem::GetLinkStateMove()"), address: 488, size: 16, kind: Function, section: Some(0), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(332), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(1)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(336), Normal, 5), (Spacing(4), Normal, 0), (Opcode("strb", 32899), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(20)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(340), Normal, 5), (Basic(" ~> "), Rotating(13), 0), (Opcode("mvn", 32829), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(344), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 32770), Normal, 10), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(80)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(348), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 32770), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(88)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(352), Normal, 5), (Spacing(4), Normal, 0), (Opcode("str", 32898), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(24)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(356), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(360), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 32773), Normal, 10), (BranchDest(384), Normal, 0), (Basic(" ~>"), Rotating(14), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(364), Normal, 5), (Basic(" ~> "), Rotating(15), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(368), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_Z19func_ov000_020b7e6cPi", demangled_name: Some("func_ov000_020b7e6c(int*)"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(372), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 32770), Normal, 10), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(4)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(376), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(380), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 32773), Normal, 10), (BranchDest(364), Normal, 0), (Basic(" ~>"), Rotating(15), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(384), Normal, 5), (Basic(" ~> "), Rotating(14), 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(36)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(428), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(388), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(392), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldrb", 32800), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(128)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(396), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(400), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 32773), Normal, 10), (BranchDest(408), Normal, 0), (Basic(" ~>"), Rotating(16), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(404), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13PlayerControl13StopFollowingEv", demangled_name: Some("PlayerControl::StopFollowing()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(408), Normal, 5), (Basic(" ~> "), Rotating(16), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(412), Normal, 5), (Spacing(4), Normal, 0), (Opcode("strb", 32899), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(38)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(416), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldmia", 32793), Normal, 10), (Argument(Opaque("sp")), Normal, 0), (Argument(Opaque("!")), Normal, 0), (Basic(", "), Normal, 0), (Basic("{"), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic("}"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(420), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Symbol(Symbol { name: "data_027e103c", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(424), Normal, 5), (Basic(" ~> "), Rotating(8), 0), (Opcode(".word", 65535), Normal, 10), (Symbol(Symbol { name: "data_027e1098", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(428), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Symbol(Symbol { name: "gPlayerControl", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
1958
objdiff-core/tests/snapshots/arch_arm__read_arm.snap
Normal file
1958
objdiff-core/tests/snapshots/arch_arm__read_arm.snap
Normal file
File diff suppressed because it is too large
Load Diff
1608
objdiff-core/tests/snapshots/arch_arm__read_thumb-2.snap
Normal file
1608
objdiff-core/tests/snapshots/arch_arm__read_thumb-2.snap
Normal file
File diff suppressed because it is too large
Load Diff
111
objdiff-core/tests/snapshots/arch_arm__read_thumb-3.snap
Normal file
111
objdiff-core/tests/snapshots/arch_arm__read_thumb-3.snap
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_arm.rs
|
||||||
|
assertion_line: 33
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
[(Line(37), Dim, 5), (Address(0), Normal, 5), (Spacing(4), Normal, 0), (Opcode("push", 56), Normal, 10), (Basic("{"), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r7")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("lr")), Normal, 0), (Basic("}"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(37), Dim, 5), (Address(2), Normal, 5), (Spacing(4), Normal, 0), (Opcode("sub", 74), Normal, 10), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(16)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(37), Dim, 5), (Address(4), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(39), Dim, 5), (Address(6), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(39), Dim, 5), (Address(8), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r1")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(39), Dim, 5), (Address(10), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r2")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(39), Dim, 5), (Address(12), Normal, 5), (Spacing(4), Normal, 0), (Opcode("str", 66), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(4)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(39), Dim, 5), (Address(14), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 19), Normal, 10), (Symbol(Symbol { name: "PokeSet_IsRemovedAll", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-4), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(39), Dim, 5), (Address(18), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 25), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(39), Dim, 5), (Address(20), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 15), Normal, 10), (BranchDest(212), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(44), Dim, 5), (Address(22), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldrh", 38), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(44), Dim, 5), (Address(24), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 25), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(164)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(44), Dim, 5), (Address(26), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 15), Normal, 10), (BranchDest(48), Normal, 0), (Basic(" ~>"), Rotating(1), 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(44), Dim, 5), (Address(28), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 34), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(184)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(216), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(44), Dim, 5), (Address(30), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 26), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(44), Dim, 5), (Address(32), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 15), Normal, 10), (BranchDest(94), Normal, 0), (Basic(" ~>"), Rotating(2), 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(47), Dim, 5), (Address(34), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 35), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(4)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(47), Dim, 5), (Address(36), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(47), Dim, 5), (Address(38), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(47), Dim, 5), (Address(40), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 19), Normal, 10), (Symbol(Symbol { name: "ServerDisplay_SkillSwap", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-4), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(86), Dim, 5), (Address(44), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 7), Normal, 10), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(16)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(86), Dim, 5), (Address(46), Normal, 5), (Spacing(4), Normal, 0), (Opcode("pop", 55), Normal, 10), (Basic("{"), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r7")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic("}"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(50), Dim, 5), (Address(48), Normal, 5), (Basic(" ~> "), Rotating(1), 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(50), Dim, 5), (Address(50), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(50), Dim, 5), (Address(52), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 19), Normal, 10), (Symbol(Symbol { name: "ServerEvent_CreateSubstitute", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-4), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(50), Dim, 5), (Address(56), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 25), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(50), Dim, 5), (Address(58), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 15), Normal, 10), (BranchDest(212), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(52), Dim, 5), (Address(60), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 34), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(156)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(220), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(52), Dim, 5), (Address(62), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 33), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(52), Dim, 5), (Address(64), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldrb", 36), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(5)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(52), Dim, 5), (Address(66), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lsl", 42), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(31)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(52), Dim, 5), (Address(68), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lsr", 44), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(31)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(52), Dim, 5), (Address(70), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 15), Normal, 10), (BranchDest(212), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(52), Dim, 5), (Address(72), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 46), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(1)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(52), Dim, 5), (Address(74), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bic", 17), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r1")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(52), Dim, 5), (Address(76), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 46), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(1)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(52), Dim, 5), (Address(78), Normal, 5), (Spacing(4), Normal, 0), (Opcode("orr", 54), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r2")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(52), Dim, 5), (Address(80), Normal, 5), (Spacing(4), Normal, 0), (Opcode("strb", 67), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(5)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(52), Dim, 5), (Address(82), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldrb", 36), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(5)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(52), Dim, 5), (Address(84), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 46), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(2)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(86), Dim, 5), (Address(86), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 7), Normal, 10), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(16)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(52), Dim, 5), (Address(88), Normal, 5), (Spacing(4), Normal, 0), (Opcode("orr", 54), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r2")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(52), Dim, 5), (Address(90), Normal, 5), (Spacing(4), Normal, 0), (Opcode("strb", 67), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(5)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(86), Dim, 5), (Address(92), Normal, 5), (Spacing(4), Normal, 0), (Opcode("pop", 55), Normal, 10), (Basic("{"), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r7")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic("}"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(57), Dim, 5), (Address(94), Normal, 5), (Basic(" ~> "), Rotating(2), 0), (Opcode("ldr", 34), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(128)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(224), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(57), Dim, 5), (Address(96), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 34), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(128)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(228), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(57), Dim, 5), (Address(98), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 4), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(57), Dim, 5), (Address(100), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 19), Normal, 10), (Symbol(Symbol { name: "HEManager_PushState", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-4), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(57), Dim, 5), (Address(104), Normal, 5), (Spacing(4), Normal, 0), (Opcode("str", 66), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(8)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(61), Dim, 5), (Address(106), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(61), Dim, 5), (Address(108), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 19), Normal, 10), (Symbol(Symbol { name: "BattleHandler_Result", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-4), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(61), Dim, 5), (Address(112), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r7")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(63), Dim, 5), (Address(114), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 6), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(12)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(63), Dim, 5), (Address(116), Normal, 5), (Spacing(4), Normal, 0), (Opcode("str", 66), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(63), Dim, 5), (Address(118), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 35), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(4)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(63), Dim, 5), (Address(120), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(63), Dim, 5), (Address(122), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(63), Dim, 5), (Address(124), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(63), Dim, 5), (Address(126), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 19), Normal, 10), (Symbol(Symbol { name: "ServerEvent_UncategorizedMove", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-4), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(63), Dim, 5), (Address(130), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 25), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(63), Dim, 5), (Address(132), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 15), Normal, 10), (BranchDest(168), Normal, 0), (Basic(" ~>"), Rotating(3), 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(65), Dim, 5), (Address(134), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(65), Dim, 5), (Address(136), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 19), Normal, 10), (Symbol(Symbol { name: "BattleHandler_Result", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-4), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(65), Dim, 5), (Address(140), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r7")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(66), Dim, 5), (Address(142), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 25), Normal, 10), (Argument(Opaque("r7")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(2)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(66), Dim, 5), (Address(144), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 15), Normal, 10), (BranchDest(168), Normal, 0), (Basic(" ~>"), Rotating(3), 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(68), Dim, 5), (Address(146), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 34), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(72)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(220), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(68), Dim, 5), (Address(148), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 33), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(68), Dim, 5), (Address(150), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldrb", 36), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(5)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(68), Dim, 5), (Address(152), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lsl", 42), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(31)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(68), Dim, 5), (Address(154), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lsr", 44), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(31)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(68), Dim, 5), (Address(156), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 15), Normal, 10), (BranchDest(168), Normal, 0), (Basic(" ~>"), Rotating(3), 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(68), Dim, 5), (Address(158), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 46), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(1)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(68), Dim, 5), (Address(160), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bic", 17), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r1")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(68), Dim, 5), (Address(162), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 46), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(1)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(68), Dim, 5), (Address(164), Normal, 5), (Spacing(4), Normal, 0), (Opcode("orr", 54), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r2")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(68), Dim, 5), (Address(166), Normal, 5), (Spacing(4), Normal, 0), (Opcode("strb", 67), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(5)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(72), Dim, 5), (Address(168), Normal, 5), (Basic(" ~> "), Rotating(3), 0), (Opcode("cmp", 25), Normal, 10), (Argument(Opaque("r7")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(1)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(72), Dim, 5), (Address(170), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bhi", 15), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(4), 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(75), Dim, 5), (Address(172), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 35), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(12)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(75), Dim, 5), (Address(174), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 25), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(75), Dim, 5), (Address(176), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 15), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(4), 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(75), Dim, 5), (Address(178), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 34), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(52)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(232), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(75), Dim, 5), (Address(180), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldrb", 37), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(75), Dim, 5), (Address(182), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lsl", 42), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(27)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(75), Dim, 5), (Address(184), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lsr", 44), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(31)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(75), Dim, 5), (Address(186), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 15), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(4), 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(77), Dim, 5), (Address(188), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(12)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(77), Dim, 5), (Address(190), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 34), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(44)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(236), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(77), Dim, 5), (Address(192), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 46), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(90)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(77), Dim, 5), (Address(194), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 46), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(71)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(77), Dim, 5), (Address(196), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 19), Normal, 10), (Symbol(Symbol { name: "SCQUE_PUT_MsgImpl", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-4), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(81), Dim, 5), (Address(200), Normal, 5), (Basic(" ~> "), Rotating(4), 0), (Opcode("ldr", 34), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(20)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(224), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(81), Dim, 5), (Address(202), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 35), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(8)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(81), Dim, 5), (Address(204), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 34), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(32)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(240), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(81), Dim, 5), (Address(206), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 4), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(81), Dim, 5), (Address(208), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 19), Normal, 10), (Symbol(Symbol { name: "HEManager_PopState", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-4), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(86), Dim, 5), (Address(212), Normal, 5), (Basic(" ~> "), Rotating(0), 0), (Opcode("add", 7), Normal, 10), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(16)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(86), Dim, 5), (Address(214), Normal, 5), (Spacing(4), Normal, 0), (Opcode("pop", 55), Normal, 10), (Basic("{"), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r7")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic("}"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(86), Dim, 5), (Address(216), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Basic("#"), Normal, 0), (Argument(Unsigned(285)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(86), Dim, 5), (Address(220), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Basic("#"), Normal, 0), (Argument(Unsigned(1192)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(86), Dim, 5), (Address(224), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Basic("#"), Normal, 0), (Argument(Unsigned(7544)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(86), Dim, 5), (Address(228), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Basic("#"), Normal, 0), (Argument(Unsigned(9103)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(86), Dim, 5), (Address(232), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Basic("#"), Normal, 0), (Argument(Unsigned(1930)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(86), Dim, 5), (Address(236), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Basic("#"), Normal, 0), (Argument(Unsigned(4294901760)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Line(86), Dim, 5), (Address(240), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Basic("#"), Normal, 0), (Argument(Unsigned(9129)), Normal, 0), (Eol, Normal, 0)]
|
||||||
3830
objdiff-core/tests/snapshots/arch_arm__read_thumb.snap
Normal file
3830
objdiff-core/tests/snapshots/arch_arm__read_thumb.snap
Normal file
File diff suppressed because it is too large
Load Diff
452
objdiff-core/tests/snapshots/arch_mips__filter_non_matching.snap
Normal file
452
objdiff-core/tests/snapshots/arch_mips__filter_non_matching.snap
Normal file
@@ -0,0 +1,452 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_mips.rs
|
||||||
|
expression: obj.symbols
|
||||||
|
---
|
||||||
|
[
|
||||||
|
Symbol {
|
||||||
|
name: "build/src/bodyprog/view/vw_main.i",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "[.text]",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Section,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "[.data]",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Section,
|
||||||
|
section: Some(
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "[.bss]",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Section,
|
||||||
|
section: Some(
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "gcc2_compiled.",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "__gnu_compiled_c",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: ".endfunc_80048AF4",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 424,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: ".endfunc_80048DA8",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 1028,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: ".endfunc_80048E3C",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 1264,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "[.reginfo]",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 24,
|
||||||
|
kind: Section,
|
||||||
|
section: Some(
|
||||||
|
4,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "[.MIPS.abiflags]",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 24,
|
||||||
|
kind: Section,
|
||||||
|
section: Some(
|
||||||
|
5,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "[.pdr]",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 320,
|
||||||
|
kind: Section,
|
||||||
|
section: Some(
|
||||||
|
6,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "[.gnu.attributes]",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 16,
|
||||||
|
kind: Section,
|
||||||
|
section: Some(
|
||||||
|
8,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Local),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "vwInitViewInfo",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 88,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "vwViewPointInfo",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "GsInitCoordinate2",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "vwSetViewInfo",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 784,
|
||||||
|
size: 96,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "vwGetViewCoord",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 88,
|
||||||
|
size: 12,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "vwGetViewPosition",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 100,
|
||||||
|
size: 40,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "vwGetViewAngle",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 140,
|
||||||
|
size: 48,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "func_80048AF4",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 188,
|
||||||
|
size: 236,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Global | Ignored),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "ratan2",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "SquareRoot0",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "func_80096C94",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "vwSetViewInfoDirectMatrix",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 696,
|
||||||
|
size: 88,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "func_80048AF4.NON_MATCHING",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 188,
|
||||||
|
size: 236,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Global | Ignored),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "vwSetCoordRefAndEntou",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 424,
|
||||||
|
size: 272,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "func_80096E78",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "shRsin",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "shRcos",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "vbSetRefView",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "vwMatrixToAngleYXZ",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 0,
|
||||||
|
size: 0,
|
||||||
|
kind: Unknown,
|
||||||
|
section: None,
|
||||||
|
flags: FlagSet(Global),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "func_80048DA8",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 880,
|
||||||
|
size: 148,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Global | Ignored),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "func_80048DA8.NON_MATCHING",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 880,
|
||||||
|
size: 148,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Global | Ignored),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "func_80048E3C",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 1028,
|
||||||
|
size: 236,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Global | Ignored),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
Symbol {
|
||||||
|
name: "func_80048E3C.NON_MATCHING",
|
||||||
|
demangled_name: None,
|
||||||
|
address: 1028,
|
||||||
|
size: 236,
|
||||||
|
kind: Function,
|
||||||
|
section: Some(
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
flags: FlagSet(Global | Ignored),
|
||||||
|
align: None,
|
||||||
|
virtual_address: None,
|
||||||
|
},
|
||||||
|
]
|
||||||
694
objdiff-core/tests/snapshots/arch_mips__read_mips-2.snap
Normal file
694
objdiff-core/tests/snapshots/arch_mips__read_mips-2.snap
Normal file
@@ -0,0 +1,694 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_mips.rs
|
||||||
|
expression: diff.instruction_rows
|
||||||
|
---
|
||||||
|
[
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 0,
|
||||||
|
size: 4,
|
||||||
|
opcode: 12,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 4,
|
||||||
|
size: 4,
|
||||||
|
opcode: 44,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 8,
|
||||||
|
size: 4,
|
||||||
|
opcode: 44,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 12,
|
||||||
|
size: 4,
|
||||||
|
opcode: 44,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 16,
|
||||||
|
size: 4,
|
||||||
|
opcode: 44,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 20,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 24,
|
||||||
|
size: 4,
|
||||||
|
opcode: 113,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 28,
|
||||||
|
size: 4,
|
||||||
|
opcode: 26,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 32,
|
||||||
|
size: 4,
|
||||||
|
opcode: 20,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 36,
|
||||||
|
size: 4,
|
||||||
|
opcode: 97,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 40,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 44,
|
||||||
|
size: 4,
|
||||||
|
opcode: 12,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 48,
|
||||||
|
size: 4,
|
||||||
|
opcode: 20,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 52,
|
||||||
|
size: 4,
|
||||||
|
opcode: 26,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 56,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 60,
|
||||||
|
size: 4,
|
||||||
|
opcode: 12,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 64,
|
||||||
|
size: 4,
|
||||||
|
opcode: 26,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 68,
|
||||||
|
size: 4,
|
||||||
|
opcode: 97,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 72,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 76,
|
||||||
|
size: 4,
|
||||||
|
opcode: 97,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 80,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: Some(
|
||||||
|
InstructionBranchFrom {
|
||||||
|
ins_idx: [
|
||||||
|
22,
|
||||||
|
],
|
||||||
|
branch_idx: 0,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 84,
|
||||||
|
size: 4,
|
||||||
|
opcode: 97,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 88,
|
||||||
|
size: 4,
|
||||||
|
opcode: 56,
|
||||||
|
branch_dest: Some(
|
||||||
|
80,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: Some(
|
||||||
|
InstructionBranchTo {
|
||||||
|
ins_idx: 20,
|
||||||
|
branch_idx: 0,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 92,
|
||||||
|
size: 4,
|
||||||
|
opcode: 113,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 96,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 100,
|
||||||
|
size: 4,
|
||||||
|
opcode: 20,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 104,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 108,
|
||||||
|
size: 4,
|
||||||
|
opcode: 12,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 112,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 116,
|
||||||
|
size: 4,
|
||||||
|
opcode: 16,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 120,
|
||||||
|
size: 4,
|
||||||
|
opcode: 20,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 124,
|
||||||
|
size: 4,
|
||||||
|
opcode: 12,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 128,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: Some(
|
||||||
|
InstructionBranchFrom {
|
||||||
|
ins_idx: [
|
||||||
|
36,
|
||||||
|
38,
|
||||||
|
44,
|
||||||
|
],
|
||||||
|
branch_idx: 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 132,
|
||||||
|
size: 4,
|
||||||
|
opcode: 12,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 136,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 140,
|
||||||
|
size: 4,
|
||||||
|
opcode: 113,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 144,
|
||||||
|
size: 4,
|
||||||
|
opcode: 55,
|
||||||
|
branch_dest: Some(
|
||||||
|
128,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: Some(
|
||||||
|
InstructionBranchTo {
|
||||||
|
ins_idx: 32,
|
||||||
|
branch_idx: 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 148,
|
||||||
|
size: 4,
|
||||||
|
opcode: 90,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 152,
|
||||||
|
size: 4,
|
||||||
|
opcode: 3,
|
||||||
|
branch_dest: Some(
|
||||||
|
128,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: Some(
|
||||||
|
InstructionBranchTo {
|
||||||
|
ins_idx: 32,
|
||||||
|
branch_idx: 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 156,
|
||||||
|
size: 4,
|
||||||
|
opcode: 113,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 160,
|
||||||
|
size: 4,
|
||||||
|
opcode: 2,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 164,
|
||||||
|
size: 4,
|
||||||
|
opcode: 60,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 168,
|
||||||
|
size: 4,
|
||||||
|
opcode: 77,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 172,
|
||||||
|
size: 4,
|
||||||
|
opcode: 113,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 176,
|
||||||
|
size: 4,
|
||||||
|
opcode: 54,
|
||||||
|
branch_dest: Some(
|
||||||
|
128,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: Some(
|
||||||
|
InstructionBranchTo {
|
||||||
|
ins_idx: 32,
|
||||||
|
branch_idx: 1,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
InstructionDiffRow {
|
||||||
|
ins_ref: Some(
|
||||||
|
InstructionRef {
|
||||||
|
address: 180,
|
||||||
|
size: 4,
|
||||||
|
opcode: 113,
|
||||||
|
branch_dest: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
kind: None,
|
||||||
|
branch_from: None,
|
||||||
|
branch_to: None,
|
||||||
|
arg_diff: [],
|
||||||
|
},
|
||||||
|
]
|
||||||
50
objdiff-core/tests/snapshots/arch_mips__read_mips-3.snap
Normal file
50
objdiff-core/tests/snapshots/arch_mips__read_mips-3.snap
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_mips.rs
|
||||||
|
expression: output
|
||||||
|
---
|
||||||
|
[(Address(0), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$sp")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$sp")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(-32)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(4), Normal, 5), (Spacing(4), Normal, 0), (Opcode("sd", 44), Normal, 10), (Argument(Opaque("$s0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$sp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(8), Normal, 5), (Spacing(4), Normal, 0), (Opcode("sd", 44), Normal, 10), (Argument(Opaque("$s1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(8)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$sp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(12), Normal, 5), (Spacing(4), Normal, 0), (Opcode("sd", 44), Normal, 10), (Argument(Opaque("$s2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(16)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$sp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(16), Normal, 5), (Spacing(4), Normal, 0), (Opcode("sd", 44), Normal, 10), (Argument(Opaque("$ra")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(24)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$sp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(20), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglSleep", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(24), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]
|
||||||
|
[(Address(28), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lw", 26), Normal, 10), (Argument(Opaque("$a1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%gp_rel("), Normal, 0), (Symbol(Symbol { name: "WorkEnd", demangled_name: None, address: 64, size: 4, kind: Object, section: Some(8), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$gp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(32), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lui", 20), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%hi("), Normal, 0), (Symbol(Symbol { name: "[.sdata]", demangled_name: None, address: 0, size: 64, kind: Section, section: Some(8), flags: FlagSet(Local), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(36), Normal, 5), (Spacing(4), Normal, 0), (Opcode("daddu", 97), Normal, 10), (Argument(Opaque("$a2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(40), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglSoundLoadEffect", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(44), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%lo("), Normal, 0), (Symbol(Symbol { name: "[.sdata]", demangled_name: None, address: 0, size: 64, kind: Section, section: Some(8), flags: FlagSet(Local), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(48), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lui", 20), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%hi("), Normal, 0), (Symbol(Symbol { name: "PacketBottomNewVu1DropMicroCode", demangled_name: None, address: 0, size: 12, kind: Object, section: Some(7), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(52), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lw", 26), Normal, 10), (Argument(Opaque("$a1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%gp_rel("), Normal, 0), (Symbol(Symbol { name: "WorkEnd", demangled_name: None, address: 64, size: 4, kind: Object, section: Some(8), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$gp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(56), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglSoundLoadSwd", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(60), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%lo("), Normal, 0), (Symbol(Symbol { name: "PacketBottomNewVu1DropMicroCode", demangled_name: None, address: 0, size: 12, kind: Object, section: Some(7), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(64), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lw", 26), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%gp_rel("), Normal, 0), (Symbol(Symbol { name: "WorkEnd", demangled_name: None, address: 64, size: 4, kind: Object, section: Some(8), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$gp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(68), Normal, 5), (Spacing(4), Normal, 0), (Opcode("daddu", 97), Normal, 10), (Argument(Opaque("$a1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(72), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "SsdAddWaveData", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(76), Normal, 5), (Spacing(4), Normal, 0), (Opcode("daddu", 97), Normal, 10), (Argument(Opaque("$a2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(80), Normal, 5), (Basic(" ~> "), Rotating(0), 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "SsdSpuDmaCompleted", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(84), Normal, 5), (Spacing(4), Normal, 0), (Opcode("daddu", 97), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(88), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bnez", 56), Normal, 10), (Argument(Opaque("$v0")), Normal, 0), (Basic(", "), Normal, 0), (BranchDest(80), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(92), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]
|
||||||
|
[(Address(96), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglRenderDispOn", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(100), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lui", 20), Normal, 10), (Argument(Opaque("$s1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Unsigned(255)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(104), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglCdLoadOverlay", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(108), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(2)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(112), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "LogoFirst", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(116), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ori", 16), Normal, 10), (Argument(Opaque("$s1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$s1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Unsigned(65535)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(120), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lui", 20), Normal, 10), (Argument(Opaque("$v0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%hi("), Normal, 0), (Symbol(Symbol { name: "Title", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(124), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$s2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$v0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%lo("), Normal, 0), (Symbol(Symbol { name: "Title", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(128), Normal, 5), (Basic(" ~> "), Rotating(1), 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglCdLoadOverlay", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(132), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(2)), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(136), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "Title", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(140), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]
|
||||||
|
[(Address(144), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beqz", 55), Normal, 10), (Argument(Opaque("$v0")), Normal, 0), (Basic(", "), Normal, 0), (BranchDest(128), Normal, 0), (Basic(" ~>"), Rotating(1), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(148), Normal, 5), (Spacing(4), Normal, 0), (Opcode("and", 90), Normal, 10), (Argument(Opaque("$s0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$v0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$s1")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(152), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 3), Normal, 10), (Argument(Opaque("$s0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$s2")), Normal, 0), (Basic(", "), Normal, 0), (BranchDest(128), Normal, 0), (Basic(" ~>"), Rotating(1), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(156), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]
|
||||||
|
[(Address(160), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglCdLoadOverlay", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(164), Normal, 5), (Spacing(4), Normal, 0), (Opcode("srl", 60), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$v0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("24")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(168), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jalr", 77), Normal, 10), (Argument(Opaque("$s0")), Normal, 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(172), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]
|
||||||
|
[(Address(176), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 54), Normal, 10), (BranchDest(128), Normal, 0), (Basic(" ~>"), Rotating(1), 0), (Eol, Normal, 0)]
|
||||||
|
[(Address(180), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]
|
||||||
1494
objdiff-core/tests/snapshots/arch_mips__read_mips.snap
Normal file
1494
objdiff-core/tests/snapshots/arch_mips__read_mips.snap
Normal file
File diff suppressed because it is too large
Load Diff
5292
objdiff-core/tests/snapshots/arch_ppc__diff_ppc-2.snap
Normal file
5292
objdiff-core/tests/snapshots/arch_ppc__diff_ppc-2.snap
Normal file
File diff suppressed because it is too large
Load Diff
87
objdiff-core/tests/snapshots/arch_ppc__diff_ppc.snap
Normal file
87
objdiff-core/tests/snapshots/arch_ppc__diff_ppc.snap
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
---
|
||||||
|
source: objdiff-core/tests/arch_ppc.rs
|
||||||
|
assertion_line: 70
|
||||||
|
expression: sections_display
|
||||||
|
---
|
||||||
|
[
|
||||||
|
SectionDisplay {
|
||||||
|
id: ".comm",
|
||||||
|
name: ".comm",
|
||||||
|
size: 0,
|
||||||
|
match_percent: None,
|
||||||
|
symbols: [
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 11,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 12,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 13,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 14,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
SectionDisplay {
|
||||||
|
id: ".ctors-0",
|
||||||
|
name: ".ctors",
|
||||||
|
size: 4,
|
||||||
|
match_percent: Some(
|
||||||
|
100.0,
|
||||||
|
),
|
||||||
|
symbols: [
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 2,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
SectionDisplay {
|
||||||
|
id: ".text-0",
|
||||||
|
name: ".text",
|
||||||
|
size: 3060,
|
||||||
|
match_percent: Some(
|
||||||
|
58.662746,
|
||||||
|
),
|
||||||
|
symbols: [
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 3,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 10,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 9,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 8,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 7,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 6,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 5,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
SectionDisplaySymbol {
|
||||||
|
symbol: 4,
|
||||||
|
is_mapping_symbol: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user