Compare commits

...

141 Commits

Author SHA1 Message Date
Luke Street e1ae369d17 CI: Fix Cargo.toml version check 2024-09-04 23:51:42 -06:00
Luke Street ce05d6d6c0 Version v2.0.0-beta.6 2024-09-04 23:36:41 -06:00
Luke Street c16a926d9b objdiff-cli: Build static binary & for more arches 2024-09-04 23:33:52 -06:00
Robin Lambertz a32d99923c Coff line number (#100)
* Update object to 0.36

* Add COFF line number support
2024-09-04 18:36:09 -06:00
Luke Street 68606dfdcb Add config.schema.json & update README.md 2024-09-03 20:48:45 -06:00
Luke Street b4650b660a Hide auto-generated objects in object list
With filter option to display them,
if desired. decomp-toolkit will
start writing auto-generated objects
in objdiff.json for reporting
purposes, so this maintains the
existing behavior.
2024-09-03 18:59:07 -06:00
Luke Street 195379968c Support for progress categories & linked stats 2024-09-03 00:59:15 -06:00
Aetias 3bd8aaee41 Bump unarm to 1.5.0 (#98) 2024-08-25 20:57:45 -06:00
Luke Street 1f4175dc21 Overall wasm refactoring & improvements 2024-08-21 19:48:58 -06:00
Luke Street 0fccae1049 Add experimental wasm bindings
Published to npm as objdiff-wasm
2024-08-20 21:40:32 -06:00
Luke Street 8250d26b77 Support R_MIPS_LITERAL, R_MIPS15_S3 relocations
Resolves #92
Resolves #95
2024-08-18 22:05:16 -06:00
Luke Street fd555a6e0f Fix reading little-endian .line section 2024-08-18 21:57:53 -06:00
Luke Street 3710b6a91e Try even harder to recover from protoc missing 2024-08-18 14:01:49 -06:00
Luke Street faebddbc5e More updates to report types 2024-08-18 13:42:41 -06:00
Luke Street a733a950a3 Avoid requiring protoc unless protos change 2024-08-18 13:40:49 -06:00
Luke Street cad9b70632 Support protobuf format for reports
This migrates to using protobuf to
define the "report" and "changes"
formats in objdiff-cli.

The JSON output now uses the Proto3
"JSON Mapping", which is slightly
incompatible with the existing JSON
format. Mainly, 64-bit numbers are
represented as strings, and addresses
are decimal strings instead of hex.

However, the older JSON format is
still accepted by "report changes"
to ease migration.
2024-08-16 00:52:24 -06:00
Luke Street cf937b0be9 Guard against symbols larger than section 2024-08-11 16:54:24 -06:00
Luke Street 23b6d33a98 Fix botched find/replace 2024-08-11 16:53:35 -06:00
Luke Street f17ee83622 Version v2.0.0-beta.3 2024-08-11 16:02:36 -06:00
Luke Street 615ec4c50a mips: Support R_MIPS_PC16 relocations 2024-08-11 16:00:10 -06:00
Luke Street 2cc10b0d06 cargo +nightly fmt 2024-08-11 14:29:58 -06:00
Luke Street 8091941448 Version v2.0.0-beta.2 2024-08-11 14:27:47 -06:00
Luke Street de74dfdba7 Add dummy symbols to empty sections
Allows diffing sections without symbols

Resolves #87
2024-08-11 14:27:27 -06:00
Luke Street 177bd5e895 Fix branch immediates missing diff colors
Resolves #86
2024-08-11 14:01:53 -06:00
Luke Street e1ccee1e73 Improve data section diffing more
This re-implements the older algorithm
used for data and BSS section match
percentages. We perform both and
choose the highest match percent
between the two options.

Resolves #84, #85
2024-08-11 13:53:52 -06:00
Luke Street 952b6a63c3 Better graphics backend fallback
This attempts the following in order:
- wgpu with user-selected backend
- wgpu with automatic backend
- glow (fallback OpenGL backend)

This should eliminate most issues
where objdiff fails to launch.
2024-08-11 13:33:10 -06:00
Steven Casper 09cc9952df Support R_MIPS_GPREL16 relocations correctly (#88)
* Support R_MIPS_GPREL16 relocations correctly

symbols defined in the same file require adding a
special ri_gp_value from the .reginfo section to
their relocation calculations.

* Run nightly rustfmt

* Prevent potential panic when slicing .reginfo
2024-08-08 20:20:41 -06:00
Luke Street fc598af329 Version 2.0.0-beta.1 2024-07-21 23:03:15 -06:00
Luke Street 871407622d Use regex for symbol search
Also fixes case insensitivity and
properly searches the .comm section

Fixes #80
2024-07-21 23:01:58 -06:00
Luke Street e3fff7b0dc Improve data section diff logic
More accurate in more cases

Fixes #81
2024-07-21 22:52:46 -06:00
Amber Brault 75b0e7d9e5 Exception table diff view (#82)
* Basic integration

* Implement basic right click option

Needs lotsa work

* nothing to worry about

* Convert extab diff to separate view

* Make clippy and fmt shut up

* Make clippy fmt shut up for real this time

* Print extab/extabindex symbol names in extab view

* I hate fmt

* Basic integration

* Implement basic right click option

Needs lotsa work

* nothing to worry about

* Convert extab diff to separate view

* Make clippy and fmt shut up

* Make clippy fmt shut up for real this time

* Print extab/extabindex symbol names in extab view

* I hate fmt

* Fix scroll position not being maintained from extab view

* Silly me

* Add rlwinm decoder window

* Remove extra files

* Create Cargo.lock

* Show extab symbol names in hover window

* Appease fmt

* Update symbol_diff.rs

* Update symbol_diff.rs

* Get extab symbol from extabindex relocations instead

* Update Cargo.lock

* Update Cargo.lock
2024-07-21 22:25:54 -06:00
Amber Brault 9f71ce9fea Add rlwinm decoder window (#83)
* Add rlwinm decoder window

* Remove extra files

* Create Cargo.lock

* Make fmt happy

* Update Cargo.lock

* Update Cargo.lock

* Update Cargo.lock
2024-07-21 17:56:46 -06:00
Aetias d9fb48853e Options for ARM disassembly style (#78) 2024-07-14 16:00:57 -06:00
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
Luke Street bb9ff4b928 Update all dependencies 2023-10-05 23:55:01 -04:00
Luke Street 57392daaeb Implement click-to-highlight
Highlights registers, instructions, arguments, symbols or addresses on click.

Resolves #7
2023-10-05 23:40:45 -04:00
Luke Street 2dd3dd60a8 Update webpki (advisory fix) 2023-10-05 00:01:09 -04:00
Luke Street f4757b8d92 Version 0.4.4
Add `#[serde(default)]` to new AppConfig field
2023-10-04 23:52:00 -04:00
Luke Street 52f8c5d4f9 Add "Recent Projects" to file menu 2023-10-03 13:52:16 -04:00
Luke Street 711f40b591 I forgot to bump the Cargo.toml version, oops 2023-09-10 00:24:53 -04:00
Luke Street 26932b2e44 Support min_version field in objdiff.json 2023-09-09 23:54:25 -04:00
Luke Street 192a06bc0b Project configuration improvements
- Support `completed` field for objects in project config. In object tree, displays red for incomplete, green for complete.
- Add support for one-sided diffs. A project can include objects without an associated source file for viewing.
- Add versioning to AppConfig, supporting upgrades without losing user configuration.
2023-09-09 23:43:12 -04:00
Luke Street 5bfa47fce9 Update webpki, rustls-webpki 2023-09-03 09:42:26 -04:00
Luke Street 1d9b9b6893 clippy fix 2023-09-03 09:31:12 -04:00
Luke Street 6b8e469261 Project configuration fixes & improvements
- Allow config to specify object "target_path" and "base_path" explicitly, rather than relying on relative path from the "target_dir" and "base_dir". Useful for more complex directory layouts.
- Fix watch_patterns in project config not using default.
- Fix "Rebuild on changes" not defaulting to true.
- Keep watching project config updates even when "Rebuild on changes" is false.
- Disable some configuration options when loaded from project config file.
2023-09-03 09:28:46 -04:00
Luke Street bf3ba48539 Match watch_patterns with project-relative paths 2023-08-14 00:21:56 -04:00
Luke Street 21cdf268f0 Update README.md 2023-08-12 14:41:19 -04:00
96 changed files with 18911 additions and 5781 deletions
+166 -22
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,54 @@ jobs:
sudo apt-get update
sudo apt-get -y install libgtk-3-dev
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Check git tag against Cargo version
if: startsWith(github.ref, 'refs/tags/')
shell: bash
run: |
set -eou pipefail
tag='${{github.ref}}'
tag="${tag#refs/tags/}"
for file in */Cargo.toml; do
version=$(grep '^version' $file | head -1 | awk -F' = ' '{print $2}' | tr -d '"')
version="v$version"
if [ "$tag" != "$version" ]; then
echo "::error::Git tag doesn't match the Cargo version! ($tag != $version)"
exit 1
fi
done
- 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 +84,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,14 +104,93 @@ 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
build-cli:
name: Build objdiff-cli
env:
CARGO_BIN_NAME: objdiff-cli
strategy:
matrix:
include:
- platform: ubuntu-latest
target: x86_64-unknown-linux-musl
name: linux-x86_64
build: zigbuild
features: default
- platform: ubuntu-latest
target: i686-unknown-linux-musl
name: linux-i686
build: zigbuild
features: default
- platform: ubuntu-latest
target: aarch64-unknown-linux-musl
name: linux-aarch64
build: zigbuild
features: default
- platform: ubuntu-latest
target: armv7-unknown-linux-musleabi
name: linux-armv7l
build: zigbuild
features: default
- platform: windows-latest
target: x86_64-pc-windows-msvc
name: windows-x86_64
build: build
features: default
- platform: windows-latest
target: aarch64-pc-windows-msvc
name: windows-arm64
build: build
features: default
- platform: macos-latest
target: x86_64-apple-darwin
name: macos-x86_64
build: build
features: default
- platform: macos-latest
target: aarch64-apple-darwin
name: macos-arm64
build: build
features: default
fail-fast: false
runs-on: ${{ matrix.platform }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install cargo-zigbuild
if: matrix.build == 'zigbuild'
run: pip install ziglang==0.13.0 cargo-zigbuild==0.19.1
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Cargo build
run: >
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
path: |
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
if-no-files-found: error
build-gui:
name: Build objdiff-gui
env:
CARGO_BIN_NAME: objdiff
strategy:
matrix:
include:
@@ -79,15 +198,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,40 +220,61 @@ 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 ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.name }}
name: ${{ env.CARGO_BIN_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 }}/${{ env.CARGO_BIN_NAME }}
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
if-no-files-found: error
release:
name: Release
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [ build ]
needs: [ check, build-cli, build-gui ]
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
arch="${dir%/}" # remove trailing slash
arch="${arch##"$name-"}" # remove bin name
dst="../out/${name}-${arch}${ext}"
mv "$file" "$dst"
done
done
ls -R ../out
- name: Release
+1
View File
@@ -22,3 +22,4 @@ android.keystore
*.frag
*.vert
*.metal
.vscode/launch.json
Generated
+2426 -1462
View File
File diff suppressed because it is too large Load Diff
+9 -82
View File
@@ -1,85 +1,12 @@
[package]
name = "objdiff"
version = "0.4.0"
edition = "2021"
rust-version = "1.65"
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.71"
byteorder = "1.4.3"
bytes = "1.4.0"
cfg-if = "1.0.0"
const_format = "0.2.31"
cwdemangle = "0.1.5"
dirs = "5.0.1"
eframe = { version = "0.22.0", features = ["persistence"] }
egui = "0.22.0"
egui_extras = "0.22.0"
flagset = "0.4.3"
globset = { version = "0.4.13", features = ["serde1"] }
log = "0.4.19"
memmap2 = "0.7.1"
notify = "6.0.1"
object = { version = "0.31.1", features = ["read_core", "std", "elf"], default-features = false }
png = "0.17.9"
ppc750cl = { git = "https://github.com/terorie/ppc750cl", rev = "9ae36eef34aa6d74e00972c7671f547a2acfd0aa" }
rabbitizer = "1.7.4"
rfd = { version = "0.11.4" } #, default-features = false, features = ['xdg-portal']
serde = { version = "1", features = ["derive"] }
serde_json = "1.0.104"
serde_yaml = "0.9.25"
tempfile = "3.6.0"
thiserror = "1.0.41"
time = { version = "0.3.22", features = ["formatting", "local-offset"] }
toml = "0.7.6"
twox-hash = "1.6.3"
# For Linux static binaries, use rustls
[target.'cfg(target_os = "linux")'.dependencies]
reqwest = { version = "0.11.18", default-features = false, features = ["blocking", "json", "rustls"] }
self_update = { version = "0.37.0", default-features = false, features = ["rustls"] }
# For all other platforms, use native TLS
[target.'cfg(not(target_os = "linux"))'.dependencies]
reqwest = "0.11.18"
self_update = "0.37.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.71"
vergen = { version = "8.2.4", features = ["build", "cargo", "git", "gitcl"] }
+97 -38
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.
@@ -16,10 +26,11 @@ See [Usage](#usage) for more information.
## Usage
objdiff works by comparing two relocatable object files (`.o`). The objects are expected to have the same relative path from the "target" and "base" directories.
objdiff works by comparing two relocatable object files (`.o`). The objects are expected to have the same relative path
from the "target" and "base" directories.
For example, if the target ("expected") object is located at `build/asm/MetroTRK/mslsupp.o` and the base ("actual") object
is located at `build/src/MetroTRK/mslsupp.o`, the following configuration would be used:
For example, if the target ("expected") object is located at `build/asm/MetroTRK/mslsupp.o` and the base ("actual")
object is located at `build/src/MetroTRK/mslsupp.o`, the following configuration would be used:
- Target build directory: `build/asm`
- Base build directory: `build/src`
@@ -38,52 +49,101 @@ See [Configuration](#configuration) for more information.
## Configuration
While **not required** (most settings can be specified in the UI), projects can add an `objdiff.json` (or `objdiff.yaml`, `objdiff.yml`) file to configure the tool automatically. The configuration file must be located in the root project directory.
While **not required** (most settings can be specified in the UI), projects can add an `objdiff.json` file to configure the tool automatically. The configuration file must be located in
the root project directory.
```json5
// objdiff.json
If your project has a generator script (e.g. `configure.py`), it's recommended to generate the objdiff configuration
file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it from being committed.
```json
{
"$schema": "https://raw.githubusercontent.com/encounter/objdiff/main/config.schema.json",
"custom_make": "ninja",
"target_dir": "build/mp1.0/asm",
"base_dir": "build/mp1.0/src",
"build_target": true,
"custom_args": [
"-d",
"keeprsp"
],
"build_target": false,
"build_base": true,
"watch_patterns": [
"*.c",
"*.cp",
"*.cpp",
"*.cxx",
"*.h",
"*.hp",
"*.hpp",
"*.py"
"*.hxx",
"*.s",
"*.S",
"*.asm",
"*.inc",
"*.py",
"*.yml",
"*.txt",
"*.json"
],
"objects": [
"units": [
{
"path": "MetroTRK/mslsupp.o",
"name": "MetroTRK/mslsupp",
"reverse_fn_order": false
},
// ...
"name": "main/MetroTRK/mslsupp",
"target_path": "build/asm/MetroTRK/mslsupp.o",
"base_path": "build/src/MetroTRK/mslsupp.o",
"metadata": {}
}
]
}
```
- `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.
- `target_dir`: Relative from the root of the project, this where the "target" or "expected" objects are located.
These are the **intended result** of the match.
- `base_dir`: Relative from the root of the project, this is where the "base" or "actual" objects are located.
These are objects built from the **current source code**.
- `build_target`: If true, objdiff will tell the build system to build the target objects before diffing (e.g. `make path/to/target.o`).
This is useful if the target objects are not built by default or can change based on project configuration or edits to assembly files.
Requires the build system to be configured properly.
- `watch_patterns` _(optional)_: A list of glob patterns to watch for changes. ([Supported syntax](https://docs.rs/globset/latest/globset/#syntax))
If any of these files change, objdiff will automatically rebuild the objects and re-compare them.
- `objects` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
- `path`: Relative path to the object from the `target_dir` and `base_dir`.
- `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.
- `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.
### Schema
View [config.schema.json](config.schema.json) for all available options. The below list is a summary of the most important options.
`custom_make` _(optional)_: By default, objdiff will use `make` to build the project.
If the project uses a different build system (e.g. `ninja`), specify it here.
The build command will be `[custom_make] [custom_args] path/to/object.o`.
`custom_args` _(optional)_: Additional arguments to pass to the build command prior to the object path.
`build_target`: If true, objdiff will tell the build system to build the target objects before diffing (e.g.
`make path/to/target.o`).
This is useful if the target objects are not built by default or can change based on project configuration or edits
to assembly files.
Requires the build system to be configured properly.
`build_base`: If true, objdiff will tell the build system to build the base objects before diffing (e.g. `make path/to/base.o`).
It's unlikely you'll want to disable this, unless you're using an external tool to rebuild the base object on source file changes.
`watch_patterns` _(optional)_: A list of glob patterns to watch for changes.
([Supported syntax](https://docs.rs/globset/latest/globset/#syntax))
If any of these files change, objdiff will automatically rebuild the objects and re-compare them.
If not specified, objdiff will use the default patterns listed above.
`units` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
> `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.
>
> `target_path`: Path to the "target" or "expected" object from the project root.
> This object is the **intended result** of the match.
>
> `base_path`: Path to the "base" or "actual" object from the project root.
> This object is built from the **current source code**.
>
> `metadata.auto_generated` _(optional)_: Hides the object from the object list, but still includes it in reports.
>
> `metadata.complete` _(optional)_: Marks the object as "complete" (or "linked") in the object list.
> This is useful for marking objects that are fully decompiled. A value of `false` will mark the object as "incomplete".
## 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
@@ -96,6 +156,5 @@ at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
additional terms or conditions.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as
defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
+221
View File
@@ -0,0 +1,221 @@
{
"$schema": "https://json-schema.org/draft-07/schema",
"$id": "https://raw.githubusercontent.com/encounter/objdiff/main/config.schema.json",
"title": "objdiff configuration",
"description": "Configuration file for objdiff",
"type": "object",
"properties": {
"min_version": {
"type": "string",
"description": "Minimum version of objdiff required to load this configuration file.",
"examples": [
"1.0.0",
"2.0.0-beta.1"
]
},
"custom_make": {
"type": "string",
"description": "By default, objdiff will use make to build the project.\nIf the project uses a different build system (e.g. ninja), specify it here.\nThe build command will be `[custom_make] [custom_args] path/to/object.o`.",
"examples": [
"make",
"ninja"
],
"default": "make"
},
"custom_args": {
"type": "array",
"description": "Additional arguments to pass to the build command prior to the object path.",
"items": {
"type": "string"
}
},
"target_dir": {
"type": "string",
"description": "Relative from the root of the project, this where the \"target\" or \"expected\" objects are located.\nThese are the intended result of the match.",
"deprecated": true
},
"base_dir": {
"type": "string",
"description": "Relative from the root of the project, this is where the \"base\" or \"actual\" objects are located.\nThese are objects built from the current source code.",
"deprecated": true
},
"build_target": {
"type": "boolean",
"description": "If true, objdiff will tell the build system to build the target objects before diffing (e.g. `make path/to/target.o`).\nThis is useful if the target objects are not built by default or can change based on project configuration or edits to assembly files.\nRequires the build system to be configured properly.",
"default": false
},
"build_base": {
"type": "boolean",
"description": "If true, objdiff will tell the build system to build the base objects before diffing (e.g. `make path/to/base.o`).\nIt's unlikely you'll want to disable this, unless you're using an external tool to rebuild the base object on source file changes.",
"default": true
},
"watch_patterns": {
"type": "array",
"description": "List of glob patterns to watch for changes in the project.\nIf any of these files change, objdiff will automatically rebuild the objects and re-compare them.\nSupported syntax: https://docs.rs/globset/latest/globset/#syntax",
"items": {
"type": "string"
},
"default": [
"*.c",
"*.cp",
"*.cpp",
"*.cxx",
"*.h",
"*.hp",
"*.hpp",
"*.hxx",
"*.s",
"*.S",
"*.asm",
"*.inc",
"*.py",
"*.yml",
"*.txt",
"*.json"
]
},
"objects": {
"type": "array",
"description": "Use units instead.",
"deprecated": true,
"items": {
"$ref": "#/$defs/unit"
}
},
"units": {
"type": "array",
"description": "If specified, objdiff will display a list of objects in the sidebar for easy navigation.",
"items": {
"$ref": "#/$defs/unit"
}
},
"progress_categories": {
"type": "array",
"description": "Progress categories used for objdiff-cli report.",
"items": {
"$ref": "#/$defs/progress_category"
}
}
},
"$defs": {
"unit": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the object in the UI. If not specified, the object's path will be used."
},
"path": {
"type": "string",
"description": "Relative path to the object from the target_dir and base_dir.\nRequires target_dir and base_dir to be specified.",
"deprecated": true
},
"target_path": {
"type": "string",
"description": "Path to the target object from the project root.\nRequired if path is not specified."
},
"base_path": {
"type": "string",
"description": "Path to the base object from the project root.\nRequired if path is not specified."
},
"reverse_fn_order": {
"type": "boolean",
"description": "Displays function symbols in reversed order.\nUsed to support MWCC's -inline deferred option, which reverses the order of functions in the object file.",
"deprecated": true
},
"complete": {
"type": "boolean",
"description": "Marks the object as \"complete\" (or \"linked\") in the object list.\nThis is useful for marking objects that are fully decompiled. A value of `false` will mark the object as \"incomplete\".",
"deprecated": true
},
"scratch": {
"ref": "#/$defs/scratch"
},
"metadata": {
"ref": "#/$defs/metadata"
}
}
},
"scratch": {
"type": "object",
"description": "If present, objdiff will display a button to create a decomp.me scratch.",
"properties": {
"platform": {
"type": "string",
"description": "The decomp.me platform ID to use for the scratch.",
"examples": [
"gc_wii",
"n64"
]
},
"compiler": {
"type": "string",
"description": "The decomp.me compiler ID to use for the scratch.",
"examples": [
"mwcc_242_81",
"ido7.1"
]
},
"c_flags": {
"type": "string",
"description": "C flags to use for the scratch. Exclude any include paths."
},
"ctx_path": {
"type": "string",
"description": "Path to the context file to use for the scratch."
},
"build_ctx": {
"type": "boolean",
"description": "If true, objdiff will run the build command with the context file as an argument to generate it.",
"default": false
}
},
"required": [
"platform",
"compiler"
]
},
"metadata": {
"type": "object",
"properties": {
"complete": {
"type": "boolean",
"description": "Marks the object as \"complete\" (or \"linked\") in the object list.\nThis is useful for marking objects that are fully decompiled. A value of `false` will mark the object as \"incomplete\"."
},
"reverse_fn_order": {
"type": "boolean",
"description": "Displays function symbols in reversed order.\nUsed to support MWCC's -inline deferred option, which reverses the order of functions in the object file."
},
"source_path": {
"type": "string",
"description": "Path to the source file that generated the object."
},
"progress_categories": {
"type": "array",
"description": "Progress categories used for objdiff-cli report.",
"items": {
"type": "string",
"description": "Unique identifier for the category. (See progress_categories)"
}
},
"auto_generated": {
"type": "boolean",
"description": "Hides the object from the object list by default, but still includes it in reports."
}
}
},
"progress_category": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for the category."
},
"name": {
"type": "string",
"description": "Human-readable name of the category."
}
}
}
}
}
+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
+31
View File
@@ -0,0 +1,31 @@
[package]
name = "objdiff-cli"
version = "2.0.0-beta.6"
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"
memmap2 = "0.9.4"
objdiff-core = { path = "../objdiff-core", features = ["all"] }
prost = "0.13.1"
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;
+423
View File
@@ -0,0 +1,423 @@
use std::{
collections::HashSet,
fs::File,
io::Read,
path::{Path, PathBuf},
time::Instant,
};
use anyhow::{bail, Context, Result};
use argp::FromArgs;
use objdiff_core::{
bindings::report::{
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, Report,
ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
REPORT_VERSION,
},
config::ProjectObject,
diff, obj,
obj::{ObjSectionKind, ObjSymbolFlags},
};
use prost::Message;
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
use tracing::{info, warn};
use crate::util::output::{write_output, OutputFormat};
#[derive(FromArgs, PartialEq, Debug)]
/// Generate a progress report for a project.
#[argp(subcommand, name = "report")]
pub struct Args {
#[argp(subcommand)]
command: SubCommand,
}
#[derive(FromArgs, PartialEq, Debug)]
#[argp(subcommand)]
pub enum SubCommand {
Generate(GenerateArgs),
Changes(ChangesArgs),
}
#[derive(FromArgs, PartialEq, Debug)]
/// Generate a progress report for a project.
#[argp(subcommand, name = "generate")]
pub struct GenerateArgs {
#[argp(option, short = 'p')]
/// Project directory
project: Option<PathBuf>,
#[argp(option, short = 'o')]
/// Output file
output: Option<PathBuf>,
#[argp(switch, short = 'd')]
/// Deduplicate global and weak symbols (runs single-threaded)
deduplicate: bool,
#[argp(option, short = 'f')]
/// Output format (json, json-pretty, proto) (default: json)
format: Option<String>,
}
#[derive(FromArgs, PartialEq, Debug)]
/// List any changes from a previous report.
#[argp(subcommand, name = "changes")]
pub struct ChangesArgs {
#[argp(positional)]
/// Previous report file
previous: PathBuf,
#[argp(positional)]
/// Current report file
current: PathBuf,
#[argp(option, short = 'o')]
/// Output file
output: Option<PathBuf>,
#[argp(option, short = 'f')]
/// Output format (json, json-pretty, proto) (default: json)
format: Option<String>,
}
pub fn run(args: Args) -> Result<()> {
match args.command {
SubCommand::Generate(args) => generate(args),
SubCommand::Changes(args) => changes(args),
}
}
fn generate(args: GenerateArgs) -> Result<()> {
let output_format = OutputFormat::from_option(args.format.as_deref())?;
let project_dir = args.project.as_deref().unwrap_or_else(|| Path::new("."));
info!("Loading project {}", project_dir.display());
let mut project = match objdiff_core::config::try_project_config(project_dir) {
Some((Ok(config), _)) => config,
Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err),
None => bail!("No project configuration found"),
};
info!(
"Generating report for {} units (using {} threads)",
project.objects.len(),
if args.deduplicate { 1 } else { rayon::current_num_threads() }
);
let start = Instant::now();
let mut units = vec![];
let mut existing_functions: HashSet<String> = HashSet::new();
if args.deduplicate {
// If deduplicating, we need to run single-threaded
for object in &mut project.objects {
if let Some(unit) = report_object(
object,
project_dir,
project.target_dir.as_deref(),
project.base_dir.as_deref(),
Some(&mut existing_functions),
)? {
units.push(unit);
}
}
} else {
let vec = project
.objects
.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>>>>()?;
units = vec.into_iter().flatten().collect();
}
let measures = units.iter().flat_map(|u| u.measures.into_iter()).collect();
let mut categories = Vec::new();
for category in &project.progress_categories {
categories.push(ReportCategory {
id: category.id.clone(),
name: category.name.clone(),
measures: Some(Default::default()),
});
}
let mut report =
Report { measures: Some(measures), units, version: REPORT_VERSION, categories };
report.calculate_progress_categories();
let duration = start.elapsed();
info!("Report generated in {}.{:03}s", duration.as_secs(), duration.subsec_millis());
write_output(&report, args.output.as_deref(), output_format)?;
Ok(())
}
fn report_object(
object: &mut ProjectObject,
project_dir: &Path,
target_dir: Option<&Path>,
base_dir: Option<&Path>,
mut existing_functions: Option<&mut HashSet<String>>,
) -> Result<Option<ReportUnit>> {
object.resolve_paths(project_dir, target_dir, base_dir);
match (&object.target_path, &object.base_path) {
(None, Some(_)) if !object.complete().unwrap_or(false) => {
warn!("Skipping object without target: {}", object.name());
return Ok(None);
}
(None, None) => {
warn!("Skipping object without target or base: {}", object.name());
return Ok(None);
}
_ => {}
}
let config = diff::DiffObjConfig { relax_reloc_diffs: true, ..Default::default() };
let target = object
.target_path
.as_ref()
.map(|p| {
obj::read::read(p, &config).with_context(|| format!("Failed to open {}", p.display()))
})
.transpose()?;
let base = object
.base_path
.as_ref()
.map(|p| {
obj::read::read(p, &config).with_context(|| format!("Failed to open {}", p.display()))
})
.transpose()?;
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), None)?;
let metadata = ReportUnitMetadata {
complete: object.complete(),
module_name: target
.as_ref()
.and_then(|o| o.split_meta.as_ref())
.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),
source_path: object.metadata.as_ref().and_then(|m| m.source_path.clone()),
progress_categories: object
.metadata
.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::default();
let mut sections = vec![];
let mut functions = vec![];
let obj = target.as_ref().or(base.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) {
let section_match_percent = section_diff.match_percent.unwrap_or_else(|| {
// Support cases where we don't have a target object,
// assume complete means 100% match
if object.complete().unwrap_or(false) {
100.0
} else {
0.0
}
});
sections.push(ReportItem {
name: section.name.clone(),
fuzzy_match_percent: section_match_percent,
size: section.size,
metadata: Some(ReportItemMetadata {
demangled_name: None,
virtual_address: section.virtual_address,
}),
});
match section.kind {
ObjSectionKind::Data | ObjSectionKind::Bss => {
measures.total_data += section.size;
if section_match_percent == 100.0 {
measures.matched_data += section.size;
}
continue;
}
ObjSectionKind::Code => (),
}
for (symbol, symbol_diff) in section.symbols.iter().zip(&section_diff.symbols) {
if symbol.size == 0 {
continue;
}
if let Some(existing_functions) = &mut existing_functions {
if (symbol.flags.0.contains(ObjSymbolFlags::Global)
|| symbol.flags.0.contains(ObjSymbolFlags::Weak))
&& !existing_functions.insert(symbol.name.clone())
{
continue;
}
}
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
// Support cases where we don't have a target object,
// assume complete means 100% match
if object.complete().unwrap_or(false) {
100.0
} else {
0.0
}
});
measures.fuzzy_match_percent += match_percent * symbol.size as f32;
measures.total_code += symbol.size;
if match_percent == 100.0 {
measures.matched_code += symbol.size;
}
functions.push(ReportItem {
name: symbol.name.clone(),
size: symbol.size,
fuzzy_match_percent: match_percent,
metadata: Some(ReportItemMetadata {
demangled_name: symbol.demangled_name.clone(),
virtual_address: symbol.virtual_address,
}),
});
if match_percent == 100.0 {
measures.matched_functions += 1;
}
measures.total_functions += 1;
}
}
if metadata.complete.unwrap_or(false) {
measures.complete_code = measures.total_code;
measures.complete_data = measures.total_data;
}
measures.calc_fuzzy_match_percent();
measures.calc_matched_percent();
Ok(Some(ReportUnit {
name: object.name().to_string(),
measures: Some(measures),
sections,
functions,
metadata: Some(metadata),
}))
}
fn changes(args: ChangesArgs) -> Result<()> {
let output_format = OutputFormat::from_option(args.format.as_deref())?;
let (previous, current) = if args.previous == Path::new("-") && args.current == Path::new("-") {
// Special case for comparing two reports from stdin
let mut data = vec![];
std::io::stdin().read_to_end(&mut data)?;
let input = ChangesInput::decode(data.as_slice())?;
(input.from.unwrap(), input.to.unwrap())
} else {
let previous = read_report(&args.previous)?;
let current = read_report(&args.current)?;
(previous, current)
};
let mut changes = Changes { from: previous.measures, to: current.measures, units: vec![] };
for prev_unit in &previous.units {
let curr_unit = current.units.iter().find(|u| u.name == prev_unit.name);
let sections = process_items(prev_unit, curr_unit, |u| &u.sections);
let functions = process_items(prev_unit, curr_unit, |u| &u.functions);
let prev_measures = prev_unit.measures;
let curr_measures = curr_unit.and_then(|u| u.measures);
if !functions.is_empty() || prev_measures != curr_measures {
changes.units.push(ChangeUnit {
name: prev_unit.name.clone(),
from: prev_measures,
to: curr_measures,
sections,
functions,
metadata: curr_unit
.as_ref()
.and_then(|u| u.metadata.clone())
.or_else(|| prev_unit.metadata.clone()),
});
}
}
for curr_unit in &current.units {
if !previous.units.iter().any(|u| u.name == curr_unit.name) {
changes.units.push(ChangeUnit {
name: curr_unit.name.clone(),
from: None,
to: curr_unit.measures,
sections: process_new_items(&curr_unit.sections),
functions: process_new_items(&curr_unit.functions),
metadata: curr_unit.metadata.clone(),
});
}
}
write_output(&changes, args.output.as_deref(), output_format)?;
Ok(())
}
fn process_items<F: Fn(&ReportUnit) -> &Vec<ReportItem>>(
prev_unit: &ReportUnit,
curr_unit: Option<&ReportUnit>,
getter: F,
) -> Vec<ChangeItem> {
let prev_items = getter(prev_unit);
let mut items = vec![];
if let Some(curr_unit) = curr_unit {
let curr_items = getter(curr_unit);
for prev_func in prev_items {
let prev_func_info = ChangeItemInfo::from(prev_func);
let curr_func = curr_items.iter().find(|f| f.name == prev_func.name);
let curr_func_info = curr_func.map(ChangeItemInfo::from);
if let Some(curr_func_info) = curr_func_info {
if prev_func_info != curr_func_info {
items.push(ChangeItem {
name: prev_func.name.clone(),
from: Some(prev_func_info),
to: Some(curr_func_info),
metadata: curr_func.as_ref().unwrap().metadata.clone(),
});
}
} else {
items.push(ChangeItem {
name: prev_func.name.clone(),
from: Some(prev_func_info),
to: None,
metadata: prev_func.metadata.clone(),
});
}
}
for curr_func in curr_items {
if !prev_items.iter().any(|f| f.name == curr_func.name) {
items.push(ChangeItem {
name: curr_func.name.clone(),
from: None,
to: Some(ChangeItemInfo::from(curr_func)),
metadata: curr_func.metadata.clone(),
});
}
}
} else {
for prev_func in prev_items {
items.push(ChangeItem {
name: prev_func.name.clone(),
from: Some(ChangeItemInfo::from(prev_func)),
to: None,
metadata: prev_func.metadata.clone(),
});
}
}
items
}
fn process_new_items(items: &[ReportItem]) -> Vec<ChangeItem> {
items
.iter()
.map(|item| ChangeItem {
name: item.name.clone(),
from: None,
to: Some(ChangeItemInfo::from(item)),
metadata: item.metadata.clone(),
})
.collect()
}
fn read_report(path: &Path) -> Result<Report> {
if path == Path::new("-") {
let mut data = vec![];
std::io::stdin().read_to_end(&mut data)?;
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 mmap = unsafe { memmap2::Mmap::map(&file) }
.with_context(|| format!("Failed to map {}", path.display()))?;
Report::parse(mmap.as_ref())
.with_context(|| format!("Failed to load report {}", path.display()))
}
+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)]
/// A local diffing tool for decompilation projects.
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)
unsafe { 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);
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod output;
pub mod term;
+84
View File
@@ -0,0 +1,84 @@
use std::{
fs::File,
io::{BufWriter, Write},
ops::DerefMut,
path::Path,
};
use anyhow::{bail, Context, Result};
use tracing::info;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum OutputFormat {
#[default]
Json,
JsonPretty,
Proto,
}
impl OutputFormat {
pub fn from_str(s: &str) -> Result<Self> {
match s.to_ascii_lowercase().as_str() {
"json" => Ok(Self::Json),
"json-pretty" | "json_pretty" => Ok(Self::JsonPretty),
"binpb" | "pb" | "proto" | "protobuf" => Ok(Self::Proto),
_ => bail!("Invalid output format: {}", s),
}
}
pub fn from_option(s: Option<&str>) -> Result<Self> {
match s {
Some(s) => Self::from_str(s),
None => Ok(Self::default()),
}
}
}
pub fn write_output<T>(input: &T, output: Option<&Path>, format: OutputFormat) -> Result<()>
where T: serde::Serialize + prost::Message {
match output {
Some(output) if output != Path::new("-") => {
info!("Writing to {}", output.display());
let file = File::options()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(output)
.with_context(|| format!("Failed to create file {}", output.display()))?;
match format {
OutputFormat::Json => {
let mut output = BufWriter::new(file);
serde_json::to_writer(&mut output, input)
.context("Failed to write output file")?;
output.flush().context("Failed to flush output file")?;
}
OutputFormat::JsonPretty => {
let mut output = BufWriter::new(file);
serde_json::to_writer_pretty(&mut output, input)
.context("Failed to write output file")?;
output.flush().context("Failed to flush output file")?;
}
OutputFormat::Proto => {
file.set_len(input.encoded_len() as u64)?;
let map = unsafe { memmap2::Mmap::map(&file) }
.context("Failed to map output file")?;
let mut output = map.make_mut().context("Failed to remap output file")?;
input.encode(&mut output.deref_mut()).context("Failed to encode output")?;
}
}
}
_ => match format {
OutputFormat::Json => {
serde_json::to_writer(std::io::stdout(), input)?;
}
OutputFormat::JsonPretty => {
serde_json::to_writer_pretty(std::io::stdout(), input)?;
}
OutputFormat::Proto => {
std::io::stdout().write_all(&input.encode_to_vec())?;
}
},
}
Ok(())
}
+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);
}));
}
+76
View File
@@ -0,0 +1,76 @@
[package]
name = "objdiff-core"
version = "2.0.0-beta.6"
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.
"""
[lib]
crate-type = ["cdylib", "rlib"]
[features]
all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "bindings"]
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", "cwextab", "ppc750cl"]
x86 = ["any-arch", "cpp_demangle", "iced-x86", "msvc-demangler"]
arm = ["any-arch", "cpp_demangle", "unarm", "arm-attr"]
bindings = ["serde_json", "prost", "pbjson"]
wasm = ["bindings", "console_error_panic_hook", "console_log"]
[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.36.0", features = ["read_core", "std", "elf", "pe"], default-features = false }
pbjson = { version = "0.7.0", optional = true }
prost = { version = "0.13.1", optional = true }
serde = { version = "1", features = ["derive"] }
similar = { version = "2.5.0", default-features = false }
strum = { version = "0.26.2", features = ["derive"] }
wasm-bindgen = "0.2.93"
tsify-next = { version = "0.5.4", default-features = false, features = ["js"] }
console_log = { version = "1.0.0", optional = true }
console_error_panic_hook = { version = "0.1.7", optional = true }
# 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 }
cwextab = { version = "0.2.3", 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.5.0", optional = true }
arm-attr = { version = "0.1.1", optional = true }
[build-dependencies]
prost-build = "0.13.1"
pbjson-build = "0.7.0"
+54
View File
@@ -0,0 +1,54 @@
use std::path::{Path, PathBuf};
fn main() {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("protos");
let descriptor_path = root.join("proto_descriptor.bin");
println!("cargo:rerun-if-changed={}", descriptor_path.display());
let descriptor_mtime = std::fs::metadata(&descriptor_path)
.map(|m| m.modified().unwrap())
.unwrap_or(std::time::SystemTime::UNIX_EPOCH);
let mut run_protoc = false;
let proto_files = vec![root.join("diff.proto"), root.join("report.proto")];
for proto_file in &proto_files {
println!("cargo:rerun-if-changed={}", proto_file.display());
let mtime = match std::fs::metadata(proto_file) {
Ok(m) => m.modified().unwrap(),
Err(e) => panic!("Failed to stat proto file {}: {:?}", proto_file.display(), e),
};
if mtime > descriptor_mtime {
run_protoc = true;
}
}
fn prost_config(descriptor_path: &Path, run_protoc: bool) -> prost_build::Config {
let mut config = prost_build::Config::new();
config.file_descriptor_set_path(descriptor_path);
// If our cached descriptor is up-to-date, we don't need to run protoc.
// This is helpful so that users don't need to have protoc installed
// unless they're updating the protos.
if !run_protoc {
config.skip_protoc_run();
}
config
}
if let Err(e) =
prost_config(&descriptor_path, run_protoc).compile_protos(&proto_files, &[root.as_path()])
{
if e.kind() == std::io::ErrorKind::NotFound && e.to_string().contains("protoc") {
eprintln!("protoc not found, skipping protobuf compilation");
prost_config(&descriptor_path, false)
.compile_protos(&proto_files, &[root.as_path()])
.expect("Failed to compile protos");
} else {
panic!("Failed to compile protos: {e:?}");
}
}
let descriptor_set = std::fs::read(descriptor_path).expect("Failed to read descriptor set");
pbjson_build::Builder::new()
.register_descriptors(&descriptor_set)
.expect("Failed to register descriptors")
.preserve_proto_field_names()
.build(&[".objdiff"])
.expect("Failed to build pbjson");
}
+163
View File
@@ -0,0 +1,163 @@
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;
}

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