Compare commits

..

95 Commits

Author SHA1 Message Date
Luke Street 233839346a Version v2.0.0-alpha.5 2024-06-20 20:31:31 -06:00
Luke Street 95615c2ec5 Improve MIPS ABI auto-detection 2024-06-20 19:57:18 -06:00
Aetias 97981160f4 ARMv4T (GBA) and ARMv6K (3DS) support (#75)
* Initial ARM support

* Disassemble const pool reloc

* Disasm ARM/Thumb/data based on mapping symbols

* Fallback to mapping symbol `$a`

* Support multiple DWARF sequences

* Update line info

* Rework DWARF line info parsing

- Properly handles multiple sections
  in DWARF 1
- line_info moved into ObjSection
- DWARF 2 parser no longer errors with
  no .text section
- Both parsers properly skip empty
  sections

* Simplify line_info (no Option)

* Get line info from section; output formatted ins string

* Unwrap code section in `arm.rs`

* Handle reloc `R_ARM_SBREL32`

* Update ARM disassembler

* Update README.md

* Format

* Revert "Update README.md"

This reverts commit 8bbfcc6f45.

* Update README.md

* Detect ARM version; support ARMv4T and v6K

* Combobox to force ARM version

* Clear LSB in ARM symbol addresses

* Support big-endian ARM ELF files

* Bump `unarm`, `arm-attr`

* Handle ARM implicit addends

* Update README.md

* Explicitly handle all ARM argument types

* Format

* Display more ARM relocs

* Mask LSB on ARM code symbols only

* Read ARM implicit addends

* Format

---------

Co-authored-by: Luke Street <luke.street@encounterpc.com>
2024-06-20 18:36:25 -06:00
Aetias 1fd901a863 Option to combine data sections (#76)
Co-authored-by: Luke Street <luke.street@encounterpc.com>
2024-06-18 22:05:24 -06:00
Luke Street 759d55994a Fix clippy warning 2024-06-18 21:49:19 -06:00
Luke Street 9710ccc38a Add graphics backend configuration
Hopefully #74, #73, #56
2024-06-05 18:01:03 -06:00
Luke Street 79cd460333 Update notify-rs to fix WSL crash
Fixes #66
2024-06-04 17:13:54 -06:00
Aetias a5a6a3928e Fix read error on objects with no .text section (#67)
* Fix read error on objects with no .text section

* Fix read error on DWARF 1.1 objects

* Revert DWARF 1 changes

---------

Co-authored-by: Luke Street <luke@street.dev>
2024-06-03 19:47:38 -06:00
Luke Street fc54e93681 API updates for ARM backend 2024-06-03 19:37:48 -06:00
Luke Street c9b11db2fa Update README.md 2024-06-03 19:09:35 -06:00
Aetias b991960080 ARMv5TE (DS) support (#68)
* Initial ARM support

* Disassemble const pool reloc

* Disasm ARM/Thumb/data based on mapping symbols

* Fallback to mapping symbol `$a`

* Support multiple DWARF sequences

* Update line info

* Rework DWARF line info parsing

- Properly handles multiple sections
  in DWARF 1
- line_info moved into ObjSection
- DWARF 2 parser no longer errors with
  no .text section
- Both parsers properly skip empty
  sections

* Simplify line_info (no Option)

* Get line info from section; output formatted ins string

* Unwrap code section in `arm.rs`

* Handle reloc `R_ARM_SBREL32`

* Update ARM disassembler

* Update README.md

* Format

* Revert "Update README.md"

This reverts commit 8bbfcc6f45.

* Update README.md

---------

Co-authored-by: Luke Street <luke.street@encounterpc.com>
2024-06-03 19:08:49 -06:00
Luke Street 425dc8546b More descriptive message for build failure
Resolves #64
2024-06-03 19:06:19 -06:00
Luke Street 9e04357d9f Use solid scrollbar in egui
Resolves #69
2024-06-03 19:03:33 -06:00
Luke Street 6037c12ad0 Disable lto to workaround crash
See #66
2024-06-03 18:58:25 -06:00
Luke Street b15f643713 Bump version to 2.0.0-alpha.3 2024-06-03 18:54:46 -06:00
Luke Street 3f82c1a50f objdiff-core API adjustments
- Allows using process_code without
  constructing an ObjInfo
- Allows creating an arch without
  having to provide an object

Used in decomp-toolkit
2024-06-03 18:52:32 -06:00
Luke Street 0ea6242669 Bump rabbitizer version (fixes crash) 2024-06-03 18:50:22 -06:00
Luke Street 0c20a0d9cd Update README.md 2024-05-21 18:12:58 -06:00
Luke Street f30b3cfae2 Default "Space between args" -> true 2024-05-21 18:09:46 -06:00
Luke Street 9e57a66a05 Auto-detect MIPS ABI/category & add config
Under Diff Options -> Arch Settings, one
can override the ABI/instruction category
2024-05-21 18:06:14 -06:00
Luke Street e254af5acf Support bss and text section diffing
Display section diff % in symbols view
2024-05-21 12:02:00 -06:00
Robin Avery 320efcb8cb objdiff-cli report: Support data sections (#49)
* objdiff-cli report: Support data sections

* Minor fixes for section match %

---------

Co-authored-by: Luke Street <luke.street@encounterpc.com>
2024-05-21 12:01:10 -06:00
Luke Street 7148b51fe0 x86: Handle IMAGE_REL_I386_REL32 LabelAddress
Resolves #57
2024-05-21 10:16:45 -06:00
Luke Street dc0c170db9 Add .obj to object select filter
Resolves #54
2024-05-21 10:12:40 -06:00
Aetias 31e9c14681 Allow None section when parsing line info
Fixes an error upon ending the last DWARF sequence
2024-05-21 10:10:16 -06:00
Luke Street 94f1f07b00 Bump to 2.0.0-alpha.1 & fix version checks 2024-05-21 09:55:45 -06:00
Aetias f5b5a612fc Display correct line numbers for multiple .text sections (#63)
* Support multiple DWARF sequences

* Rework DWARF line info parsing

- Properly handles multiple sections
  in DWARF 1
- line_info moved into ObjSection
- DWARF 2 parser no longer errors with
  no .text section
- Both parsers properly skip empty
  sections

* Simplify line_info (no Option)

---------

Co-authored-by: Luke Street <luke.street@encounterpc.com>
2024-05-21 09:55:39 -06:00
Luke Street 22a24f37f5 Diff data symbols & improve symbol match logic 2024-05-20 23:53:37 -06:00
Aetias 854dc9e4f5 Use base arch when processing base code (#62) 2024-05-20 17:38:58 -06:00
Luke Street 5bfaaaaf65 Instruction hover / context menu improvements 2024-05-20 17:38:20 -06:00
cadmic 8b36fa4fc6 Fix size of .note.split section (#61)
* Fix size of .note.split section

* clippy fix

---------

Co-authored-by: Luke Street <luke@street.dev>
2024-05-16 18:29:24 -06:00
Luke Street 660e6c879e Update README.md 2024-05-15 19:01:55 -06:00
Aetias db726a68a6 Strip distro root prefix (#58) 2024-05-15 18:56:08 -06:00
Aetias b457453639 Add custom make args (#59) 2024-05-15 18:53:14 -06:00
Luke Street 3e5008524e cargo fmt & cargo deny fix 2024-04-30 20:45:45 -06:00
Luke Street 2c46286aff Update all dependencies & use ppc750cl InsIter 2024-04-30 20:06:04 -06:00
Luke Street 106652ae7d Fix PPC branch display; update README.md 2024-03-22 23:06:41 -06:00
Luke Street 30d14870ef Update ppc750cl, add Itanium demangler & cleanup 2024-03-21 21:36:50 -06:00
Luke Street e7991cb28d cargo fmt 2024-03-18 22:56:57 -06:00
Luke Street 4dfc28fc68 Diff cleanup & fixes 2024-03-18 22:56:13 -06:00
Luke Street 3c74b89f15 Restructure diffing code & initial 3-way diffing (WIP) 2024-03-18 18:10:18 -06:00
Luke Street 1343f4fd2b cargo fmt 2024-03-17 12:20:25 -06:00
Luke Street 9df98f263e Move all architecture-specific code into modules
No more scattered relocation handling and
feature checks. Everything will go through
the ObjArch trait, which makes it easier
to add new architectures going forward.
2024-03-17 12:16:47 -06:00
Luke Street bbe49eb8b4 Initial x86 support
Includes a bit of work to make adding new
architectures easier in the future
2024-03-16 23:30:27 -06:00
Luke Street aecb078b2a ci: Update sccache-action version 2024-03-13 18:34:28 -06:00
Luke Street a5668b484b Update all dependencies 2024-03-13 18:20:46 -06:00
Luke Street ef41e393d4 Resolve dependency advisories 2024-03-04 18:19:08 -07:00
Luke Street 20e42a499a Rework .splitmeta, now .note.split
Uses actual ELF .note format, which is
more standard and handled better by mwld.
2024-03-04 18:06:21 -07:00
Luke Street c39795ae2c Use actual decomp.me host 2024-03-04 18:03:32 -07:00
Luke Street 49ee9b44aa Remove "Algorithm" menu item 2024-03-04 18:03:20 -07:00
Robin Avery 341c1d4b33 Fix release CI (and add sccache) (#52)
* Fix release CI (and add `sccache`)

* Rename `objdiff-gui` binary to `objdiff`
2024-03-02 22:42:24 -07:00
Robin Avery 9f4a1e86cd objdiff-cli diff: Reduce duplicate key event code (#51) 2024-03-02 18:47:54 -07:00
Robin Avery ed5d092b11 objdiff-cli diff: Support "Relax relocation diffs" (#50)
Bound to the `-x` flag or the `x` key.
2024-03-02 18:47:18 -07:00
Robin Avery 023dd7a55b objdiff-cli diff: Accept any kind of unit path (#48)
* objdiff-cli diff: Accept any kind of unit path

* Appease clippy

* Call `resolve_paths` in slightly fewer cases
2024-03-01 18:18:27 -07:00
Luke Street 3b1249e1ab objdiff-cli diff: Add horizontal scrolling 2024-03-01 01:30:47 -07:00
Luke Street cb13638e07 objdiff-cli: Migrate to ratatui for rendering 2024-03-01 01:03:17 -07:00
Robin Avery 37ddbb7f4a cli: Log to stderr instead of stdout (#46)
Fixes pipe issues.
2024-02-29 22:27:10 -07:00
Robin Avery b80d361e91 cli report: Generate virtual addresses as uppercase (#45)
Matches dtk symbols.txt and most projects' identifiers.
2024-02-29 22:22:59 -07:00
Robin Avery fd27f4d0cd cli diff: Resolve object and project if not specified (#44)
* cli diff: Resolve object and project if not specified

* Make `symbol` positional

* Short circuit ambiguous matches

* Tighten argument matching

* Speed up function lookup
2024-02-29 22:22:41 -07:00
Robin Avery 5cfd04fd4f Add #[serde(default)] to ReportFunction::address (#43) 2024-02-29 11:21:30 -07:00
Luke Street 5b9ac93c08 ci: Build both objdiff-cli and objdiff-gui 2024-02-28 21:52:35 -07:00
Luke Street 39a13f4d36 objdiff-cli diff & report changes, support .splitmeta object section
- Add `objdiff-cli report changes` for diffing two reports
- Unify some click-to-highlight logic between CLI and GUI
- Load .splitmeta section for extra object metadata (original virtual addr, etc)
- More work on objdiff-cli diff
2024-02-28 21:44:53 -07:00
Ryan Burns 28348606bf Handle ^F, ^B, ^U and ^D readline shortcuts in pager (#42) 2024-02-28 19:33:15 -07:00
Luke Street fb24063c54 objdiff-cli diff: Click-to-highlight & build fixes 2024-02-27 22:52:18 -07:00
Luke Street cff6a230a3 Remove alternate diff algorithms, only keep Patience 2024-02-27 21:18:42 -07:00
Luke Street 9a7d2bcebf Experimental objdiff-cli (WIP) 2024-02-27 18:47:51 -07:00
Luke Street 4eba5f71b0 Split into objdiff-core / objdiff-gui; update egui to 0.26.2 2024-02-26 18:48:48 -07:00
Luke Street 0a85c498c5 Version 1.0.0 2024-01-22 00:32:54 -07:00
Luke Street c2fcf2797b Export function to decomp.me scratch (beta) 2024-01-22 00:31:43 -07:00
Luke Street e88a58ba39 Option to relax relocation diffs
Ignores differences in relocation targets. (Address, name, etc)

Resolves #34
2024-01-22 00:14:03 -07:00
Luke Street 02f521a528 Disable more options when project config is loaded 2024-01-21 23:58:10 -07:00
Luke Street 197d1247a8 Highlight: Consider uimm/simm/offset all equivalent
Fixes #33
2024-01-21 23:48:12 -07:00
Luke Street eef9598e76 Add DWARF 2+ line info support
Resolves #37
2024-01-21 23:38:52 -07:00
Luke Street 405a2a82db Upgrade all dependencies (+ egui/eframe 0.25.0) 2024-01-20 23:41:48 -07:00
Luke Street 4cdad8a519 Re-enable wgpu and wsl features; rework WSL config
Improve build failure log view & add copy buttons
2024-01-20 23:29:05 -07:00
Luke Street b74a49ed0c Upgrade to egui/eframe 0.24.1 2023-12-11 13:36:00 -05:00
Luke Street e1079db93a Add font loading & configuration 2023-11-28 23:13:51 -05:00
Luke Street 879e03eed5 All one line (thanks PowerShell) 2023-11-27 19:55:17 -05:00
Luke Street 53e6e0c7c4 Build macOS with wgpu enabled 2023-11-27 19:52:12 -05:00
Luke Street 67cea2a8d9 Update README.md 2023-11-24 23:17:35 -05:00
Luke Street e4f97adbdd Version 0.6.1 2023-11-22 00:11:33 -05:00
Luke Street 0ec7bf078b Highlight: Consider reg offsets and signed immediates equivalent
Fixes #32
2023-11-22 00:07:21 -05:00
Luke Street 74e89130a8 Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).

This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.

For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 14:34:26 -05:00
Luke Street 236e4d8d26 CI updates, update deny.toml, clippy fix 2023-11-21 12:16:15 -05:00
Luke Street b900ae5a00 Disable WSL integration
With WSL, objdiff is unable to get filesystem notifications.
It's recommended to run objdiff natively on Windows, so having this option
is more confusing than useful.
2023-11-21 12:15:41 -05:00
Luke Street 261e1b8e07 Upgrade all dependencies 2023-11-21 11:57:02 -05:00
Luke Street a29e913b45 Add "Incomplete" filter to object tree
Allows filtering out objects marked as "complete".
2023-11-21 11:50:11 -05:00
Luke Street 49257dc73c Better logic to reload previous file on app start
Before, if "Rebuild on changes" was disabled, the last file
wouldn't be properly loaded when starting.
2023-11-21 11:49:26 -05:00
Luke Street dc9eec66b0 Configurable diff algorithms & new default algorithm
Uses the similar crate to support new diff algorithms:
- Patience (new default)
- Levenshtein (old default)
- Myers
- LCS (Longest Common Subsequence)

Options in "Diff Options" -> "Algorithm..."
2023-11-21 11:48:18 -05:00
Luke Street 7b58f9a269 Adjust "Diffable" to exclude missing target objects 2023-10-09 12:47:22 -04:00
Luke Street d9e7dacb6d Version 0.5.1 2023-10-07 14:49:01 -04:00
Luke Street 04b4fdcd21 Reload objects when changed externally
Uses file modification timestamp polling for project config and objects to avoid unneeded complexity from the filesystem notification watcher.

Allows disabling `build_base` as well for projects using an external build system.
2023-10-07 14:48:34 -04:00
Luke Street 803eaafee6 Hide hidden symbols by default; add "Diff Options" to menu 2023-10-07 13:27:12 -04:00
Luke Street e1dc84698f Restore context menu on highlightable fields
Fixes #30
2023-10-07 13:04:08 -04:00
Luke Street e68629c339 Update ppc750cl (subi{,s,c} mnemonics, capstone-style CR bits) 2023-10-06 01:22:26 -04:00
71 changed files with 10550 additions and 4684 deletions
+73 -18
View File
@@ -9,7 +9,7 @@ on:
workflow_dispatch:
env:
CARGO_BIN_NAME: objdiff
BUILD_PROFILE: release-lto
CARGO_TARGET_DIR: target
jobs:
@@ -24,15 +24,39 @@ jobs:
sudo apt-get update
sudo apt-get -y install libgtk-3-dev
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
components: clippy
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.4
- name: Cargo check
run: cargo check --all-features
env:
RUSTC_WRAPPER: sccache
SCCACHE_GHA_ENABLED: "true"
run: cargo check
- name: Cargo clippy
run: cargo clippy --all-features
env:
RUSTC_WRAPPER: sccache
SCCACHE_GHA_ENABLED: "true"
run: cargo clippy
fmt:
name: Format
runs-on: ubuntu-latest
env:
RUSTFLAGS: -D warnings
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Rust toolchain
# We use nightly options in rustfmt.toml
uses: dtolnay/rust-toolchain@nightly
with:
components: rustfmt
- name: Cargo fmt
run: cargo fmt --all --check
deny:
name: Deny
@@ -45,13 +69,14 @@ jobs:
# Prevent new advisories from failing CI
continue-on-error: ${{ matrix.checks == 'advisories' }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v1
with:
command: check ${{ matrix.checks }}
test:
name: Test
if: 'false' # No tests yet
strategy:
matrix:
platform: [ ubuntu-latest, windows-latest, macos-latest ]
@@ -64,11 +89,16 @@ jobs:
sudo apt-get update
sudo apt-get -y install libgtk-3-dev
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.4
- name: Cargo test
run: cargo test --release --all-features
env:
RUSTC_WRAPPER: sccache
SCCACHE_GHA_ENABLED: "true"
run: cargo test --release
build:
name: Build
@@ -79,15 +109,19 @@ jobs:
target: x86_64-unknown-linux-gnu
name: linux-x86_64
packages: libgtk-3-dev
features: default
- platform: windows-latest
target: x86_64-pc-windows-msvc
name: windows-x86_64
features: default
- platform: macos-latest
target: x86_64-apple-darwin
name: macos-x86_64
features: default
- platform: macos-latest
target: aarch64-apple-darwin
name: macos-arm64
features: default
fail-fast: false
runs-on: ${{ matrix.platform }}
steps:
@@ -97,22 +131,29 @@ jobs:
sudo apt-get update
sudo apt-get -y install ${{ matrix.packages }}
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.4
- name: Cargo build
run: cargo build --release --all-features --target ${{ matrix.target }} --bin ${{ env.CARGO_BIN_NAME }}
env:
RUSTC_WRAPPER: sccache
SCCACHE_GHA_ENABLED: "true"
run: >
cargo build --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
--bin objdiff-cli --bin objdiff --features ${{ matrix.features }}
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.name }}
path: |
${{ env.CARGO_TARGET_DIR }}/release/${{ env.CARGO_BIN_NAME }}
${{ env.CARGO_TARGET_DIR }}/release/${{ env.CARGO_BIN_NAME }}.exe
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/release/${{ env.CARGO_BIN_NAME }}
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/release/${{ env.CARGO_BIN_NAME }}.exe
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/objdiff-cli
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/objdiff-cli.exe
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/objdiff
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/objdiff.exe
if-no-files-found: error
release:
@@ -120,17 +161,31 @@ jobs:
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [ build ]
permissions:
contents: write
steps:
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Rename artifacts
working-directory: artifacts
run: |
set -euo pipefail
mkdir ../out
for i in */*/release/$CARGO_BIN_NAME*; do
mv "$i" "../out/$(sed -E "s/([^/]+)\/[^/]+\/release\/($CARGO_BIN_NAME)/\2-\1/" <<< "$i")"
for dir in */; do
for file in "$dir"*; do
base=$(basename "$file")
name="${base%.*}"
ext="${base##*.}"
if [ "$ext" = "$base" ]; then
ext=""
else
ext=".$ext"
fi
dst="../out/${name}-${dir%/}${ext}"
mv "$file" "$dst"
done
done
ls -R ../out
- name: Release
Generated
+2047 -1426
View File
File diff suppressed because it is too large Load Diff
+9 -84
View File
@@ -1,87 +1,12 @@
[package]
name = "objdiff"
version = "0.5.0"
edition = "2021"
rust-version = "1.70"
authors = ["Luke Street <luke@street.dev>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/encounter/objdiff"
readme = "README.md"
description = """
A local diffing tool for decompilation projects.
"""
publish = false
build = "build.rs"
[workspace]
members = [
"objdiff-cli",
"objdiff-core",
"objdiff-gui",
]
resolver = "2"
[profile.release]
[profile.release-lto]
inherits = "release"
lto = "thin"
strip = "debuginfo"
[features]
default = []
wgpu = ["eframe/wgpu"]
[dependencies]
anyhow = "1.0.75"
byteorder = "1.5.0"
bytes = "1.5.0"
cfg-if = "1.0.0"
const_format = "0.2.31"
cwdemangle = "0.1.6"
dirs = "5.0.1"
eframe = { version = "0.23.0", features = ["persistence"] }
egui = "0.23.0"
egui_extras = "0.23.0"
flagset = "0.4.4"
globset = { version = "0.4.13", features = ["serde1"] }
log = "0.4.20"
memmap2 = "0.9.0"
notify = "6.1.1"
object = { version = "0.32.1", features = ["read_core", "std", "elf"], default-features = false }
png = "0.17.10"
ppc750cl = { git = "https://github.com/terorie/ppc750cl", rev = "9ae36eef34aa6d74e00972c7671f547a2acfd0aa" }
rabbitizer = "1.7.10"
rfd = { version = "0.12.0" } #, default-features = false, features = ['xdg-portal']
ron = "0.8.1"
semver = "1.0.19"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0.107"
serde_yaml = "0.9.25"
tempfile = "3.8.0"
thiserror = "1.0.49"
time = { version = "0.3.29", features = ["formatting", "local-offset"] }
toml = "0.8.2"
twox-hash = "1.6.3"
# For Linux static binaries, use rustls
[target.'cfg(target_os = "linux")'.dependencies]
reqwest = { version = "0.11.22", default-features = false, features = ["blocking", "json", "rustls"] }
self_update = { version = "0.38.0", default-features = false, features = ["rustls"] }
# For all other platforms, use native TLS
[target.'cfg(not(target_os = "linux"))'.dependencies]
reqwest = "0.11.22"
self_update = "0.38.0"
[target.'cfg(windows)'.dependencies]
path-slash = "0.2.1"
winapi = "0.3.9"
[target.'cfg(windows)'.build-dependencies]
winres = "0.1.12"
[target.'cfg(unix)'.dependencies]
exec = "0.3.1"
# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tracing-subscriber = "0.3"
# web:
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.7"
tracing-wasm = "0.2"
[build-dependencies]
anyhow = "1.0.75"
vergen = { version = "8.2.5", features = ["build", "cargo", "git", "gitcl"] }
+31 -4
View File
@@ -3,11 +3,21 @@
[Build Status]: https://github.com/encounter/objdiff/actions/workflows/build.yaml/badge.svg
[actions]: https://github.com/encounter/objdiff/actions
A local diffing tool for decompilation projects.
A local diffing tool for decompilation projects. Inspired by [decomp.me](https://decomp.me) and [asm-differ](https://github.com/simonlindholm/asm-differ).
Features:
- Compare entire object files: functions and data.
- Built-in symbol demangling for C++. (CodeWarrior, Itanium & MSVC)
- Automatic rebuild on source file changes.
- Project integration via [configuration file](#configuration).
- Search and filter all of a project's objects and quickly switch.
- Click to highlight all instances of values and registers.
Supports:
- PowerPC 750CL (GameCube & Wii)
- MIPS (Nintendo 64)
- PowerPC 750CL (GameCube, Wii)
- MIPS (N64, PS1, PS2, PSP)
- x86 (COFF only at the moment)
- ARM (GBA, DS, 3DS)
See [Usage](#usage) for more information.
@@ -50,6 +60,10 @@ file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it
// objdiff.json
{
"custom_make": "ninja",
"custom_args": [
"-d",
"keeprsp"
],
// Only required if objects use "path" instead of "target_path" and "base_path".
"target_dir": "build/asm",
@@ -83,7 +97,10 @@ file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it
```
`custom_make` _(optional)_: By default, objdiff will use `make` to build the project.
If the project uses a different build system (e.g. `ninja`), specify it here.
If the project uses a different build system (e.g. `ninja`), specify it here.
The build command will be `[custom_make] [custom_args] path/to/object.o`.
`custom_args` _(optional)_: Additional arguments to pass to the build command prior to the object path.
`target_dir` _(optional)_: Relative from the root of the project, this where the "target" or "expected" objects are located.
These are the **intended result** of the match.
@@ -118,7 +135,17 @@ If not specified, objdiff will use the default patterns listed above.
> `reverse_fn_order` _(optional)_: Displays function symbols in reversed order.
Used to support MWCC's `-inline deferred` option, which reverses the order of functions in the object file.
## Building
Install Rust via [rustup](https://rustup.rs).
```shell
$ git clone https://github.com/encounter/objdiff.git
$ cd objdiff
$ cargo run --release
# or, for wgpu backend (recommended on macOS)
$ cargo run --release --features wgpu
```
## License
+3 -8
View File
@@ -47,13 +47,7 @@ yanked = "warn"
notice = "warn"
# A list of advisory IDs to ignore. Note that ignored advisories will still
# output a note when they are encountered.
ignore = [
"RUSTSEC-2023-0022",
"RUSTSEC-2023-0023",
"RUSTSEC-2023-0024",
"RUSTSEC-2023-0034",
"RUSTSEC-2023-0044",
]
ignore = []
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
# lower than the range specified will be ignored. Note that ignored advisories
# will still output a note when they are encountered.
@@ -76,6 +70,7 @@ unlicensed = "deny"
allow = [
"MIT",
"Apache-2.0",
"Apache-2.0 WITH LLVM-exception",
"ISC",
"BSD-2-Clause",
"BSD-3-Clause",
@@ -159,7 +154,7 @@ registries = [
# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
[bans]
# Lint level for when multiple versions of the same crate are detected
multiple-versions = "warn"
multiple-versions = "allow"
# Lint level for when a crate version requirement is `*`
wildcards = "allow"
# The graph highlighting used when creating dotgraphs for crates
+29
View File
@@ -0,0 +1,29 @@
[package]
name = "objdiff-cli"
version = "2.0.0-alpha.5"
edition = "2021"
rust-version = "1.70"
authors = ["Luke Street <luke@street.dev>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/encounter/objdiff"
readme = "../README.md"
description = """
A local diffing tool for decompilation projects.
"""
publish = false
build = "build.rs"
[dependencies]
anyhow = "1.0.82"
argp = "0.3.0"
crossterm = "0.27.0"
enable-ansi-support = "0.2.1"
objdiff-core = { path = "../objdiff-core", features = ["all"] }
ratatui = "0.26.2"
rayon = "1.10.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0.116"
supports-color = "3.0.0"
time = { version = "0.3.36", features = ["formatting", "local-offset"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
+9
View File
@@ -0,0 +1,9 @@
fn main() {
let output = std::process::Command::new("git")
.args(["rev-parse", "HEAD"])
.output()
.expect("Failed to execute git");
let rev = String::from_utf8(output.stdout).expect("Failed to parse git output");
println!("cargo:rustc-env=GIT_COMMIT_SHA={rev}");
println!("cargo:rustc-rerun-if-changed=.git/HEAD");
}
+64
View File
@@ -0,0 +1,64 @@
// Originally from https://gist.github.com/suluke/e0c672492126be0a4f3b4f0e1115d77c
//! Extend `argp` to be better integrated with the `cargo` ecosystem
//!
//! For now, this only adds a --version/-V option which causes early-exit.
use std::ffi::OsStr;
use argp::{parser::ParseGlobalOptions, EarlyExit, FromArgs, TopLevelCommand};
struct ArgsOrVersion<T>(T)
where T: FromArgs;
impl<T> TopLevelCommand for ArgsOrVersion<T> where T: FromArgs {}
impl<T> FromArgs for ArgsOrVersion<T>
where T: FromArgs
{
fn _from_args(
command_name: &[&str],
args: &[&OsStr],
parent: Option<&mut dyn ParseGlobalOptions>,
) -> Result<Self, EarlyExit> {
/// Also use argp for catching `--version`-only invocations
#[derive(FromArgs)]
struct Version {
/// Print version information and exit.
#[argp(switch, short = 'V')]
pub version: bool,
}
match Version::from_args(command_name, args) {
Ok(v) => {
if v.version {
println!(
"{} {} {}",
command_name.first().unwrap_or(&""),
env!("CARGO_PKG_VERSION"),
env!("GIT_COMMIT_SHA"),
);
std::process::exit(0);
} else {
// Pass through empty arguments
T::_from_args(command_name, args, parent).map(Self)
}
}
Err(exit) => match exit {
EarlyExit::Help(_help) => {
// TODO: Chain help info from Version
// For now, we just put the switch on T as well
T::from_args(command_name, &["--help"]).map(Self)
}
EarlyExit::Err(_) => T::_from_args(command_name, args, parent).map(Self),
},
}
}
}
/// Create a `FromArgs` type from the current process’s `env::args`.
///
/// This function will exit early from the current process if argument parsing was unsuccessful or if information like `--help` was requested.
/// Error messages will be printed to stderr, and `--help` output to stdout.
pub fn from_env<T>() -> T
where T: TopLevelCommand {
argp::parse_args_or_exit::<ArgsOrVersion<T>>(argp::DEFAULT).0
}
File diff suppressed because it is too large Load Diff
+2
View File
@@ -0,0 +1,2 @@
pub mod diff;
pub mod report;
File diff suppressed because it is too large Load Diff
+141
View File
@@ -0,0 +1,141 @@
mod argp_version;
mod cmd;
mod util;
use std::{env, ffi::OsStr, fmt::Display, path::PathBuf, str::FromStr};
use anyhow::{Error, Result};
use argp::{FromArgValue, FromArgs};
use enable_ansi_support::enable_ansi_support;
use supports_color::Stream;
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
enum LogLevel {
Error,
Warn,
Info,
Debug,
Trace,
}
impl FromStr for LogLevel {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"error" => Self::Error,
"warn" => Self::Warn,
"info" => Self::Info,
"debug" => Self::Debug,
"trace" => Self::Trace,
_ => return Err(()),
})
}
}
impl Display for LogLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
LogLevel::Error => "error",
LogLevel::Warn => "warn",
LogLevel::Info => "info",
LogLevel::Debug => "debug",
LogLevel::Trace => "trace",
})
}
}
impl FromArgValue for LogLevel {
fn from_arg_value(value: &OsStr) -> Result<Self, String> {
String::from_arg_value(value)
.and_then(|s| Self::from_str(&s).map_err(|_| "Invalid log level".to_string()))
}
}
#[derive(FromArgs, PartialEq, Debug)]
/// Yet another GameCube/Wii decompilation toolkit.
struct TopLevel {
#[argp(subcommand)]
command: SubCommand,
#[argp(option, short = 'C')]
/// Change working directory.
chdir: Option<PathBuf>,
#[argp(option, short = 'L')]
/// Minimum logging level. (Default: info)
/// Possible values: error, warn, info, debug, trace
log_level: Option<LogLevel>,
/// Print version information and exit.
#[argp(switch, short = 'V')]
version: bool,
/// Disable color output. (env: NO_COLOR)
#[argp(switch)]
no_color: bool,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argp(subcommand)]
enum SubCommand {
Diff(cmd::diff::Args),
Report(cmd::report::Args),
}
// Duplicated from supports-color so we can check early.
fn env_no_color() -> bool {
match env::var("NO_COLOR").as_deref() {
Ok("") | Ok("0") | Err(_) => false,
Ok(_) => true,
}
}
fn main() {
let args: TopLevel = argp_version::from_env();
let use_colors = if args.no_color || env_no_color() {
false
} else {
// Try to enable ANSI support on Windows.
let _ = enable_ansi_support();
// Disable isatty check for supports-color. (e.g. when used with ninja)
env::set_var("IGNORE_IS_TERMINAL", "1");
supports_color::on(Stream::Stdout).is_some_and(|c| c.has_basic)
};
let format =
tracing_subscriber::fmt::format().with_ansi(use_colors).with_target(false).without_time();
let builder = tracing_subscriber::fmt().event_format(format).with_writer(std::io::stderr);
if let Some(level) = args.log_level {
builder
.with_max_level(match level {
LogLevel::Error => LevelFilter::ERROR,
LogLevel::Warn => LevelFilter::WARN,
LogLevel::Info => LevelFilter::INFO,
LogLevel::Debug => LevelFilter::DEBUG,
LogLevel::Trace => LevelFilter::TRACE,
})
.init();
} else {
builder
.with_env_filter(
EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy(),
)
.init();
}
let mut result = Ok(());
if let Some(dir) = &args.chdir {
result = env::set_current_dir(dir).map_err(|e| {
Error::new(e)
.context(format!("Failed to change working directory to '{}'", dir.display()))
});
}
result = result.and_then(|_| match args.command {
SubCommand::Diff(c_args) => cmd::diff::run(c_args),
SubCommand::Report(c_args) => cmd::report::run(c_args),
});
if let Err(e) = result {
eprintln!("Failed: {e:?}");
std::process::exit(1);
}
}
+1
View File
@@ -0,0 +1 @@
pub mod term;
+16
View File
@@ -0,0 +1,16 @@
use std::{io::stdout, panic};
use crossterm::{
cursor::Show,
event::DisableMouseCapture,
terminal::{disable_raw_mode, LeaveAlternateScreen},
};
pub fn crossterm_panic_handler() {
let original_hook = panic::take_hook();
panic::set_hook(Box::new(move |panic_info| {
let _ = crossterm::execute!(stdout(), LeaveAlternateScreen, DisableMouseCapture, Show);
let _ = disable_raw_mode();
original_hook(panic_info);
}));
}
+60
View File
@@ -0,0 +1,60 @@
[package]
name = "objdiff-core"
version = "2.0.0-alpha.5"
edition = "2021"
rust-version = "1.70"
authors = ["Luke Street <luke@street.dev>"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/encounter/objdiff"
readme = "../README.md"
description = """
A local diffing tool for decompilation projects.
"""
[features]
all = ["config", "dwarf", "mips", "ppc", "x86", "arm"]
any-arch = [] # Implicit, used to check if any arch is enabled
config = ["globset", "semver", "serde_json", "serde_yaml"]
dwarf = ["gimli"]
mips = ["any-arch", "rabbitizer"]
ppc = ["any-arch", "cwdemangle", "ppc750cl"]
x86 = ["any-arch", "cpp_demangle", "iced-x86", "msvc-demangler"]
arm = ["any-arch", "cpp_demangle", "unarm", "arm-attr"]
[dependencies]
anyhow = "1.0.82"
byteorder = "1.5.0"
filetime = "0.2.23"
flagset = "0.4.5"
log = "0.4.21"
memmap2 = "0.9.4"
num-traits = "0.2.18"
object = { version = "0.35.0", features = ["read_core", "std", "elf", "pe"], default-features = false }
serde = { version = "1", features = ["derive"] }
similar = { version = "2.5.0", default-features = false }
strum = { version = "0.26.2", features = ["derive"] }
# config
globset = { version = "0.4.14", features = ["serde1"], optional = true }
semver = { version = "1.0.22", optional = true }
serde_json = { version = "1.0.116", optional = true }
serde_yaml = { version = "0.9.34", optional = true }
# dwarf
gimli = { version = "0.29.0", default-features = false, features = ["read-all"], optional = true }
# ppc
cwdemangle = { version = "1.0.0", optional = true }
ppc750cl = { git = "https://github.com/encounter/ppc750cl", rev = "6cbd7d888c7082c2c860f66cbb9848d633f753ed", optional = true }
# mips
rabbitizer = { version = "1.11.0", optional = true }
# x86
cpp_demangle = { version = "0.4.3", optional = true }
iced-x86 = { version = "1.21.0", default-features = false, features = ["std", "decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums"], optional = true }
msvc-demangler = { version = "0.10.0", optional = true }
# arm
unarm = { version = "1.3.0", optional = true }
arm-attr = { version = "0.1.1", optional = true }
+414
View File
@@ -0,0 +1,414 @@
use std::{
borrow::Cow,
collections::{BTreeMap, HashMap},
};
use anyhow::{bail, Result};
use arm_attr::{enums::CpuArch, tag::Tag, BuildAttrs};
use object::{
elf::{self, SHT_ARM_ATTRIBUTES},
Endian, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, SectionIndex,
SectionKind, Symbol, SymbolKind,
};
use unarm::{
args::{Argument, OffsetImm, OffsetReg, Register},
parse::{ArmVersion, ParseMode, Parser},
ParsedIns,
};
use crate::{
arch::{ObjArch, ProcessCodeResult},
diff::{ArmArchVersion, DiffObjConfig},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
};
pub struct ObjArchArm {
/// Maps section index, to list of disasm modes (arm, thumb or data) sorted by address
disasm_modes: HashMap<SectionIndex, Vec<DisasmMode>>,
detected_version: Option<ArmVersion>,
endianness: object::Endianness,
}
impl ObjArchArm {
pub fn new(file: &File) -> Result<Self> {
let endianness = file.endianness();
match file {
File::Elf32(_) => {
let disasm_modes = Self::elf_get_mapping_symbols(file);
let detected_version = Self::elf_detect_arm_version(file)?;
Ok(Self { disasm_modes, detected_version, endianness })
}
_ => bail!("Unsupported file format {:?}", file.format()),
}
}
fn elf_detect_arm_version(file: &File) -> Result<Option<ArmVersion>> {
// Check ARM attributes
if let Some(arm_attrs) = file.sections().find(|s| {
s.kind() == SectionKind::Elf(SHT_ARM_ATTRIBUTES) && s.name() == Ok(".ARM.attributes")
}) {
let attr_data = arm_attrs.uncompressed_data()?;
let build_attrs = BuildAttrs::new(&attr_data, match file.endianness() {
object::Endianness::Little => arm_attr::Endian::Little,
object::Endianness::Big => arm_attr::Endian::Big,
})?;
for subsection in build_attrs.subsections() {
let subsection = subsection?;
if !subsection.is_aeabi() {
continue;
}
// Only checking first CpuArch tag. Others may exist, but that's very unlikely.
let cpu_arch = subsection.into_public_tag_iter()?.find_map(|(_, tag)| {
if let Tag::CpuArch(cpu_arch) = tag {
Some(cpu_arch)
} else {
None
}
});
match cpu_arch {
Some(CpuArch::V4T) => return Ok(Some(ArmVersion::V4T)),
Some(CpuArch::V5TE) => return Ok(Some(ArmVersion::V5Te)),
Some(CpuArch::V6K) => return Ok(Some(ArmVersion::V6K)),
Some(arch) => bail!("ARM arch {} not supported", arch),
None => {}
};
}
}
Ok(None)
}
fn elf_get_mapping_symbols(file: &File) -> HashMap<SectionIndex, Vec<DisasmMode>> {
file.sections()
.filter(|s| s.kind() == SectionKind::Text)
.map(|s| {
let index = s.index();
let mut mapping_symbols: Vec<_> = file
.symbols()
.filter(|s| s.section_index().map(|i| i == index).unwrap_or(false))
.filter_map(|s| DisasmMode::from_symbol(&s))
.collect();
mapping_symbols.sort_unstable_by_key(|x| x.address);
(s.index(), mapping_symbols)
})
.collect()
}
}
impl ObjArch for ObjArchArm {
fn symbol_address(&self, symbol: &Symbol) -> u64 {
let address = symbol.address();
if symbol.kind() == SymbolKind::Text {
address & !1
} else {
address
}
}
fn process_code(
&self,
address: u64,
code: &[u8],
section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u64>,
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 =
match mapping_symbols.binary_search_by_key(&start_addr, |x| x.address) {
Ok(idx) => idx,
Err(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 mut parser = Parser::new(version, first_mapping, start_addr, endian, code);
while let Some((address, op, 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 =
ins.args.iter().rposition(|a| matches!(a, Argument::BranchDest(_)));
}
// Data
RelocationFlags::Elf { r_type: elf::R_ARM_ABS32 } => {
reloc_arg = 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(&ins, config, reloc_arg, address)?
};
ops.push(op.id());
insts.push(ObjIns {
address: address as u64,
size: (parser.address - address) as u8,
op: op.id(),
mnemonic: ins.mnemonic.to_string(),
args,
reloc,
branch_dest,
line,
formatted: ins.to_string(),
orig: None,
});
}
Ok(ProcessCodeResult { ops, insts })
}
fn implcit_addend(
&self,
section: &ObjSection,
address: u64,
reloc: &Relocation,
) -> anyhow::Result<i64> {
let address = address as usize;
Ok(match reloc.flags() {
// ARM calls
RelocationFlags::Elf { r_type: elf::R_ARM_PC24 }
| RelocationFlags::Elf { r_type: elf::R_ARM_XPC25 }
| RelocationFlags::Elf { r_type: 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
RelocationFlags::Elf { r_type: elf::R_ARM_THM_PC22 }
| RelocationFlags::Elf { r_type: 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
RelocationFlags::Elf { r_type: 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:?}"),
} as i64)
}
fn demangle(&self, name: &str) -> Option<String> {
cpp_demangle::Symbol::new(name)
.ok()
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
}
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
Cow::Owned(format!("<{flags:?}>"))
}
}
#[derive(Clone, Copy, Debug)]
struct DisasmMode {
address: u32,
mapping: ParseMode,
}
impl DisasmMode {
fn from_symbol<'a>(sym: &Symbol<'a, '_, &'a [u8]>) -> Option<Self> {
if let Ok(name) = sym.name() {
ParseMode::from_mapping_symbol(name)
.map(|mapping| DisasmMode { address: sym.address() as u32, mapping })
} else {
None
}
}
}
fn push_args(
parsed_ins: &ParsedIns,
config: &DiffObjConfig,
reloc_arg: Option<usize>,
cur_addr: u32,
) -> Result<(Vec<ObjInsArg>, Option<u64>)> {
let mut args = vec![];
let mut branch_dest = None;
let mut writeback = false;
let mut deref = false;
for (i, arg) in parsed_ins.args_iter().enumerate() {
// Emit punctuation before separator
if deref {
match arg {
Argument::OffsetImm(OffsetImm { post_indexed: true, value: _ })
| Argument::OffsetReg(OffsetReg { add: _, post_indexed: true, reg: _ })
| Argument::CoOption(_) => {
deref = false;
args.push(ObjInsArg::PlainText("]".into()));
if writeback {
writeback = false;
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
}
}
_ => {}
}
}
if i > 0 {
args.push(ObjInsArg::PlainText(config.separator().into()));
}
if reloc_arg == Some(i) {
args.push(ObjInsArg::Reloc);
} else {
match arg {
Argument::None => {}
Argument::Reg(reg) => {
if reg.deref {
deref = true;
args.push(ObjInsArg::PlainText("[".into()));
}
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(reg.reg.to_string().into())));
if reg.writeback {
if reg.deref {
writeback = true;
} else {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
}
}
}
Argument::RegList(reg_list) => {
args.push(ObjInsArg::PlainText("{".into()));
let mut first = true;
for i in 0..16 {
if (reg_list.regs & (1 << i)) != 0 {
if !first {
args.push(ObjInsArg::PlainText(config.separator().into()));
}
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
Register::parse(i).to_string().into(),
)));
first = false;
}
}
args.push(ObjInsArg::PlainText("}".into()));
if reg_list.user_mode {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("^".to_string().into())));
}
}
Argument::UImm(value) | Argument::CoOpcode(value) | Argument::SatImm(value) => {
args.push(ObjInsArg::PlainText("#".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64)));
}
Argument::SImm(value)
| Argument::OffsetImm(OffsetImm { post_indexed: _, value }) => {
args.push(ObjInsArg::PlainText("#".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(*value as i64)));
}
Argument::BranchDest(value) => {
let dest = cur_addr.wrapping_add_signed(*value) as u64;
args.push(ObjInsArg::BranchDest(dest));
branch_dest = Some(dest);
}
Argument::CoOption(value) => {
args.push(ObjInsArg::PlainText("{".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64)));
args.push(ObjInsArg::PlainText("}".into()));
}
Argument::CoprocNum(value) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(format!("p{}", value).into())));
}
Argument::ShiftImm(shift) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into())));
args.push(ObjInsArg::PlainText(" #".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(shift.imm as u64)));
}
Argument::ShiftReg(shift) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into())));
args.push(ObjInsArg::PlainText(" ".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.reg.to_string().into())));
}
Argument::OffsetReg(offset) => {
if !offset.add {
args.push(ObjInsArg::PlainText("-".into()));
}
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
offset.reg.to_string().into(),
)));
}
Argument::CpsrMode(mode) => {
args.push(ObjInsArg::PlainText("#".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(mode.mode as u64)));
if mode.writeback {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
}
}
Argument::CoReg(_)
| Argument::StatusReg(_)
| Argument::StatusMask(_)
| Argument::Shift(_)
| Argument::CpsrFlags(_)
| Argument::Endian(_) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(arg.to_string().into())))
}
}
}
}
if deref {
args.push(ObjInsArg::PlainText("]".into()));
if writeback {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
}
}
Ok((args, branch_dest))
}
+255
View File
@@ -0,0 +1,255 @@
use std::{borrow::Cow, collections::BTreeMap, sync::Mutex};
use anyhow::{anyhow, bail, Result};
use object::{elf, Endian, Endianness, File, FileFlags, Object, Relocation, RelocationFlags};
use rabbitizer::{config, Abi, InstrCategory, Instruction, OperandType};
use crate::{
arch::{ObjArch, ProcessCodeResult},
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
};
static RABBITIZER_MUTEX: Mutex<()> = Mutex::new(());
fn configure_rabbitizer(abi: Abi) {
unsafe {
config::RabbitizerConfig_Cfg.reg_names.fpr_abi_names = abi;
}
}
pub struct ObjArchMips {
pub endianness: Endianness,
pub abi: Abi,
pub instr_category: InstrCategory,
}
const EF_MIPS_ABI: u32 = 0x0000F000;
const EF_MIPS_MACH: u32 = 0x00FF0000;
const EF_MIPS_MACH_ALLEGREX: u32 = 0x00840000;
const EF_MIPS_MACH_5900: u32 = 0x00920000;
impl ObjArchMips {
pub fn new(object: &File) -> Result<Self> {
let mut abi = Abi::NUMERIC;
let mut instr_category = InstrCategory::CPU;
match object.flags() {
FileFlags::None => {}
FileFlags::Elf { e_flags, .. } => {
abi = match e_flags & EF_MIPS_ABI {
elf::EF_MIPS_ABI_O32 | elf::EF_MIPS_ABI_O64 => Abi::O32,
elf::EF_MIPS_ABI_EABI32 | elf::EF_MIPS_ABI_EABI64 => Abi::N32,
_ => {
if e_flags & elf::EF_MIPS_ABI2 != 0 {
Abi::N32
} else {
Abi::NUMERIC
}
}
};
instr_category = match e_flags & EF_MIPS_MACH {
EF_MIPS_MACH_ALLEGREX => InstrCategory::R4000ALLEGREX,
EF_MIPS_MACH_5900 => InstrCategory::R5900,
_ => InstrCategory::CPU,
};
}
_ => bail!("Unsupported MIPS file flags"),
}
Ok(Self { endianness: object.endianness(), abi, instr_category })
}
}
impl ObjArch for ObjArchMips {
fn process_code(
&self,
address: u64,
code: &[u8],
_section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u64>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
let _guard = RABBITIZER_MUTEX.lock().map_err(|e| anyhow!("Failed to lock mutex: {e}"))?;
configure_rabbitizer(match config.mips_abi {
MipsAbi::Auto => self.abi,
MipsAbi::O32 => Abi::O32,
MipsAbi::N32 => Abi::N32,
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().to_string();
let is_branch = instruction.is_branch();
let branch_offset = instruction.branch_offset();
let 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(branch_dest) = branch_dest {
args.push(ObjInsArg::BranchDest(branch_dest));
} else if let Some(reloc) = reloc {
if matches!(&reloc.target_section, Some(s) if s == ".text")
&& reloc.target.address > start_address
&& reloc.target.address < end_address
{
args.push(ObjInsArg::BranchDest(reloc.target.address));
} else {
push_reloc(&mut args, reloc)?;
}
} 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()));
}
_ => {
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,
args,
reloc: reloc.cloned(),
branch_dest,
line,
formatted,
orig: None,
});
cur_addr += 4;
}
Ok(ProcessCodeResult { ops, insts })
}
fn implcit_addend(
&self,
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_HI16 } => {
((addend & 0x0000FFFF) << 16) as i32 as i64
}
RelocationFlags::Elf {
r_type:
elf::R_MIPS_LO16 | elf::R_MIPS_GOT16 | elf::R_MIPS_CALL16 | elf::R_MIPS_GPREL16,
} => (addend & 0x0000FFFF) as i16 as i64,
RelocationFlags::Elf { r_type: elf::R_MIPS_26 } => ((addend & 0x03FFFFFF) << 2) as i64,
flags => bail!("Unsupported MIPS implicit relocation {flags:?}"),
})
}
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
match flags {
RelocationFlags::Elf { r_type } => match r_type {
elf::R_MIPS_HI16 => Cow::Borrowed("R_MIPS_HI16"),
elf::R_MIPS_LO16 => Cow::Borrowed("R_MIPS_LO16"),
elf::R_MIPS_GOT16 => Cow::Borrowed("R_MIPS_GOT16"),
elf::R_MIPS_CALL16 => Cow::Borrowed("R_MIPS_CALL16"),
elf::R_MIPS_GPREL16 => Cow::Borrowed("R_MIPS_GPREL16"),
elf::R_MIPS_32 => Cow::Borrowed("R_MIPS_32"),
elf::R_MIPS_26 => Cow::Borrowed("R_MIPS_26"),
_ => Cow::Owned(format!("<{flags:?}>")),
},
_ => Cow::Owned(format!("<{flags:?}>")),
}
}
}
fn push_reloc(args: &mut Vec<ObjInsArg>, reloc: &ObjReloc) -> Result<()> {
match reloc.flags {
RelocationFlags::Elf { r_type } => match r_type {
elf::R_MIPS_HI16 => {
args.push(ObjInsArg::PlainText("%hi(".into()));
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText(")".into()));
}
elf::R_MIPS_LO16 => {
args.push(ObjInsArg::PlainText("%lo(".into()));
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText(")".into()));
}
elf::R_MIPS_GOT16 => {
args.push(ObjInsArg::PlainText("%got(".into()));
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText(")".into()));
}
elf::R_MIPS_CALL16 => {
args.push(ObjInsArg::PlainText("%call16(".into()));
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText(")".into()));
}
elf::R_MIPS_GPREL16 => {
args.push(ObjInsArg::PlainText("%gp_rel(".into()));
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText(")".into()));
}
elf::R_MIPS_32 | elf::R_MIPS_26 => {
args.push(ObjInsArg::Reloc);
}
_ => bail!("Unsupported ELF MIPS relocation type {r_type}"),
},
flags => panic!("Unsupported MIPS relocation flags {flags:?}"),
}
Ok(())
}
+58
View File
@@ -0,0 +1,58 @@
use std::{borrow::Cow, collections::BTreeMap};
use anyhow::{bail, Result};
use object::{Architecture, Object, ObjectSymbol, Relocation, RelocationFlags, Symbol};
use crate::{
diff::DiffObjConfig,
obj::{ObjIns, ObjReloc, ObjSection},
};
#[cfg(feature = "arm")]
mod arm;
#[cfg(feature = "mips")]
pub mod mips;
#[cfg(feature = "ppc")]
pub mod ppc;
#[cfg(feature = "x86")]
pub mod x86;
pub trait ObjArch: Send + Sync {
fn process_code(
&self,
address: u64,
code: &[u8],
section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u64>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult>;
fn implcit_addend(&self, section: &ObjSection, address: u64, reloc: &Relocation)
-> Result<i64>;
fn demangle(&self, _name: &str) -> Option<String> { None }
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str>;
fn symbol_address(&self, symbol: &Symbol) -> u64 { symbol.address() }
}
pub struct ProcessCodeResult {
pub ops: Vec<u16>,
pub insts: Vec<ObjIns>,
}
pub fn new_arch(object: &object::File) -> Result<Box<dyn ObjArch>> {
Ok(match object.architecture() {
#[cfg(feature = "ppc")]
Architecture::PowerPc => Box::new(ppc::ObjArchPpc::new(object)?),
#[cfg(feature = "mips")]
Architecture::Mips => Box::new(mips::ObjArchMips::new(object)?),
#[cfg(feature = "x86")]
Architecture::I386 | Architecture::X86_64 => Box::new(x86::ObjArchX86::new(object)?),
#[cfg(feature = "arm")]
Architecture::Arm => Box::new(arm::ObjArchArm::new(object)?),
arch => bail!("Unsupported architecture: {arch:?}"),
})
}
+209
View File
@@ -0,0 +1,209 @@
use std::{borrow::Cow, collections::BTreeMap};
use anyhow::{bail, Result};
use object::{elf, File, Relocation, RelocationFlags};
use ppc750cl::{Argument, InsIter, GPR};
use crate::{
arch::{ObjArch, ProcessCodeResult},
diff::DiffObjConfig,
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
};
// 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 {}
impl ObjArchPpc {
pub fn new(_file: &File) -> Result<Self> { Ok(Self {}) }
}
impl ObjArch for ObjArchPpc {
fn process_code(
&self,
address: u64,
code: &[u8],
_section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u64>,
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: simplified.mnemonic.to_string(),
args,
reloc: reloc.cloned(),
op: ins.op as u16,
branch_dest,
line,
formatted,
orig: Some(orig),
});
}
Ok(ProcessCodeResult { ops, insts })
}
fn implcit_addend(
&self,
_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 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(())
}
+342
View File
@@ -0,0 +1,342 @@
use std::{borrow::Cow, collections::BTreeMap};
use anyhow::{anyhow, bail, ensure, Result};
use iced_x86::{
Decoder, DecoderOptions, DecoratorKind, Formatter, FormatterOutput, FormatterTextKind,
GasFormatter, Instruction, IntelFormatter, MasmFormatter, NasmFormatter, NumberKind, OpKind,
PrefixKind, Register,
};
use object::{pe, Endian, Endianness, File, Object, Relocation, RelocationFlags};
use crate::{
arch::{ObjArch, ProcessCodeResult},
diff::{DiffObjConfig, X86Formatter},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
};
pub struct ObjArchX86 {
bits: u32,
endianness: Endianness,
}
impl ObjArchX86 {
pub fn new(object: &File) -> Result<Self> {
Ok(Self { bits: if object.is_64() { 64 } else { 32 }, endianness: object.endianness() })
}
}
impl ObjArch for ObjArchX86 {
fn process_code(
&self,
address: u64,
code: &[u8],
_section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u64>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
let mut result = ProcessCodeResult { ops: Vec::new(), insts: Vec::new() };
let mut decoder = Decoder::with_ip(self.bits, code, address, DecoderOptions::NONE);
let mut formatter: Box<dyn Formatter> = match config.x86_formatter {
X86Formatter::Intel => Box::new(IntelFormatter::new()),
X86Formatter::Gas => Box::new(GasFormatter::new()),
X86Formatter::Nasm => Box::new(NasmFormatter::new()),
X86Formatter::Masm => Box::new(MasmFormatter::new()),
};
formatter.options_mut().set_space_after_operand_separator(config.space_between_args);
let mut output = InstructionFormatterOutput {
formatted: String::new(),
ins: ObjIns {
address: 0,
size: 0,
op: 0,
mnemonic: String::new(),
args: vec![],
reloc: None,
branch_dest: None,
line: None,
formatted: String::new(),
orig: None,
},
error: None,
ins_operands: vec![],
};
let mut instruction = Instruction::default();
while decoder.can_decode() {
decoder.decode_out(&mut instruction);
let address = instruction.ip();
let op = instruction.mnemonic() as u16;
let reloc = relocations
.iter()
.find(|r| r.address >= address && r.address < address + instruction.len() as u64);
let line = line_info.range(..=address).last().map(|(_, &b)| b);
output.ins = ObjIns {
address,
size: instruction.len() as u8,
op,
mnemonic: String::new(),
args: vec![],
reloc: reloc.cloned(),
branch_dest: None,
line,
formatted: String::new(),
orig: None,
};
// Run the formatter, which will populate output.ins
formatter.format(&instruction, &mut output);
if let Some(error) = output.error.take() {
return Err(error);
}
ensure!(output.ins_operands.len() == output.ins.args.len());
output.ins.formatted.clone_from(&output.formatted);
// Make sure we've put the relocation somewhere in the instruction
if reloc.is_some() && !output.ins.args.iter().any(|a| matches!(a, ObjInsArg::Reloc)) {
let mut found = replace_arg(
OpKind::Memory,
ObjInsArg::Reloc,
&mut output.ins.args,
&instruction,
&output.ins_operands,
)?;
if !found {
found = replace_arg(
OpKind::Immediate32,
ObjInsArg::Reloc,
&mut output.ins.args,
&instruction,
&output.ins_operands,
)?;
}
ensure!(found, "x86: Failed to find operand for Absolute relocation");
}
if reloc.is_some() && !output.ins.args.iter().any(|a| matches!(a, ObjInsArg::Reloc)) {
bail!("Failed to find relocation in instruction");
}
result.ops.push(op);
result.insts.push(output.ins.clone());
// Clear for next iteration
output.formatted.clear();
output.ins_operands.clear();
}
Ok(result)
}
fn implcit_addend(
&self,
section: &ObjSection,
address: u64,
reloc: &Relocation,
) -> Result<i64> {
match reloc.flags() {
RelocationFlags::Coff { typ: pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32 } => {
let data = section.data[address as usize..address as usize + 4].try_into()?;
Ok(self.endianness.read_i32_bytes(data) as i64)
}
flags => bail!("Unsupported x86 implicit relocation {flags:?}"),
}
}
fn demangle(&self, name: &str) -> Option<String> {
if name.starts_with('?') {
msvc_demangler::demangle(name, msvc_demangler::DemangleFlags::llvm()).ok()
} else {
cpp_demangle::Symbol::new(name)
.ok()
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
}
}
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
match flags {
RelocationFlags::Coff { typ } => match typ {
pe::IMAGE_REL_I386_DIR32 => Cow::Borrowed("IMAGE_REL_I386_DIR32"),
pe::IMAGE_REL_I386_REL32 => Cow::Borrowed("IMAGE_REL_I386_REL32"),
_ => Cow::Owned(format!("<{flags:?}>")),
},
_ => Cow::Owned(format!("<{flags:?}>")),
}
}
}
fn replace_arg(
from: OpKind,
to: ObjInsArg,
args: &mut [ObjInsArg],
instruction: &Instruction,
ins_operands: &[Option<u32>],
) -> Result<bool> {
let mut replace = None;
for i in 0..instruction.op_count() {
let op_kind = instruction.op_kind(i);
if op_kind == from {
replace = Some(i);
break;
}
}
if let Some(i) = replace {
for (j, arg) in args.iter_mut().enumerate() {
if ins_operands[j] == Some(i) {
*arg = to;
return Ok(true);
}
}
}
Ok(false)
}
struct InstructionFormatterOutput {
formatted: String,
ins: ObjIns,
error: Option<anyhow::Error>,
ins_operands: Vec<Option<u32>>,
}
impl InstructionFormatterOutput {
fn push_signed(&mut self, value: i64) {
// The formatter writes the '-' operator and then gives us a negative value,
// so convert it to a positive value to avoid double negatives
if value < 0
&& matches!(self.ins.args.last(), Some(ObjInsArg::Arg(ObjInsArgValue::Opaque(v))) if v == "-")
{
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(value.wrapping_abs())));
} else {
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(value)));
}
}
}
impl FormatterOutput for InstructionFormatterOutput {
fn write(&mut self, text: &str, kind: FormatterTextKind) {
self.formatted.push_str(text);
// Skip whitespace after the mnemonic
if self.ins.args.is_empty() && kind == FormatterTextKind::Text {
return;
}
self.ins_operands.push(None);
match kind {
FormatterTextKind::Text | FormatterTextKind::Punctuation => {
self.ins.args.push(ObjInsArg::PlainText(text.to_string().into()));
}
FormatterTextKind::Keyword | FormatterTextKind::Operator => {
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(text.to_string().into())));
}
_ => {
if self.error.is_none() {
self.error = Some(anyhow!("x86: Unsupported FormatterTextKind {:?}", kind));
}
}
}
}
fn write_prefix(&mut self, _instruction: &Instruction, text: &str, _prefix: PrefixKind) {
self.formatted.push_str(text);
self.ins_operands.push(None);
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(text.to_string().into())));
}
fn write_mnemonic(&mut self, _instruction: &Instruction, text: &str) {
self.formatted.push_str(text);
self.ins.mnemonic = text.to_string();
}
fn write_number(
&mut self,
_instruction: &Instruction,
_operand: u32,
instruction_operand: Option<u32>,
text: &str,
value: u64,
number_kind: NumberKind,
kind: FormatterTextKind,
) {
self.formatted.push_str(text);
self.ins_operands.push(instruction_operand);
// Handle relocations
match kind {
FormatterTextKind::LabelAddress => {
if let Some(reloc) = self.ins.reloc.as_ref() {
if matches!(reloc.flags, RelocationFlags::Coff {
typ: pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32
}) {
self.ins.args.push(ObjInsArg::Reloc);
return;
} else if self.error.is_none() {
self.error = Some(anyhow!(
"x86: Unsupported LabelAddress relocation flags {:?}",
reloc.flags
));
}
}
self.ins.args.push(ObjInsArg::BranchDest(value));
self.ins.branch_dest = Some(value);
return;
}
FormatterTextKind::FunctionAddress => {
if let Some(reloc) = self.ins.reloc.as_ref() {
if matches!(reloc.flags, RelocationFlags::Coff {
typ: pe::IMAGE_REL_I386_REL32
}) {
self.ins.args.push(ObjInsArg::Reloc);
return;
} else if self.error.is_none() {
self.error = Some(anyhow!(
"x86: Unsupported FunctionAddress relocation flags {:?}",
reloc.flags
));
}
}
}
_ => {}
}
match number_kind {
NumberKind::Int8 => {
self.push_signed(value as i8 as i64);
}
NumberKind::Int16 => {
self.push_signed(value as i16 as i64);
}
NumberKind::Int32 => {
self.push_signed(value as i32 as i64);
}
NumberKind::Int64 => {
self.push_signed(value as i64);
}
NumberKind::UInt8 | NumberKind::UInt16 | NumberKind::UInt32 | NumberKind::UInt64 => {
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(value)));
}
}
}
fn write_decorator(
&mut self,
_instruction: &Instruction,
_operand: u32,
instruction_operand: Option<u32>,
text: &str,
_decorator: DecoratorKind,
) {
self.formatted.push_str(text);
self.ins_operands.push(instruction_operand);
self.ins.args.push(ObjInsArg::PlainText(text.to_string().into()));
}
fn write_register(
&mut self,
_instruction: &Instruction,
_operand: u32,
instruction_operand: Option<u32>,
text: &str,
_register: Register,
) {
self.formatted.push_str(text);
self.ins_operands.push(instruction_operand);
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(text.to_string().into())));
}
}

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