Compare commits

...

148 Commits

Author SHA1 Message Date
Luke Street 6bcac60df8 ci: Re-enable cargo release 2026-07-10 00:33:21 -06:00
Luke Street b34108fa24 Bump a few more deps & fix advisories 2026-07-10 00:29:27 -06:00
Luke Street 5f9e9063f8 clippy fixes & cargo update 2026-07-10 00:22:48 -06:00
Luke Street a3e11149f7 Version 3.7.3 2026-07-09 23:48:39 -06:00
Haydn Trigg d22c785610 Update GitHub Actions (#357)
* Update build.yaml

* Update build.yaml
2026-07-09 23:46:59 -06:00
Anghelo Carvajal 5035f8ec36 Update rabbitizer integration for 2.0.0-alpha.9 (#347) 2026-07-09 23:46:05 -06:00
Dávid Balatoni b8f401b0ba Make the .mdebug line parser support EE-GCC (PS2) (#362) 2026-07-09 23:45:53 -06:00
Luke Street 282783c081 Suppress unused warnings in objdiff-cli for now 2026-07-09 23:45:10 -06:00
LagoLunatic f5ae76e64d ARM: Fix index out of bounds error when there are no mapping symbols (#359) 2026-07-09 23:30:03 -06:00
LagoLunatic dbabdbff11 ARM: Fix trailing relocations being ignored when inferring function sizes (#360)
* ARM: Fix relocations being ignored when inferring function sizes

* ARM: Add test for trailing relocations

* ARM: Fix trailing relocations being cut in half
2026-07-09 23:29:51 -06:00
Bruno Macabeus 2fdba8842e Add support for R_ARM_V4BX (#377) 2026-07-09 23:28:01 -06:00
Darxoon 0fe4fadbc3 Fix armcc symbols always being hidden (#364) 2026-07-09 23:27:40 -06:00
Michael Rieder 04be189e51 Honor symbol mappings in CLI diffs (#378) 2026-07-09 23:26:50 -06:00
Ethan Roseman 4d0a273d65 Remove git-based dependencies and update several (#379) 2026-07-09 23:25:34 -06:00
Dávid Balatoni 73917c12a8 Fix PS2 MWCC line number parsing (#363) 2026-06-17 20:23:34 -04:00
Haydn Trigg 2cd1e2e0ce Add missing x64 relocation types (#355)
* Add missing x64 relocation types

* Format x86.rs

* Update x86.rs
2026-06-01 21:50:05 -06:00
Haydn Trigg a6ddc506f8 Bump Object Crate to 0.39.1 (#348)
* Bump Object Crate to 0.39.0

* Bump Object Crate to 0.39.1
2026-06-01 21:49:52 -06:00
Haydn Trigg b8d7b3bc1a Strip trailing int3 instructions (alignment padding) for x64 (#354)
* Strip trailing int3 instructions (alignment padding) for x64

* Improve strip trailing int3 instructions

* Improve strip trailing int3 instructions again
2026-06-01 20:58:45 -06:00
LagoLunatic 02daae417c Version 3.7.2 2026-05-31 23:25:27 -04:00
LagoLunatic 8b1b4a9a79 Add config option to limit which string literal encodings are used (#356)
* Fix annoying rust-analyzer error with a cfg

* Refactor literal info into a struct

* Add field hide a literal from the GUI but still use for diffing

* Add config option to limit string literal diffing

* Fix deprecation warning `float_literal_f32_fallback`

* Hide non-preferred string encodings from the GUI

* Fix new cargo check warning

* Clippy again

* Rename "None" -> "Auto"
2026-05-31 23:23:10 -04:00
LagoLunatic 13f1267eac Fix several issues with string literal detection (#351)
* Support detecting more symbol names for strings

* Fix string detectiong being broken for encodings that use null terminators

* Add test cases for string detection and decoding

* Fix test helper function sometimes being considered dead code

* Clippy fixes
2026-05-03 21:27:12 -04:00
Luke Street 3cebee6766 Version 3.7.1 2026-03-21 09:54:09 -06:00
Amber Brault ab0538e9a4 Fix extab hover window display issue (#344)
* Bleh

* Fix formatting

* Update arch_ppc__read_extab.snap

* Put code in separate function

* Make compiler shut up

* Update mod.rs
2026-03-21 09:38:01 -06:00
Luke Street b643265809 Disable advisories check in CI 2026-03-20 23:29:54 -06:00
Bruno Macabeus 7a65e07e9b Add support for R_ARM_THM_PC8 and R_ARM_THM_PC11 (#342)
* Add support for `R_ARM_THM_JUMP8` and `R_ARM_THM_JUMP11`

* Fix `scan_instructions_internal` when reading 2-bytes instruction
2026-03-16 22:08:03 -06:00
Bruno Macabeus 3c4092b965 Fix cache issue when parsing the same file with different side params (#341) 2026-03-15 20:21:05 -06:00
Luke Street 0f599bea2b Version 3.7.0 2026-03-08 13:30:17 -06:00
Luke Street 9927fc35b6 Update time and bytes crates (resolves advisories) 2026-03-08 13:07:20 -06:00
Sebastián Meljem 16fb6a491f Demangle function name on decomp.me scratch creation (#329) 2026-03-08 11:14:24 -06:00
Luke Street 2541aef71a Add symbol kind and structured flags to diff proto output (#337)
* Add symbol kind to diff proto output

Expose SymbolKind (Function, Object, Section, Unknown) in the
DiffSymbol proto message, allowing CLI oneshot diff consumers to
distinguish symbol types without inferring from instruction/data
presence.

* Stop filtering symbols from diff output, replace flags bitmask with message

Remove the symbol filter (size == 0 / Ignored) from DiffObject so all
symbols are emitted. This fixes target_symbol indices being invalid
after filtering.

Replace the DiffSymbolFlag bitmask enum + uint32 field with a
DiffSymbolFlags message of booleans, adding ignored and size_inferred
flags so consumers can filter on their end.
2026-03-08 11:13:40 -06:00
First Last 7ad1f95bf7 Fix symbol index-to-addend bug (#336) 2026-02-21 21:15:53 -07:00
LagoLunatic 66c879a95d Version 3.6.1 (#328) 2026-01-30 05:40:02 +00:00
rjkiv d53c358d2f automatically match MSVC symbols in anonymous namespaces (#325)
* automatically match MSVC symbols in anonymous namespaces

* tweak logic for detecting anonymous MSVC symbols

* fix package format

* address feedback

* lazylock the regex

* formatting

* use once_cell instead

* formatting

* remove regex
2026-01-30 00:13:10 -05:00
Avery Townsend f24c017545 merge gnu function and data sections (#327) 2026-01-29 16:58:52 -07:00
Luke Street 3b8603ab22 Version 3.6.0 2026-01-24 20:29:33 -07:00
Luke Street 86b7d6b229 Remove extra relocs in PPC data_reloc_size 2026-01-24 20:23:40 -07:00
Haydn Trigg 3f82781808 Better COFF String Detection and Big Endian Wide Strings (#321)
* Better COFF and Big Endian Strings

* Cargo Check and Format Fixes

* Rework DataType::String in display_literals

---------

Co-authored-by: Luke Street <luke@street.dev>
2026-01-25 03:23:06 +00:00
Haydn Trigg 926dd06ce9 Handle IMAGE_REL_PPC_ADDR32 size in Coff (#320) 2026-01-24 20:22:45 -07:00
Luke Street de70b6fca0 Restore objdiff-cli oneshot mode (JSON output) (#323) 2026-01-24 19:58:29 -07:00
LagoLunatic 79542a4725 Significantly improve diffing performance and fix minor bug with bss section match percents (#316)
* Move symbol name processing from diff step to read step

* Use min_by_key instead of sorted_unstable_by_key

* Sort all symbols by address when reading the object

This fixes an underreport bug in diff_bss_section.

* More symbol sorting logic on read

* Move is_name_compiler_generated into SymbolFlag & filter out File symbols

---------

Co-authored-by: Luke Street <luke@street.dev>
2026-01-24 19:41:03 -07:00
Avery Townsend 0ebd2ae459 add more i386 elf relocs (#319)
Co-authored-by: Avery Townsend <self@codeshaunted.com>
2026-01-24 19:33:42 -07:00
LagoLunatic 08ebea8bd2 Fix the symbol filter not working in the mapping view (#315)
* Fix the symbol filter not working in the mapping view

* Separate the mapping filter from the regular filter

* Make set/get search function names consistent with each other
2026-01-25 01:15:01 +00:00
Luke Street 46b8890159 Fix clippy & advisory warnings 2026-01-24 17:07:25 -07:00
LagoLunatic 8356fe670d Implement go to next/previous difference (#313) 2026-01-24 16:47:27 -07:00
Luke Street 7834185223 Version 3.5.1 2025-12-23 12:33:12 -07:00
LagoLunatic 216dd70d5b Fix mouse hovers overriding typing in the symbol search field (#308) 2025-12-23 12:32:07 -07:00
LagoLunatic 976e629f08 Fix non-strings also being escaped when copied (#307)
* Fix non-strings also being escaped when copied

* Move copy escape logic into objdiff-core
2025-12-23 12:31:36 -07:00
Luke Street 67b28b7da1 Version 3.5.0 2025-12-21 10:02:59 -07:00
LagoLunatic c0b9bfae21 Fix special ASCII characters not being escaped when copied to clipboard (#306) 2025-12-21 09:58:45 -07:00
LagoLunatic f3f4b0482a Combine .init sections separately from .text sections (#304) 2025-12-21 09:58:13 -07:00
LagoLunatic 278683f103 Improve automatic symbol pairing for functions with compiler-generated names (#303)
* Fix `__arraydtor$1234`s sometimes being swapped

The list of symbols is not necessarily sorted by address, so if there are multiple `__arraydtor$1234` functions it's possible that they're in reverse order on the right, which would cause them to be paired up incorrectly.

To fix this we sort by section index and symbol address after filtering down the list of symbols to ones that have matching names.

* Support pairing up symbols for anonymous classes

* Disallow exact-name pairups for compiler-generated symbols
2025-12-21 09:57:17 -07:00
LagoLunatic 6ad60de32a Allow clicking on branch arrows to scroll target into view (#302) 2025-12-21 09:55:55 -07:00
Luke Street 90c93164ab Update deny.toml 2025-12-21 09:36:27 -07:00
Luke Street c02eb31dbb Version 3.4.5 2025-12-03 18:03:11 -07:00
Darxoon 86d92866aa Fix "Unsupported ARM implicit relocation 29" on armcc binaries (#296) 2025-12-03 17:59:24 -07:00
Luke Street 51c3af2bbe Version 3.4.4 2025-11-26 19:33:00 -07:00
LagoLunatic d0b8b449d9 Ignore hidden symbols when diffing data sections (#291) 2025-11-26 18:49:36 -07:00
LagoLunatic 32f5f202f7 Update outdated extab test snapshot (#292) 2025-11-26 17:56:32 -05:00
LagoLunatic 481dbc185a Support more string encodings and allow copying unescaped strings (#288)
* Copy strings without escape sequences in them

* Add support for more encodings from encoding_rs

Also use encoding_rs instead of CStr for UTF-8.
2025-11-22 19:36:41 -07:00
Luke Street 26a4cc79cf Version 3.4.3 2025-11-22 12:47:03 -07:00
Luke Street 5c96c2ee51 Update cwextab 2025-11-22 12:45:58 -07:00
Luke Street d162fe847e Remove --mapping, --selecting-{left,right} from CLI 2025-11-21 23:55:26 -07:00
Luke Street 2a24eb5aec Version 3.4.2 2025-11-21 22:15:20 -07:00
Aetias f7c291bd55 arm: Fix .word reading 4 bytes when less than 4 remain (#285) 2025-11-20 15:38:55 -07:00
Luke Street 03a578c1bb Update test snapshots for new cwextab 2025-11-18 22:29:42 -07:00
Luke Street d09ef8e207 Version v3.4.1 2025-11-18 22:22:43 -07:00
Luke Street 63552a58ae Move copy button to first header row 2025-11-18 22:22:02 -07:00
Luke Street 827f4a42bd Upgrade all dependencies (incl. egui) 2025-11-18 22:21:46 -07:00
Franco M b2dcecc5d8 Copy button (#271)
* Update diff.rs

* Fixes

* More fixes

* More fixes...

* Cargo fmt

* fmt

* Trim extra

* cargo +nightly fmt

* Update 'Copy ASM' button text to include emoji
2025-11-18 22:00:20 -07:00
Dávid Balatoni 67b237eab6 Adjust symbol name matching logic for GCC (#278)
* Adjust symbol name matching logic for GCC

* Turn $ and . into a list

* Fix borrow issue
2025-11-18 21:55:55 -07:00
Aetias 66da80ff69 arm: Fix .word not on 4-byte boundary (#282) 2025-11-18 21:55:40 -07:00
Luke Street 0a85f540f2 Version v3.4.0 2025-11-12 12:22:01 -07:00
Anghelo Carvajal d79b5b1233 Update gnuv2_demangle to 0.4 (#280) 2025-11-09 11:53:02 -07:00
Anghelo Carvajal 8f5519cb6a Update gnuv2_demangle to version 0.3 (#275) 2025-10-12 21:19:07 -06:00
Aetias 19ec08be9a Update unarm to 2.0 (#274)
* Migrate to unarm 2.0

* Update unarm to proper 2.0 release

* Deduplicate formatters for opaque instruction parts in ARM

* Add option to enable VFPv2 for ARM

* Update unarm to 2.0.1

* Fix read order for big-endian Thumb code

* Skip leading space in ARM instruction params

* Update ARM tests

* arm.rs: Use `alloc::borrow::Cow`
2025-10-12 21:13:29 -06:00
Luke Street 2ad0898efa Version v3.3.1 2025-10-08 21:47:36 -06:00
Ethan Lafrenais 03f2bcb8b1 R_MIPS_GPREL32 Support (#270)
* R_MIPS_GPREL32 Support

* cargo fmt

* Add name for R_MIPS_GPREL32 relocation
2025-10-08 20:34:35 -06:00
BR- 781071761a show symbol size in symbol list (#268)
* show symbol size in symbol list

* configs for show symbol size

* move symbol size config to config-schema
2025-10-08 20:32:07 -06:00
Luke Street 572afa8551 Support IDO .mdebug for line numbers (#264)
* Support IDO .mdebug for line numbers

Resolves #179

* Fix wasm build && cargo clippy --fix

* Resolve review comment
2025-09-25 10:00:40 -06:00
Luke Street 90e81fad7e Improve ARM function size inference
This allows 2-byte padding to be trimmed in ARM functions.

Resolves #253
2025-09-25 00:26:43 -06:00
Luke Street 7a8efb4c88 Version v3.3.0 2025-09-23 13:09:57 -06:00
Luke Street 56dac46280 Support overriding diff options in project config (& for individual units) (#263)
* Support loading diff options from project config

* Support per-unit option overrides
2025-09-23 12:11:40 -06:00
Luke Street 1866158092 Add 'None' demangler option 2025-09-22 23:31:57 -06:00
Anghelo Carvajal fe8e7029f6 Arch-independent demangling and add gnuv2_demangle for old g++ projects (#262) 2025-09-22 17:24:29 -06:00
Ethan Roseman e2c70342c9 Standardize the values for invalid and data opcodes (#261)
* Standardize the value for an invalid opcode
>
> This makes it so that all arches share the same value for an invalid opcode, so platform-specific logic isn't needed for checking whether instructions are valid.
Also updated dependencies

* OPCODE_DATA too
2025-09-13 12:33:06 -06:00
Anghelo Carvajal e6035b00df [MIPS] Add support for the other ABIs supported by rabbitizer (#259)
* [MIPS] Add support for the other abis supported by rabbitizer

* update test
2025-09-10 09:01:08 -06:00
Luke Street 97bcfe23d4 Update test snapshots 2025-09-08 22:40:51 -06:00
Luke Street d684b622b5 Version v3.2.1 2025-09-08 22:37:02 -06:00
Luke Street c698750068 Use dim color for address column in asm view 2025-09-08 22:32:52 -06:00
iFarbod a06d3455ae Ignore extern differences in relocations (#258) 2025-09-08 21:41:53 -06:00
Luke Street fbdaa89cc0 Refactor data diffing & expose WASM API (#256)
* Refactor data diffing & expose WASM API

* Update test snapshots
2025-09-07 18:59:46 -06:00
Luke Street f7cb494a62 Version v3.1.1 2025-09-07 11:32:43 -06:00
iFarbod 7cc6ed2b43 Add support for x86 indirect tables (#251)
* Add support for x86 indirect tables

Closes #239

* Fix formatting issues in x86.rs

* Add x86 indirect table test
2025-09-07 11:28:23 -06:00
Toms 532b684682 Implement automatic symbol pairing for MSVC generated static functions (#255) 2025-09-07 11:28:01 -06:00
LagoLunatic fb1d434bbc Version v3.1.0 2025-09-03 21:44:03 -04:00
LagoLunatic 23009bf9a3 Implement diffing individual data symbols (#244)
* Implement diffing individual data symbols

* Remove unused code for diffing sections

* Data diff view: Make rows show offset within the symbol, not within the section

* Remove SelectedSymbol enum as it only has a single variant now

* Create fake data section symbols to allow diffing entire sections again

* Fix text sections not having their size zeroed out

* Update test snapshots

* Clean up code for inferring section symbol size

* Fix bug where PPC pool references weren't ignoring section symbols

* Update comment

* Always add unique section symbols for data sections

* Update test snapshots

* Remove unnecessary clone in format! call

* Auto-start mapping for unpaired data symbols
2025-09-02 19:37:17 -06:00
Anghelo Carvajal 6fb4bb8855 [MIPS] Fix symbols being filtered out from target side of diff if target object contains .NON_MATCHING markers (#250)
* Fix filtering out symbols from the target side that have a symbol with the same name and a `.NON_MATCHING` suffix.

- Target side: Show all the symbols except the `.NON_MATCHING` ones.
- Base side: Ignore all the `.NON_MATCHING` symbols and also ignore the ones with the same name without the suffix

* fmt

* comment

* tests

* fmt tests

* maybe this could fix wasm?

* Fix wasm?

* fmt

* Move `DiffSide` to `diff` mod

* Update the stuff the advisories CI told me to
2025-09-02 19:13:29 -06:00
Luke Street a138dfa907 Gracefully handle OOB in symbol_context/symbol_hover
Resolves decomp.me#1576
2025-09-02 19:11:07 -06:00
rjkiv 0b95613768 Hide certain symbols for X360 COFFs (#248)
* hide except_data symbols

* hide unwinds by default

* move COFF filters to obj/read.rs

* cargo fmt read.rs

* clippy moment

* clippy pls
2025-08-31 16:45:21 -06:00
Luke Street 5d4b33a500 Update README.md & config.schema.json 2025-08-30 23:56:46 -06:00
LagoLunatic f2a591356e Improve automatic symbol pairing for nameless literals (#247)
* Improve automatic symbol pairing for nameless literals

* Fix data reloc diffing when the reloc points to an in-function static symbol

* Only pair up literals that match perfectly

* Clippy

* Do two separate passes when pairing up literals

* Fix partially-matching literal pairups not working right

* Remove duplicate $ splitting code

* Implement $ splitting for section names too

* Minor cleanup
2025-08-30 23:07:43 -06:00
Luke Street 8d24ec6373 Detect x86 instruction size differences
Check raw code length when instructions have same disassembly
but different encodings. Marks as OpMismatch to indicate
encoding difference.

Fixes #242
Fixes #233
2025-08-30 22:57:39 -06:00
Luke Street 58430d947b ci: Remove quotes from CARGO_ZIGBUILD_ZIG_PATH 2025-08-30 15:49:14 -06:00
Luke Street 1533125f9d Resolve objdiff-wasm clippy warnings 2025-08-30 15:02:10 -06:00
Luke Street 5654060dc8 ci: Use uv tool instead of venv 2025-08-30 15:02:10 -06:00
Luke Street 84079c3934 objdiff-wasm build improvements 2025-08-30 15:02:10 -06:00
LagoLunatic 48804dc2e3 Merge pull request #245 from LagoLunatic/subi
PPC pooled data references: Add support for `subi`, `addis`, and `subis` instructions
2025-08-23 18:05:47 -04:00
LagoLunatic 8cfa8b7dab Update default watch patterns to include more extensions (#246) 2025-08-23 15:37:34 -06:00
LagoLunatic 93a4d7e55d PPC pooled data references: Add support for subi, addis, and subis instructions 2025-08-23 16:37:57 -04:00
iFarbod 7c424a7966 Ignore switchD_ labels generated by Ghidra (#241)
Seeing how commonly used boricj's delinker extension is, it makes sense for this one to be included by default, before #238 is considered and worked on.
2025-08-19 10:23:07 -06:00
Luke Street 678210d58a Change file watcher log message to debug 2025-08-15 16:38:06 -06:00
Luke Street 4302821615 Fix Windows build 2025-08-15 16:31:33 -06:00
Luke Street c4b4244b59 Version v3.0.0 2025-08-15 16:27:27 -06:00
Luke Street 52c138bf06 Add "ignore_patterns" option to config
This allows explicitly ignoring changes to certain
files or directories, even if the changed file ends
up matching `watch_patterns`. The default value,
`build/**/*` will ensure that changes in the build
directory will not trigger a duplicate rebuild.

Resolves #143
Resolves #215
2025-08-15 16:24:26 -06:00
Luke Street 813c8aa539 Add "Diff fill color" option to Appearance
Allows configuring the background color of
lines with a diff.

Resolves #230
2025-08-15 15:43:23 -06:00
Luke Street 0f0aaab795 Fix WSL path handling
Resolves #170
2025-08-15 15:34:58 -06:00
Luke Street b21892be31 Add CLI args to objdiff-gui (incl. --project-dir/-p)
Resolves #41
Resolves #211
2025-08-15 15:25:55 -06:00
Luke Street 247d6da94b Restore extab diff view 2025-08-15 15:06:16 -06:00
Luke Street bd95faa9c3 Remove objdiff-cli diff JSON output mode
This has been unimplemented since v3.0.0-alpha.1,
and I don't currently have plans to bring it back.
If you need it for something, please open an issue!
2025-08-15 14:57:34 -06:00
Luke Street 2c57e4960f Add ARM logic for inferred function size padding
Resolves #237
2025-08-15 14:48:26 -06:00
Luke Street cff4be2979 Update gimli, object
Resolves #228
2025-08-15 14:47:06 -06:00
LagoLunatic e1da90943c Version v3.0.0-beta.14 2025-08-13 01:45:05 -04:00
LagoLunatic b5713db333 Update dependencies 2025-08-13 01:42:34 -04:00
LagoLunatic 4c3f5e9836 Merge pull request #236 from LagoLunatic/symbol-ctx
Fix context menu not appearing when right clicking the function name in the function diff view
2025-08-11 17:28:26 -04:00
LagoLunatic e9ce02feb0 Disable double tooltip for elided function name in function diff view 2025-08-08 20:36:48 -04:00
LagoLunatic 9a378d85ed Fix context menu on function name in function diff view 2025-08-08 20:35:16 -04:00
LagoLunatic c8ff45f2c8 Merge pull request #234 from LagoLunatic/no-diff-data
Fix data section not showing when there is no section on the other side
2025-08-08 20:32:28 -04:00
LagoLunatic 34e4513c69 Fix data section not showing when there is no section on the other side 2025-08-06 16:09:18 -04:00
Luke Street a015971c20 Use deprecated egui::menu as temp workaround
egui 0.32 refactored menus, and now having a
ComboBox within the menu does not work properly.
Temporarily use the deprecated menu instead.
2025-08-02 13:34:10 -06:00
Luke Street e67d5998b3 Enable PS instructions for any 32-bit PPC ELF
Fixes an issue where ProDG for GameCube objects
were not enabling PS instruction support.
2025-08-02 11:36:35 -06:00
Luke Street 91bc23edfc Fix objdiff-wasm build 2025-08-02 11:32:04 -06:00
Luke Street c9c3b32376 Use let chains (a.k.a. cargo clippy --fix) 2025-08-02 11:27:28 -06:00
Luke Street 0dc123b064 Don't fail on line info parsing; use gimli::RelocateReader
Workaround for #228
2025-08-02 11:27:28 -06:00
Luke Street 1e62d4664c Make function size inference logic arch-specific
For MIPS, account for delay slot nops. For x86,
check for trailing nops (0x90). For PPC, check
for 4-byte 0x00 padding.

Resolves #229
2025-08-02 10:56:26 -06:00
Luke Street 1205e8ceb4 Version v3.0.0-beta.12 2025-07-29 21:31:12 -06:00
Luke Street c917cad5f0 Strip zeros from end of inferred function sizes
Resolves #3
2025-07-29 21:20:47 -06:00
Luke Street dd653329f5 Fix reading IMAGE_REL_PPC_REFHI/REFLO without PAIR 2025-07-29 20:58:34 -06:00
Luke Street f5d3d5f10a gui: Split OpenGL into OpenGL (glow), OpenGL ES (wgpu) 2025-07-29 20:49:09 -06:00
Luke Street c327ed3ea8 Update to egui 0.32 (& update all deps) 2025-07-28 17:30:52 -06:00
Ethan Roseman 85fb18a21a Update rabbitizer to v2.0.0-alpha.4 (#226)
* Update rabbitizer to v2.0.0-alpha.4

* Cargo update
2025-07-27 22:13:42 -06:00
Luke Street 00ad0d8094 Support PPC64 ELFs (PS3); refactor relocation processing 2025-07-21 21:01:03 -06:00
Luke Street 8fac63c42c Remove temporary test 2025-07-17 21:23:12 -06:00
Luke Street 0fb7f3901c Add PPC COFF tests; fix IMAGE_REL_PPC_PAIR handling 2025-07-17 21:19:16 -06:00
Mark Langen 3385f58341 Fix data flow analysis for multiple text sections (#220)
* Fix data flow analysis for multiple text sections

* Data flow analysis results were only keyed by the symbol (function)
  address. That doen't work if there are multiple text sections, the
  result from the first function in one section will stomp the result
  from the first function in another because both have address zero.

* Remove the ambiguity by keying off of the section address as well.

* Formatting

* Satisfy wasm build

* Clippy

* Formatting again

* Thought that section was the section address not the section number.

---------

Co-authored-by: Luke Street <luke.street@encounterpc.com>
2025-07-17 16:04:56 -06:00
Luke Street 60b227f45e Support PowerPC COFF (Xenon, Xbox 360)
ppc750cl is superseded by the powerpc crate, which
supports PPC64, AltiVec and VMX128 extensions.
2025-07-17 13:33:12 -06:00
LagoLunatic 127ae5ae44 PPC pooled relocations: Ignore hidden symbols (#221)
* PPC pooled relocations: Ignore hidden symbols

* PPC pooled relocations: Also ignore 'ignored' symbols
2025-07-07 17:29:05 -06:00
Luke Street 5f48e69775 More clippy fixes 2025-07-07 15:29:30 -06:00
Luke Street 8756eee07b clippy fixes & version bump 2025-07-07 14:56:41 -06:00
133 changed files with 36732 additions and 11089 deletions
+29 -28
View File
@@ -28,7 +28,7 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get -y install libgtk-3-dev sudo apt-get -y install libgtk-3-dev
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup Rust toolchain - name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
with: with:
@@ -36,9 +36,9 @@ jobs:
- name: Cache Rust workspace - name: Cache Rust workspace
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
- name: Cargo check - name: Cargo check
run: cargo check --all-targets --all-features run: cargo check --all-targets --all-features --workspace
- name: Cargo clippy - name: Cargo clippy
run: cargo clippy --all-targets --all-features run: cargo clippy --all-targets --all-features --workspace
fmt: fmt:
name: Format name: Format
@@ -47,7 +47,7 @@ jobs:
RUSTFLAGS: -D warnings RUSTFLAGS: -D warnings
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup Rust toolchain - name: Setup Rust toolchain
# We use nightly options in rustfmt.toml # We use nightly options in rustfmt.toml
uses: dtolnay/rust-toolchain@nightly uses: dtolnay/rust-toolchain@nightly
@@ -62,12 +62,12 @@ jobs:
strategy: strategy:
matrix: matrix:
checks: checks:
- advisories # - advisories
- bans licenses sources - bans licenses sources
# Prevent new advisories from failing CI # Prevent new advisories from failing CI
continue-on-error: ${{ matrix.checks == 'advisories' }} continue-on-error: ${{ matrix.checks == 'advisories' }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: EmbarkStudios/cargo-deny-action@v2 - uses: EmbarkStudios/cargo-deny-action@v2
with: with:
command: check ${{ matrix.checks }} command: check ${{ matrix.checks }}
@@ -86,13 +86,13 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get -y install libgtk-3-dev sudo apt-get -y install libgtk-3-dev
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup Rust toolchain - name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: Cache Rust workspace - name: Cache Rust workspace
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
- name: Cargo test - name: Cargo test
run: cargo test --release --features all run: cargo test --release --all-features --workspace
build-cli: build-cli:
name: Build objdiff-cli name: Build objdiff-cli
@@ -145,14 +145,15 @@ jobs:
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Install uv
if: matrix.build == 'zigbuild'
uses: astral-sh/setup-uv@v7
- name: Install cargo-zigbuild - name: Install cargo-zigbuild
if: matrix.build == 'zigbuild' if: matrix.build == 'zigbuild'
run: | run: |
python3 -m venv .venv uv tool install cargo-zigbuild==0.20.1 --with-executables-from ziglang==0.15.1
. .venv/bin/activate echo "CARGO_ZIGBUILD_ZIG_PATH=$(uv tool dir)/cargo-zigbuild/bin/python-zig" >> $GITHUB_ENV
echo PATH=$PATH >> $GITHUB_ENV
pip install ziglang==0.13.0.post1 cargo-zigbuild==0.19.8
- name: Setup Rust toolchain - name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
with: with:
@@ -166,7 +167,7 @@ jobs:
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }} cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }} --bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }} name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
path: | path: |
@@ -212,14 +213,15 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get -y install ${{ matrix.packages }} sudo apt-get -y install ${{ matrix.packages }}
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Install uv
if: matrix.build == 'zigbuild'
uses: astral-sh/setup-uv@v7
- name: Install cargo-zigbuild - name: Install cargo-zigbuild
if: matrix.build == 'zigbuild' if: matrix.build == 'zigbuild'
run: | run: |
python3 -m venv .venv uv tool install cargo-zigbuild==0.20.1 --with-executables-from ziglang==0.15.1
. .venv/bin/activate echo "CARGO_ZIGBUILD_ZIG_PATH=$(uv tool dir)/cargo-zigbuild/bin/python-zig" >> $GITHUB_ENV
echo PATH=$PATH >> $GITHUB_ENV
pip install ziglang==0.13.0.post1 cargo-zigbuild==0.19.8
- name: Setup Rust toolchain - name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
with: with:
@@ -233,7 +235,7 @@ jobs:
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }} cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }} --bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }} name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
path: | path: |
@@ -246,7 +248,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup Rust toolchain - name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@nightly uses: dtolnay/rust-toolchain@nightly
with: with:
@@ -254,7 +256,7 @@ jobs:
- name: Cache Rust workspace - name: Cache Rust workspace
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v6.4.0
with: with:
node-version: lts/* node-version: lts/*
- name: Install dependencies - name: Install dependencies
@@ -262,7 +264,7 @@ jobs:
- name: Build - name: Build
run: npm -C objdiff-wasm run build run: npm -C objdiff-wasm run build
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v7
with: with:
name: wasm name: wasm
path: objdiff-wasm/dist/ path: objdiff-wasm/dist/
@@ -275,7 +277,7 @@ jobs:
needs: [ build-cli, build-gui, build-wasm ] needs: [ build-cli, build-gui, build-wasm ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Check git tag against package versions - name: Check git tag against package versions
shell: bash shell: bash
run: | run: |
@@ -338,12 +340,11 @@ jobs:
release-cargo: release-cargo:
name: Release (Cargo) name: Release (Cargo)
if: 'false' # TODO re-enable when all dependencies are published
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [ check-version ] needs: [ check-version ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup Rust toolchain - name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: Publish - name: Publish
@@ -358,9 +359,9 @@ jobs:
needs: [ check-version ] needs: [ check-version ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
- name: Setup Node - name: Setup Node
uses: actions/setup-node@v4 uses: actions/setup-node@v6.4.0
with: with:
node-version: lts/* node-version: lts/*
registry-url: 'https://registry.npmjs.org' registry-url: 'https://registry.npmjs.org'
+1 -1
View File
@@ -24,7 +24,7 @@ repos:
description: Run cargo clippy on all project files. description: Run cargo clippy on all project files.
language: system language: system
entry: cargo entry: cargo
args: ["+nightly", "clippy", "--all-targets", "--all-features"] args: ["+nightly", "clippy", "--all-targets", "--all-features", "--workspace"]
pass_filenames: false pass_filenames: false
- id: cargo-deny - id: cargo-deny
name: cargo deny name: cargo deny
+44
View File
@@ -0,0 +1,44 @@
# Repository Guidelines
## Project Structure & Module Organization
- `objdiff-core`: core library with diffing logic and shared components
- `objdiff-cli`: CLI/TUI with `ratatui`
- `objdiff-gui`: GUI with `eframe`/`egui`
- `objdiff-wasm`: WebAssembly bindings
objdiff has three main frontends: GUI, CLI/TUI, and [web](https://github.com/encounter/objdiff-web) (utilizing `objdiff-wasm`).
`objdiff-gui` is the most fully-featured and is the primary target for new features.
`objdiff-cli` has an interactive TUI `diff` mode (powered by `ratatui`) and a non-interactive `report` mode to generate detailed progress reports (e.g. for <https://decomp.dev>).
`objdiff-wasm` is compiled into a [WebAssembly Component](https://component-model.bytecodealliance.org/). The [API](objdiff-wasm/wit/objdiff.wit) is defined using [WIT](https://component-model.bytecodealliance.org/design/wit.html). The web interface is separate, but mirrors the GUI in functionality.
## Build, Test, and Development Commands
Run `cargo build` for a debug build and `cargo build --release` for an optimized build.
The CLI can be exercised with `cargo run --release --bin objdiff-cli -- --help`.
The WASM build is not included in the workspace by default due to differences in toolchain and target; build it with `npm -C objdiff-wasm run build`. (nightly required)
In general, do NOT use `--workspace`, as it will include the WASM crate and likely fail. Formatting and linting commands are exempt.
Pre-commit tasks:
- `cargo test` to run the test suite
- `cargo +nightly fmt --all` to format code (nightly required)
- `cargo +nightly clippy --all-targets --all-features --workspace -- -D warnings` to lint code (nightly required)
- `cargo deny check` (`cargo install --locked cargo-deny` if needed) if dependencies change
## Testing Guidelines
Favor focused unit tests adjacent to the code, and integration scenarios in `objdiff-core/tests`.
Integration tests utilize snapshots with the `insta` crate; run `curl -LsSf https://insta.rs/install.sh | sh` if needed.
To generate updated snapshots, use `cargo insta test`, review diffs manually, then accept changes with `cargo insta accept` (or simply `mv` to accept specific snapshots).
## Configuration Tips
- `config.schema.json`: JSON schema for the user-facing project config file (`objdiff.json`)
- `objdiff-core/config-schema.json`: Internal options schema to generate `DiffObjConfig` (see `objdiff-core/config_gen.rs`)
The project configuration (`objdiff.json`) is intended to be generated by the projects' build system. It includes paths to the target (expected) and base (current) object files, along with build commands and file watch patterns. See `README.md` for a summary of key options.
Generated
+2491 -1521
View File
File diff suppressed because it is too large Load Diff
+17 -7
View File
@@ -5,18 +5,28 @@ members = [
"objdiff-gui", "objdiff-gui",
"objdiff-wasm", "objdiff-wasm",
] ]
default-members = [
"objdiff-cli",
"objdiff-core",
"objdiff-gui",
# Exclude objdiff-wasm by default
]
resolver = "3" resolver = "3"
[workspace.package]
version = "3.7.3"
authors = ["Luke Street <luke@street.dev>"]
edition = "2024"
license = "MIT OR Apache-2.0"
repository = "https://github.com/encounter/objdiff"
rust-version = "1.88"
[profile.release-lto] [profile.release-lto]
inherits = "release" inherits = "release"
lto = "fat" lto = "fat"
strip = "debuginfo" strip = "debuginfo"
codegen-units = 1 codegen-units = 1
[workspace.package] [profile.release-min]
version = "3.0.0-beta.10" inherits = "release-lto"
authors = ["Luke Street <luke@street.dev>"] opt-level = "z"
edition = "2024"
license = "MIT OR Apache-2.0"
repository = "https://github.com/encounter/objdiff"
rust-version = "1.85"
+73 -70
View File
@@ -7,19 +7,21 @@ A local diffing tool for decompilation projects. Inspired by [decomp.me](https:/
Features: Features:
- Compare entire object files: functions and data. - Compare entire object files: functions and data
- Built-in symbol demangling for C++. (CodeWarrior, Itanium & MSVC) - Built-in C++ symbol demangling (GCC, MSVC, CodeWarrior, Itanium)
- Automatic rebuild on source file changes. - Automatic rebuild on source file changes
- Project integration via [configuration file](#configuration). - Project integration via [configuration file](#configuration)
- Search and filter all of a project's objects and quickly switch. - Search and filter objects with quick switching
- Click to highlight all instances of values and registers. - Click-to-highlight values and registers
- Detailed progress reporting (powers [decomp.dev](https://decomp.dev))
- WebAssembly API, [web interface](https://github.com/encounter/objdiff-web) and [Visual Studio Code extension](https://marketplace.visualstudio.com/items?itemName=decomp-dev.objdiff) (WIP)
Supports: Supports:
- ARM (GBA, DS, 3DS) - ARM (GBA, DS, 3DS)
- ARM64 (Switch) - ARM64 (Switch)
- MIPS (N64, PS1, PS2, PSP) - MIPS (N64, PS1, PS2, PSP)
- PowerPC (GameCube, Wii) - PowerPC (GameCube, Wii, PS3, Xbox 360)
- SuperH (Saturn, Dreamcast) - SuperH (Saturn, Dreamcast)
- x86, x86_64 (PC) - x86, x86_64 (PC)
@@ -40,7 +42,7 @@ For Linux and macOS, run `chmod +x objdiff-*` to make the binary executable.
### CLI ### CLI
CLI binaries can be found on the [releases page](https://github.com/encounter/objdiff/releases). CLI binaries are available on the [releases page](https://github.com/encounter/objdiff/releases).
## Screenshots ## Screenshots
@@ -49,33 +51,30 @@ CLI binaries can be found on the [releases page](https://github.com/encounter/ob
## Usage ## Usage
objdiff works by comparing two relocatable object files (`.o`). The objects are expected to have the same relative path objdiff compares two relocatable object files (`.o`). Here's how it works:
from the "target" and "base" directories.
For example, if the target ("expected") object is located at `build/asm/MetroTRK/mslsupp.o` and the base ("actual") 1. **Create an `objdiff.json` configuration file** in your project root (or generate it with your build script).
object is located at `build/src/MetroTRK/mslsupp.o`, the following configuration would be used: This file lists **all objects in the project** with their target ("expected") and base ("current") paths.
- Target build directory: `build/asm` 2. **Load the project** in objdiff.
- Base build directory: `build/src`
- Object: `MetroTRK/mslsupp.o`
objdiff will then execute the build system from the project directory to build both objects: 3. **Select an object** from the sidebar to begin diffing.
```sh 4. **objdiff automatically:**
$ make build/asm/MetroTRK/mslsupp.o # Only if "Build target object" is enabled - Executes the build system to compile the base object (from current source code)
$ make build/src/MetroTRK/mslsupp.o - Compares the two objects and displays the differences
``` - Watches for source file changes and rebuilds when detected
The objects will then be compared and the results will be displayed in the UI. The configuration file allows complete flexibility in project structure - your build directories can have any layout as long as the paths are specified correctly.
See [Configuration](#configuration) for more information. See [Configuration](#configuration) for setup details.
## Configuration ## Configuration
While **not required** (most settings can be specified in the UI), projects can add an `objdiff.json` file to configure the tool automatically. The configuration file must be located in Projects can add an `objdiff.json` file to configure the tool automatically. The configuration file must be located in
the root project directory. the root project directory.
If your project has a generator script (e.g. `configure.py`), it's recommended to generate the objdiff configuration If your project has a generator script (e.g. `configure.py`), it's highly recommended to generate the objdiff configuration
file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it from being committed. file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it from being committed.
```json ```json
@@ -90,22 +89,31 @@ file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it
"build_base": true, "build_base": true,
"watch_patterns": [ "watch_patterns": [
"*.c", "*.c",
"*.cc",
"*.cp", "*.cp",
"*.cpp", "*.cpp",
"*.cxx", "*.cxx",
"*.c++",
"*.h", "*.h",
"*.hh",
"*.hp", "*.hp",
"*.hpp", "*.hpp",
"*.hxx", "*.hxx",
"*.h++",
"*.pch",
"*.pch++",
"*.inc",
"*.s", "*.s",
"*.S", "*.S",
"*.asm", "*.asm",
"*.inc",
"*.py", "*.py",
"*.yml", "*.yml",
"*.txt", "*.txt",
"*.json" "*.json"
], ],
"ignore_patterns": [
"build/**/*"
],
"units": [ "units": [
{ {
"name": "main/MetroTRK/mslsupp", "name": "main/MetroTRK/mslsupp",
@@ -119,74 +127,69 @@ file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it
### Schema ### Schema
View [config.schema.json](config.schema.json) for all available options. The below list is a summary of the most important options. > [!NOTE]
> View [config.schema.json](config.schema.json) for all available options. Below is a summary of the most important options.
`custom_make` _(optional)_: By default, objdiff will use `make` to build the project. #### Build Configuration
If the project uses a different build system (e.g. `ninja`), specify it here.
The build command will be `[custom_make] [custom_args] path/to/object.o`.
`custom_args` _(optional)_: Additional arguments to pass to the build command prior to the object path. **`custom_make`** _(optional, default: `"make"`)_
If the project uses a different build system (e.g. `ninja`), specify it here. The build command will be `[custom_make] [custom_args] path/to/object.o`.
`build_target`: If true, objdiff will tell the build system to build the target objects before diffing (e.g. **`custom_args`** _(optional)_
`make path/to/target.o`). Additional arguments to pass to the build command prior to the object path.
This is useful if the target objects are not built by default or can change based on project configuration or edits
to assembly files.
Requires the build system to be configured properly.
`build_base`: If true, objdiff will tell the build system to build the base objects before diffing (e.g. `make path/to/base.o`). **`build_target`** _(default: `false`)_
It's unlikely you'll want to disable this, unless you're using an external tool to rebuild the base object on source file changes. If true, objdiff will build the target objects before diffing (e.g. `make path/to/target.o`). Useful if target objects are not built by default or can change based on project configuration. Requires proper build system configuration.
`watch_patterns` _(optional)_: A list of glob patterns to watch for changes. **`build_base`** _(default: `true`)_
([Supported syntax](https://docs.rs/globset/latest/globset/#syntax)) If true, objdiff will build the base objects before diffing (e.g. `make path/to/base.o`). It's unlikely you'll want to disable this unless using an external tool to rebuild the base object.
If any of these files change, objdiff will automatically rebuild the objects and re-compare them.
If not specified, objdiff will use the default patterns listed above.
`units` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation. #### File Watching
> `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used. **`watch_patterns`** _(optional, default: listed above)_
> A list of glob patterns to watch for changes ([supported syntax](https://docs.rs/globset/latest/globset/#syntax)). When these files change, objdiff automatically rebuilds and re-compares objects.
> `target_path`: Path to the "target" or "expected" object from the project root.
> This object is the **intended result** of the match. **`ignore_patterns`** _(optional, default: listed above)_
> A list of glob patterns to explicitly ignore when watching for changes ([supported syntax](https://docs.rs/globset/latest/globset/#syntax)).
> `base_path`: Path to the "base" or "actual" object from the project root.
> This object is built from the **current source code**. #### Units (Objects)
>
> `metadata.auto_generated` _(optional)_: Hides the object from the object list, but still includes it in reports. **`units`** _(optional)_
> If specified, objdiff displays a list of objects in the sidebar for easy navigation. Each unit contains:
> `metadata.complete` _(optional)_: Marks the object as "complete" (or "linked") in the object list.
> This is useful for marking objects that are fully decompiled. A value of `false` will mark the object as "incomplete". - **`name`** _(optional)_ - The display name in the UI. Defaults to the object's `path`.
- **`target_path`** _(optional)_ - Path to the "target" or "expected" object (the **intended result**).
- **`base_path`** _(optional)_ - Path to the "base" or "current" object (built from **current source code**). Omit if there is no source object yet.
- **`metadata.auto_generated`** _(optional)_ - Hides the object from the sidebar but includes it in progress reports.
- **`metadata.complete`** _(optional)_ - Marks the object as "complete" (linked) when `true` or "incomplete" when `false`.
## Building ## Building
Install Rust via [rustup](https://rustup.rs). Install Rust via [rustup](https://rustup.rs).
```shell ```shell
$ git clone https://github.com/encounter/objdiff.git git clone https://github.com/encounter/objdiff.git
$ cd objdiff cd objdiff
$ cargo run --release cargo run --release
``` ```
Or using `cargo install`. Or install directly with cargo:
```shell ```shell
$ cargo install --locked --git https://github.com/encounter/objdiff.git objdiff-gui objdiff-cli cargo install --locked --git https://github.com/encounter/objdiff.git objdiff-gui objdiff-cli
``` ```
The binaries will be installed to `~/.cargo/bin` as `objdiff` and `objdiff-cli`. Binaries will be installed to `~/.cargo/bin` as `objdiff` and `objdiff-cli`.
## Installing `pre-commit` ## Contributing
When contributing, it's recommended to install `pre-commit` to automatically run the linter and formatter before a commit. Install `pre-commit` to run linting and formatting automatically:
[`uv`](https://github.com/astral-sh/uv#installation) is recommended to manage Python version and tools.
Rust nightly is required for `cargo +nightly fmt` and `cargo +nightly clippy`.
```shell ```shell
$ cargo install --locked cargo-deny rustup toolchain install nightly # Required for cargo fmt/clippy
$ rustup toolchain install nightly cargo install --locked cargo-deny # https://github.com/EmbarkStudios/cargo-deny
$ uv tool install pre-commit uv tool install pre-commit # https://docs.astral.sh/uv, or use pipx or pip
$ pre-commit install pre-commit install
``` ```
## License ## License
+59 -10
View File
@@ -15,7 +15,7 @@
}, },
"custom_make": { "custom_make": {
"type": "string", "type": "string",
"description": "By default, objdiff will use make to build the project.\nIf the project uses a different build system (e.g. ninja), specify it here.\nThe build command will be `[custom_make] [custom_args] path/to/object.o`.", "description": "If the project uses a different build system (e.g. ninja), specify it here.\nThe build command will be `[custom_make] [custom_args] path/to/object.o`.",
"examples": [ "examples": [
"make", "make",
"ninja" "ninja"
@@ -41,39 +41,55 @@
}, },
"build_target": { "build_target": {
"type": "boolean", "type": "boolean",
"description": "If true, objdiff will tell the build system to build the target objects before diffing (e.g. `make path/to/target.o`).\nThis is useful if the target objects are not built by default or can change based on project configuration or edits to assembly files.\nRequires the build system to be configured properly.", "description": "If true, objdiff will build the target objects before diffing (e.g. `make path/to/target.o`).\nUseful if target objects are not built by default or can change based on project configuration.\nRequires proper build system configuration.",
"default": false "default": false
}, },
"build_base": { "build_base": {
"type": "boolean", "type": "boolean",
"description": "If true, objdiff will tell the build system to build the base objects before diffing (e.g. `make path/to/base.o`).\nIt's unlikely you'll want to disable this, unless you're using an external tool to rebuild the base object on source file changes.", "description": "If true, objdiff will build the base objects before diffing (e.g. `make path/to/base.o`).\nIt's unlikely you'll want to disable this unless using an external tool to rebuild the base object.",
"default": true "default": true
}, },
"watch_patterns": { "watch_patterns": {
"type": "array", "type": "array",
"description": "List of glob patterns to watch for changes in the project.\nIf any of these files change, objdiff will automatically rebuild the objects and re-compare them.\nSupported syntax: https://docs.rs/globset/latest/globset/#syntax", "description": "A list of glob patterns to watch for changes.\nWhen these files change, objdiff automatically rebuilds and re-compares objects.\nSupported syntax: https://docs.rs/globset/latest/globset/#syntax",
"items": { "items": {
"type": "string" "type": "string"
}, },
"default": [ "default": [
"*.c", "*.c",
"*.cc",
"*.cp", "*.cp",
"*.cpp", "*.cpp",
"*.cxx", "*.cxx",
"*.c++",
"*.h", "*.h",
"*.hh",
"*.hp", "*.hp",
"*.hpp", "*.hpp",
"*.hxx", "*.hxx",
"*.h++",
"*.pch",
"*.pch++",
"*.inc",
"*.s", "*.s",
"*.S", "*.S",
"*.asm", "*.asm",
"*.inc",
"*.py", "*.py",
"*.yml", "*.yml",
"*.txt", "*.txt",
"*.json" "*.json"
] ]
}, },
"ignore_patterns": {
"type": "array",
"description": "A list of glob patterns to explicitly ignore when watching for changes.\nSupported syntax: https://docs.rs/globset/latest/globset/#syntax",
"items": {
"type": "string"
},
"default": [
"build/**/*"
]
},
"objects": { "objects": {
"type": "array", "type": "array",
"description": "Use units instead.", "description": "Use units instead.",
@@ -95,6 +111,25 @@
"items": { "items": {
"$ref": "#/$defs/progress_category" "$ref": "#/$defs/progress_category"
} }
},
"options": {
"type": "object",
"description": "Diff configuration options that should be applied automatically when the project is loaded.",
"additionalProperties": {
"oneOf": [
{
"type": "boolean"
},
{
"type": "string"
}
]
},
"examples": [
{
"demangler": "gnu_legacy"
}
]
} }
}, },
"$defs": { "$defs": {
@@ -103,7 +138,7 @@
"properties": { "properties": {
"name": { "name": {
"type": "string", "type": "string",
"description": "The name of the object in the UI. If not specified, the object's path will be used." "description": "The display name in the UI. Defaults to the object's path."
}, },
"path": { "path": {
"type": "string", "type": "string",
@@ -112,11 +147,11 @@
}, },
"target_path": { "target_path": {
"type": "string", "type": "string",
"description": "Path to the target object from the project root.\nRequired if path is not specified." "description": "Path to the \"target\" or \"expected\" object (the intended result)."
}, },
"base_path": { "base_path": {
"type": "string", "type": "string",
"description": "Path to the base object from the project root.\nRequired if path is not specified." "description": "Path to the \"base\" or \"current\" object (built from current source code).\nOmit if there is no source object yet."
}, },
"reverse_fn_order": { "reverse_fn_order": {
"type": "boolean", "type": "boolean",
@@ -140,6 +175,20 @@
"additionalProperties": { "additionalProperties": {
"type": "string" "type": "string"
} }
},
"options": {
"type": "object",
"description": "Diff configuration options that should be applied when this unit is active.",
"additionalProperties": {
"oneOf": [
{
"type": "boolean"
},
{
"type": "string"
}
]
}
} }
} }
}, },
@@ -191,7 +240,7 @@
"properties": { "properties": {
"complete": { "complete": {
"type": "boolean", "type": "boolean",
"description": "Marks the object as \"complete\" (or \"linked\") in the object list.\nThis is useful for marking objects that are fully decompiled. A value of `false` will mark the object as \"incomplete\"." "description": "Marks the object as \"complete\" (linked) when `true` or \"incomplete\" when `false`."
}, },
"reverse_fn_order": { "reverse_fn_order": {
"type": "boolean", "type": "boolean",
@@ -211,7 +260,7 @@
}, },
"auto_generated": { "auto_generated": {
"type": "boolean", "type": "boolean",
"description": "Hides the object from the object list by default, but still includes it in reports." "description": "Hides the object from the sidebar but includes it in progress reports."
} }
} }
}, },
+8 -2
View File
@@ -74,6 +74,14 @@ ignore = [
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
{ id = "RUSTSEC-2024-0436", reason = "Unmaintained paste crate is an indirect dependency" }, { id = "RUSTSEC-2024-0436", reason = "Unmaintained paste crate is an indirect dependency" },
{ id = "RUSTSEC-2025-0052", reason = "Unmaintained async-std crate is an indirect dependency" },
{ id = "RUSTSEC-2025-0134", reason = "Unmaintained rustls-pemfile crate is an indirect dependency" },
{ id = "RUSTSEC-2026-0098", reason = "Build-time objdiff-wasm wit-deps dependency still uses reqwest 0.11; latest wit-deps has no safe upgrade path" },
{ id = "RUSTSEC-2026-0099", reason = "Build-time objdiff-wasm wit-deps dependency still uses reqwest 0.11; latest wit-deps has no safe upgrade path" },
{ id = "RUSTSEC-2026-0104", reason = "Build-time objdiff-wasm wit-deps dependency still uses reqwest 0.11; latest wit-deps has no safe upgrade path" },
{ id = "RUSTSEC-2026-0192", reason = "Unmaintained ttf-parser is an egui 0.33 indirect dependency; egui 0.35 requires raising MSRV from 1.88 to 1.92" },
{ id = "RUSTSEC-2026-0194", reason = "Build-time wayland-scanner dependency parses vendored protocol XML; no newer stable wayland-scanner release is available" },
{ id = "RUSTSEC-2026-0195", reason = "Build-time wayland-scanner dependency parses vendored protocol XML; no newer stable wayland-scanner release is available" },
] ]
# If this is true, then cargo deny will use the git executable to fetch advisory database. # If this is true, then cargo deny will use the git executable to fetch advisory database.
# If this is false, then it uses a built-in git library. # If this is false, then it uses a built-in git library.
@@ -241,8 +249,6 @@ allow-git = []
[sources.allow-org] [sources.allow-org]
# github.com organizations to allow git sources for # github.com organizations to allow git sources for
github = [ github = [
"enarx", # flagset
"encounter",
] ]
# gitlab.com organizations to allow git sources for # gitlab.com organizations to allow git sources for
gitlab = [] gitlab = []
+6 -6
View File
@@ -15,20 +15,20 @@ publish = false
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
argp = "0.4" argp = "0.4"
crossterm = "0.28" crossterm = "0.29"
enable-ansi-support = "0.2" enable-ansi-support = "0.3"
memmap2 = "0.9" memmap2 = "0.9"
objdiff-core = { path = "../objdiff-core", features = ["all"] } objdiff-core = { path = "../objdiff-core", features = ["all"] }
prost = "0.13" prost = "0.14"
ratatui = "0.29" ratatui = "0.30"
rayon = "1.10" rayon = "1.11"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
supports-color = "3.0" supports-color = "3.0"
time = { version = "0.3", features = ["formatting", "local-offset"] } time = { version = "0.3", features = ["formatting", "local-offset"] }
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
typed-path = "0.11" typed-path = "0.12"
[target.'cfg(target_env = "musl")'.dependencies] [target.'cfg(target_env = "musl")'.dependencies]
mimalloc = "0.1" mimalloc = "0.1"
+88 -55
View File
@@ -1,4 +1,6 @@
#![allow(unused)] // TODO
use std::{ use std::{
collections::BTreeMap,
io::stdout, io::stdout,
mem, mem,
sync::{ sync::{
@@ -25,10 +27,11 @@ use objdiff_core::{
watcher::{Watcher, create_watcher}, watcher::{Watcher, create_watcher},
}, },
config::{ config::{
ProjectConfig, ProjectObject, ProjectObjectMetadata, build_globset, ProjectConfig, ProjectObject, ProjectObjectMetadata, ProjectOptions, apply_project_options,
build_globset,
path::{check_path_buf, platform_path, platform_path_serde_option}, path::{check_path_buf, platform_path, platform_path_serde_option},
}, },
diff::{self, DiffObjConfig, MappingConfig, ObjectDiff}, diff::{self, DiffObjConfig, DiffSide, MappingConfig, ObjectDiff},
jobs::{ jobs::{
Job, JobQueue, JobResult, Job, JobQueue, JobResult,
objdiff::{ObjDiffConfig, start_build}, objdiff::{ObjDiffConfig, start_build},
@@ -75,23 +78,16 @@ pub struct Args {
#[argp(option, short = 'c')] #[argp(option, short = 'c')]
/// Configuration property (key=value) /// Configuration property (key=value)
config: Vec<String>, config: Vec<String>,
#[argp(option, short = 'm')]
/// Symbol mapping (target=base)
mapping: Vec<String>,
#[argp(option)]
/// Left symbol name for selection
selecting_left: Option<String>,
#[argp(option)]
/// Right symbol name for selection
selecting_right: Option<String>,
} }
pub fn run(args: Args) -> Result<()> { pub fn run(args: Args) -> Result<()> {
let (target_path, base_path, project_config) = let (target_path, base_path, project_config, unit_options, symbol_mappings) =
match (&args.target, &args.base, &args.project, &args.unit) { match (&args.target, &args.base, &args.project, &args.unit) {
(Some(_), Some(_), None, None) (Some(_), Some(_), None, None)
| (Some(_), None, None, None) | (Some(_), None, None, None)
| (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None), | (None, Some(_), None, None) => {
(args.target.clone(), args.base.clone(), None, None, BTreeMap::new())
}
(None, None, p, u) => { (None, None, p, u) => {
let project = match p { let project = match p {
Some(project) => project.clone(), Some(project) => project.clone(),
@@ -103,7 +99,7 @@ pub fn run(args: Args) -> Result<()> {
let Some((project_config, project_config_info)) = let Some((project_config, project_config_info)) =
objdiff_core::config::try_project_config(project.as_ref()) objdiff_core::config::try_project_config(project.as_ref())
else { else {
bail!("Project config not found in {}", &project) bail!("Project config not found in {}", project)
}; };
let project_config = project_config.with_context(|| { let project_config = project_config.with_context(|| {
format!("Reading project config {}", project_config_info.path.display()) format!("Reading project config {}", project_config_info.path.display())
@@ -116,28 +112,32 @@ pub fn run(args: Args) -> Result<()> {
.base_dir .base_dir
.as_ref() .as_ref()
.map(|p| project.join(p.with_platform_encoding())); .map(|p| project.join(p.with_platform_encoding()));
let objects = project_config let units = project_config.units.as_deref().unwrap_or_default();
.units let objects = units
.iter() .iter()
.flatten() .enumerate()
.map(|o| { .map(|(idx, o)| {
ObjectConfig::new( (
o, ObjectConfig::new(
&project, o,
target_obj_dir.as_deref(), &project,
base_obj_dir.as_deref(), target_obj_dir.as_deref(),
base_obj_dir.as_deref(),
),
idx,
) )
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let object = if let Some(u) = u { let (object, unit_idx) = if let Some(u) = u {
objects objects
.iter() .iter()
.find(|obj| obj.name == *u) .find(|(obj, _)| obj.name == *u)
.map(|(obj, idx)| (obj, *idx))
.ok_or_else(|| anyhow!("Unit not found: {}", u))? .ok_or_else(|| anyhow!("Unit not found: {}", u))?
} else if let Some(symbol_name) = &args.symbol { } else if let Some(symbol_name) = &args.symbol {
let mut idx = None; let mut idx = None;
let mut count = 0usize; let mut count = 0usize;
for (i, obj) in objects.iter().enumerate() { for (i, (obj, unit_idx)) in objects.iter().enumerate() {
if obj if obj
.target_path .target_path
.as_deref() .as_deref()
@@ -145,7 +145,7 @@ pub fn run(args: Args) -> Result<()> {
.transpose()? .transpose()?
.unwrap_or(false) .unwrap_or(false)
{ {
idx = Some(i); idx = Some((i, *unit_idx));
count += 1; count += 1;
if count > 1 { if count > 1 {
break; break;
@@ -154,7 +154,7 @@ pub fn run(args: Args) -> Result<()> {
} }
match (count, idx) { match (count, idx) {
(0, None) => bail!("Symbol not found: {}", symbol_name), (0, None) => bail!("Symbol not found: {}", symbol_name),
(1, Some(i)) => &objects[i], (1, Some((i, unit_idx))) => (&objects[i].0, unit_idx),
(2.., Some(_)) => bail!( (2.., Some(_)) => bail!(
"Multiple instances of {} were found, try specifying a unit", "Multiple instances of {} were found, try specifying a unit",
symbol_name symbol_name
@@ -164,62 +164,84 @@ pub fn run(args: Args) -> Result<()> {
} else { } else {
bail!("Must specify one of: symbol, project and unit, target and base objects") bail!("Must specify one of: symbol, project and unit, target and base objects")
}; };
let unit_options = units.get(unit_idx).and_then(|u| u.options().cloned());
let target_path = object.target_path.clone(); let target_path = object.target_path.clone();
let base_path = object.base_path.clone(); let base_path = object.base_path.clone();
(target_path, base_path, Some(project_config)) let symbol_mappings = object.symbol_mappings.clone();
(target_path, base_path, Some(project_config), unit_options, symbol_mappings)
} }
_ => bail!("Either target and base or project and unit must be specified"), _ => bail!("Either target and base or project and unit must be specified"),
}; };
if let Some(output) = &args.output { if let Some(output) = &args.output {
run_oneshot(&args, output, target_path.as_deref(), base_path.as_deref()) run_oneshot(
&args,
output,
target_path.as_deref(),
base_path.as_deref(),
project_config.as_ref(),
unit_options,
&symbol_mappings,
)
} else { } else {
run_interactive(args, target_path, base_path, project_config) run_interactive(args, target_path, base_path, project_config, unit_options, symbol_mappings)
} }
} }
fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)> {
let mut diff_config = DiffObjConfig::default();
apply_config_args(&mut diff_config, &args.config)?;
let mut mapping_config = MappingConfig {
mappings: Default::default(),
selecting_left: args.selecting_left.clone(),
selecting_right: args.selecting_right.clone(),
};
for mapping in &args.mapping {
let (target, base) =
mapping.split_once('=').context("--mapping expects \"target=base\"")?;
mapping_config.mappings.insert(target.to_string(), base.to_string());
}
Ok((diff_config, mapping_config))
}
fn run_oneshot( fn run_oneshot(
args: &Args, args: &Args,
output: &Utf8PlatformPath, output: &Utf8PlatformPath,
target_path: Option<&Utf8PlatformPath>, target_path: Option<&Utf8PlatformPath>,
base_path: Option<&Utf8PlatformPath>, base_path: Option<&Utf8PlatformPath>,
project_config: Option<&ProjectConfig>,
unit_options: Option<ProjectOptions>,
symbol_mappings: &BTreeMap<String, String>,
) -> Result<()> { ) -> Result<()> {
let output_format = OutputFormat::from_option(args.format.as_deref())?; let output_format = OutputFormat::from_option(args.format.as_deref())?;
let (diff_config, mapping_config) = build_config_from_args(args)?; let (diff_config, mapping_config) =
build_config_from_args(args, project_config, unit_options.as_ref(), symbol_mappings)?;
let target = target_path let target = target_path
.map(|p| { .map(|p| {
obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {}", p)) obj::read::read(p.as_ref(), &diff_config, DiffSide::Target)
.with_context(|| format!("Loading {p}"))
}) })
.transpose()?; .transpose()?;
let base = base_path let base = base_path
.map(|p| { .map(|p| {
obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {}", p)) obj::read::read(p.as_ref(), &diff_config, DiffSide::Base)
.with_context(|| format!("Loading {p}"))
}) })
.transpose()?; .transpose()?;
let result = let result =
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?; diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
let left = target.as_ref().and_then(|o| result.left.as_ref().map(|d| (o, d))); let left = target.as_ref().zip(result.left.as_ref());
let right = base.as_ref().and_then(|o| result.right.as_ref().map(|d| (o, d))); let right = base.as_ref().zip(result.right.as_ref());
write_output(&DiffResult::new(left, right), Some(output), output_format)?; let diff_result = DiffResult::new(left, right, &diff_config)?;
write_output(&diff_result, Some(output), output_format)?;
Ok(()) Ok(())
} }
fn build_config_from_args(
args: &Args,
project_config: Option<&ProjectConfig>,
unit_options: Option<&ProjectOptions>,
symbol_mappings: &BTreeMap<String, String>,
) -> Result<(DiffObjConfig, MappingConfig)> {
let mut diff_config = DiffObjConfig::default();
if let Some(options) = project_config.and_then(|config| config.options.as_ref()) {
apply_project_options(&mut diff_config, options)?;
}
if let Some(options) = unit_options {
apply_project_options(&mut diff_config, options)?;
}
apply_config_args(&mut diff_config, &args.config)?;
Ok((diff_config, MappingConfig {
mappings: symbol_mappings.clone(),
selecting_left: None,
selecting_right: None,
}))
}
pub struct AppState { pub struct AppState {
pub jobs: JobQueue, pub jobs: JobQueue,
pub waker: Arc<TermWaker>, pub waker: Arc<TermWaker>,
@@ -278,6 +300,7 @@ pub struct ObjectConfig {
pub base_path: Option<Utf8PlatformPathBuf>, pub base_path: Option<Utf8PlatformPathBuf>,
pub metadata: ProjectObjectMetadata, pub metadata: ProjectObjectMetadata,
pub complete: Option<bool>, pub complete: Option<bool>,
pub symbol_mappings: BTreeMap<String, String>,
} }
impl ObjectConfig { impl ObjectConfig {
@@ -307,6 +330,7 @@ impl ObjectConfig {
base_path, base_path,
metadata: object.metadata.clone().unwrap_or_default(), metadata: object.metadata.clone().unwrap_or_default(),
complete: object.complete(), complete: object.complete(),
symbol_mappings: object.symbol_mappings.clone().unwrap_or_default(),
} }
} }
} }
@@ -356,11 +380,18 @@ fn run_interactive(
target_path: Option<Utf8PlatformPathBuf>, target_path: Option<Utf8PlatformPathBuf>,
base_path: Option<Utf8PlatformPathBuf>, base_path: Option<Utf8PlatformPathBuf>,
project_config: Option<ProjectConfig>, project_config: Option<ProjectConfig>,
unit_options: Option<ProjectOptions>,
symbol_mappings: BTreeMap<String, String>,
) -> Result<()> { ) -> Result<()> {
let Some(symbol_name) = &args.symbol else { bail!("Interactive mode requires a symbol name") }; let Some(symbol_name) = &args.symbol else { bail!("Interactive mode requires a symbol name") };
let time_format = time::format_description::parse_borrowed::<2>("[hour]:[minute]:[second]") let time_format = time::format_description::parse_borrowed::<2>("[hour]:[minute]:[second]")
.context("Failed to parse time format")?; .context("Failed to parse time format")?;
let (diff_obj_config, mapping_config) = build_config_from_args(&args)?; let (diff_obj_config, mapping_config) = build_config_from_args(
&args,
project_config.as_ref(),
unit_options.as_ref(),
&symbol_mappings,
)?;
let mut state = AppState { let mut state = AppState {
jobs: Default::default(), jobs: Default::default(),
waker: Default::default(), waker: Default::default(),
@@ -382,10 +413,12 @@ fn run_interactive(
}; };
if let (Some(project_dir), Some(project_config)) = (&state.project_dir, &state.project_config) { if let (Some(project_dir), Some(project_config)) = (&state.project_dir, &state.project_config) {
let watch_patterns = project_config.build_watch_patterns()?; let watch_patterns = project_config.build_watch_patterns()?;
let ignore_patterns = project_config.build_ignore_patterns()?;
state.watcher = Some(create_watcher( state.watcher = Some(create_watcher(
state.modified.clone(), state.modified.clone(),
project_dir.as_ref(), project_dir.as_ref(),
build_globset(&watch_patterns)?, build_globset(&watch_patterns)?,
build_globset(&ignore_patterns)?,
Waker::from(state.waker.clone()), Waker::from(state.waker.clone()),
)?); )?);
} }
@@ -399,7 +432,7 @@ fn run_interactive(
stdout(), stdout(),
EnterAlternateScreen, EnterAlternateScreen,
EnableMouseCapture, EnableMouseCapture,
SetTitle(format!("{} - objdiff", symbol_name)), SetTitle(format!("{symbol_name} - objdiff")),
)?; )?;
let backend = CrosstermBackend::new(stdout()); let backend = CrosstermBackend::new(stdout());
let mut terminal = Terminal::new(backend)?; let mut terminal = Terminal::new(backend)?;
+74 -32
View File
@@ -7,9 +7,9 @@ use objdiff_core::{
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, REPORT_VERSION, ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, REPORT_VERSION,
Report, ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata, Report, ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
}, },
config::path::platform_path, config::{ProjectObject, ProjectOptions, apply_project_options, path::platform_path},
diff, obj, diff,
obj::{SectionKind, SymbolFlag}, obj::{self, SectionKind, SymbolFlag, SymbolKind},
}; };
use prost::Message; use prost::Message;
use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
@@ -83,14 +83,13 @@ pub fn run(args: Args) -> Result<()> {
} }
fn generate(args: GenerateArgs) -> Result<()> { fn generate(args: GenerateArgs) -> Result<()> {
let mut diff_config = diff::DiffObjConfig { let base_diff_config = diff::DiffObjConfig {
function_reloc_diffs: diff::FunctionRelocDiffs::None, function_reloc_diffs: diff::FunctionRelocDiffs::None,
combine_data_sections: true, combine_data_sections: true,
combine_text_sections: true, combine_text_sections: true,
ppc_calculate_pool_relocations: false, ppc_calculate_pool_relocations: false,
..Default::default() ..Default::default()
}; };
apply_config_args(&mut diff_config, &args.config)?;
let output_format = OutputFormat::from_option(args.format.as_deref())?; let output_format = OutputFormat::from_option(args.format.as_deref())?;
let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new(".")); let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new("."));
@@ -101,31 +100,44 @@ fn generate(args: GenerateArgs) -> Result<()> {
Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err), Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err),
None => bail!("No project configuration found"), None => bail!("No project configuration found"),
}; };
info!(
"Generating report for {} units (using {} threads)",
project.units().len(),
if args.deduplicate { 1 } else { rayon::current_num_threads() }
);
let target_obj_dir = let target_obj_dir =
project.target_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding())); project.target_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
let base_obj_dir = let base_obj_dir =
project.base_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding())); project.base_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
let objects = project let project_units = project.units.as_deref().unwrap_or_default();
.units let objects = project_units
.iter() .iter()
.flatten() .enumerate()
.map(|o| { .map(|(idx, o)| {
ObjectConfig::new(o, project_dir, target_obj_dir.as_deref(), base_obj_dir.as_deref()) (
ObjectConfig::new(
o,
project_dir,
target_obj_dir.as_deref(),
base_obj_dir.as_deref(),
),
idx,
)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
info!(
"Generating report for {} units (using {} threads)",
objects.len(),
if args.deduplicate { 1 } else { rayon::current_num_threads() }
);
let start = Instant::now(); let start = Instant::now();
let mut units = vec![]; let mut units = vec![];
let mut existing_functions: HashSet<String> = HashSet::new(); let mut existing_functions: HashSet<String> = HashSet::new();
if args.deduplicate { if args.deduplicate {
// If deduplicating, we need to run single-threaded // If deduplicating, we need to run single-threaded
for object in &objects { for (object, unit_idx) in &objects {
let diff_config = build_unit_diff_config(
&base_diff_config,
project.options.as_ref(),
project_units.get(*unit_idx).and_then(ProjectObject::options),
&args.config,
)?;
if let Some(unit) = report_object(object, &diff_config, Some(&mut existing_functions))? if let Some(unit) = report_object(object, &diff_config, Some(&mut existing_functions))?
{ {
units.push(unit); units.push(unit);
@@ -134,7 +146,15 @@ fn generate(args: GenerateArgs) -> Result<()> {
} else { } else {
let vec = objects let vec = objects
.par_iter() .par_iter()
.map(|object| report_object(object, &diff_config, None)) .map(|(object, unit_idx)| {
let diff_config = build_unit_diff_config(
&base_diff_config,
project.options.as_ref(),
project_units.get(*unit_idx).and_then(ProjectObject::options),
&args.config,
)?;
report_object(object, &diff_config, None)
})
.collect::<Result<Vec<Option<ReportUnit>>>>()?; .collect::<Result<Vec<Option<ReportUnit>>>>()?;
units = vec.into_iter().flatten().collect(); units = vec.into_iter().flatten().collect();
} }
@@ -156,6 +176,24 @@ fn generate(args: GenerateArgs) -> Result<()> {
Ok(()) Ok(())
} }
fn build_unit_diff_config(
base: &diff::DiffObjConfig,
project_options: Option<&ProjectOptions>,
unit_options: Option<&ProjectOptions>,
cli_args: &[String],
) -> Result<diff::DiffObjConfig> {
let mut diff_config = base.clone();
if let Some(options) = project_options {
apply_project_options(&mut diff_config, options)?;
}
if let Some(options) = unit_options {
apply_project_options(&mut diff_config, options)?;
}
// CLI args override project and unit options
apply_config_args(&mut diff_config, cli_args)?;
Ok(diff_config)
}
fn report_object( fn report_object(
object: &ObjectConfig, object: &ObjectConfig,
diff_config: &diff::DiffObjConfig, diff_config: &diff::DiffObjConfig,
@@ -172,21 +210,25 @@ fn report_object(
} }
_ => {} _ => {}
} }
let mapping_config = diff::MappingConfig::default(); let mapping_config = diff::MappingConfig {
mappings: object.symbol_mappings.clone(),
selecting_left: None,
selecting_right: None,
};
let target = object let target = object
.target_path .target_path
.as_ref() .as_ref()
.map(|p| { .map(|p| {
obj::read::read(p.as_ref(), diff_config) obj::read::read(p.as_ref(), diff_config, diff::DiffSide::Target)
.with_context(|| format!("Failed to open {}", p)) .with_context(|| format!("Failed to open {p}"))
}) })
.transpose()?; .transpose()?;
let base = object let base = object
.base_path .base_path
.as_ref() .as_ref()
.map(|p| { .map(|p| {
obj::read::read(p.as_ref(), diff_config) obj::read::read(p.as_ref(), diff_config, diff::DiffSide::Base)
.with_context(|| format!("Failed to open {}", p)) .with_context(|| format!("Failed to open {p}"))
}) })
.transpose()?; .transpose()?;
let result = let result =
@@ -247,16 +289,16 @@ fn report_object(
|| symbol.size == 0 || symbol.size == 0
|| symbol.flags.contains(SymbolFlag::Hidden) || symbol.flags.contains(SymbolFlag::Hidden)
|| symbol.flags.contains(SymbolFlag::Ignored) || symbol.flags.contains(SymbolFlag::Ignored)
|| symbol.kind == SymbolKind::Section
{ {
continue; continue;
} }
if let Some(existing_functions) = &mut existing_functions { if let Some(existing_functions) = &mut existing_functions
if (symbol.flags.contains(SymbolFlag::Global) && (symbol.flags.contains(SymbolFlag::Global)
|| symbol.flags.contains(SymbolFlag::Weak)) || symbol.flags.contains(SymbolFlag::Weak))
&& !existing_functions.insert(symbol.name.clone()) && !existing_functions.insert(symbol.name.clone())
{ {
continue; continue;
}
} }
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| { let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
// Support cases where we don't have a target object, // Support cases where we don't have a target object,
@@ -433,8 +475,8 @@ fn read_report(path: &Utf8PlatformPath) -> Result<Report> {
std::io::stdin().read_to_end(&mut data)?; std::io::stdin().read_to_end(&mut data)?;
return Report::parse(&data).with_context(|| "Failed to load report from stdin"); return Report::parse(&data).with_context(|| "Failed to load report from stdin");
} }
let file = File::open(path).with_context(|| format!("Failed to open {}", path))?; let file = File::open(path).with_context(|| format!("Failed to open {path}"))?;
let mmap = let mmap =
unsafe { memmap2::Mmap::map(&file) }.with_context(|| format!("Failed to map {}", path))?; unsafe { memmap2::Mmap::map(&file) }.with_context(|| format!("Failed to map {path}"))?;
Report::parse(mmap.as_ref()).with_context(|| format!("Failed to load report {}", path)) Report::parse(mmap.as_ref()).with_context(|| format!("Failed to load report {path}"))
} }
+35 -34
View File
@@ -1,3 +1,4 @@
#![allow(unused)] // TODO
use core::cmp::Ordering; use core::cmp::Ordering;
use anyhow::Result; use anyhow::Result;
@@ -87,7 +88,7 @@ impl UiView for FunctionDiffUi {
.and_then(|(_, _, d)| d.match_percent) .and_then(|(_, _, d)| d.match_percent)
{ {
line_r.spans.push(Span::styled( line_r.spans.push(Span::styled(
format!("{:.2}% ", percent), format!("{percent:.2}% "),
Style::new().fg(match_percent_color(percent)), Style::new().fg(match_percent_color(percent)),
)); ));
} }
@@ -97,7 +98,7 @@ impl UiView for FunctionDiffUi {
.and_then(|t| t.format(&state.time_format).ok()) .and_then(|t| t.format(&state.time_format).ok())
.unwrap_or_else(|| "N/A".to_string()); .unwrap_or_else(|| "N/A".to_string());
line_r.spans.push(Span::styled( line_r.spans.push(Span::styled(
format!("Last reload: {}", reload_time), format!("Last reload: {reload_time}"),
Style::new().fg(Color::White), Style::new().fg(Color::White),
)); ));
line_r.spans.push(Span::styled( line_r.spans.push(Span::styled(
@@ -170,32 +171,31 @@ impl UiView for FunctionDiffUi {
let mut prev_text = None; let mut prev_text = None;
let mut prev_margin_text = None; let mut prev_margin_text = None;
if self.three_way { if self.three_way
if let Some((obj, symbol_idx, symbol_diff)) = && let Some((obj, symbol_idx, symbol_diff)) =
get_symbol(state.prev_obj.as_ref(), self.prev_sym) get_symbol(state.prev_obj.as_ref(), self.prev_sym)
{ {
let mut text = Text::default(); let mut text = Text::default();
let rect = content_chunks[4].inner(Margin::new(0, 1)); let rect = content_chunks[4].inner(Margin::new(0, 1));
self.print_sym( self.print_sym(
&mut text, &mut text,
obj, obj,
symbol_idx, symbol_idx,
symbol_diff, symbol_diff,
&state.diff_obj_config, &state.diff_obj_config,
rect, rect,
&self.right_highlight, &self.right_highlight,
result, result,
true, true,
); );
max_width = max_width.max(text.width()); max_width = max_width.max(text.width());
prev_text = Some(text); prev_text = Some(text);
// Render margin // Render margin
let mut text = Text::default(); let mut text = Text::default();
let rect = content_chunks[3].inner(Margin::new(1, 1)); let rect = content_chunks[3].inner(Margin::new(1, 1));
self.print_margin(&mut text, symbol_diff, rect); self.print_margin(&mut text, symbol_diff, rect);
prev_margin_text = Some(text); prev_margin_text = Some(text);
}
} }
let max_scroll_x = let max_scroll_x =
@@ -379,7 +379,6 @@ impl UiView for FunctionDiffUi {
} }
// Reload // Reload
KeyCode::Char('r') => { KeyCode::Char('r') => {
result.redraw = true;
return EventControlFlow::Reload; return EventControlFlow::Reload;
} }
// Scroll right // Scroll right
@@ -401,7 +400,6 @@ impl UiView for FunctionDiffUi {
FunctionRelocDiffs::DataValue => FunctionRelocDiffs::All, FunctionRelocDiffs::DataValue => FunctionRelocDiffs::All,
FunctionRelocDiffs::All => FunctionRelocDiffs::None, FunctionRelocDiffs::All => FunctionRelocDiffs::None,
}; };
result.redraw = true;
return EventControlFlow::Reload; return EventControlFlow::Reload;
} }
// Toggle three-way diff // Toggle three-way diff
@@ -538,15 +536,16 @@ impl FunctionDiffUi {
let label_text = match segment.text { let label_text = match segment.text {
DiffText::Basic(text) => text.to_string(), DiffText::Basic(text) => text.to_string(),
DiffText::Line(num) => format!("{num} "), DiffText::Line(num) => format!("{num} "),
DiffText::Address(addr) => format!("{:x}:", addr), DiffText::Address(addr) => format!("{addr:x}:"),
DiffText::Opcode(mnemonic, _op) => format!("{mnemonic} "), DiffText::Opcode(mnemonic, _op) => format!("{mnemonic} "),
DiffText::Argument(arg) => arg.to_string(), DiffText::Argument(arg) => arg.to_string(),
DiffText::BranchDest(addr) => format!("{addr:x}"), DiffText::BranchDest(addr) => format!("{addr:x}"),
DiffText::BranchArrow(_) => " ~> ".to_string(),
DiffText::Symbol(sym) => { DiffText::Symbol(sym) => {
sym.demangled_name.as_ref().unwrap_or(&sym.name).clone() sym.demangled_name.as_ref().unwrap_or(&sym.name).clone()
} }
DiffText::Addend(addend) => match addend.cmp(&0i64) { DiffText::Addend(addend) => match addend.cmp(&0i64) {
Ordering::Greater => format!("+{:#x}", addend), Ordering::Greater => format!("+{addend:#x}"),
Ordering::Less => format!("-{:#x}", -addend), Ordering::Less => format!("-{:#x}", -addend),
_ => String::new(), _ => String::new(),
}, },
@@ -561,10 +560,12 @@ impl FunctionDiffUi {
let len = label_text.len(); let len = label_text.len();
let highlighted = let highlighted =
highlight_kind != HighlightKind::None && *highlight == highlight_kind; highlight_kind != HighlightKind::None && *highlight == highlight_kind;
if let Some((cx, cy)) = result.click_xy { if let Some((cx, cy)) = result.click_xy
if cx >= sx && cx < sx + len as u16 && cy == sy { && cx >= sx
new_highlight = Some(highlight_kind); && cx < sx + len as u16
} && cy == sy
{
new_highlight = Some(highlight_kind);
} }
let mut style = Style::new().fg(match segment.color { let mut style = Style::new().fg(match segment.color {
DiffTextColor::Normal => Color::Gray, DiffTextColor::Normal => Color::Gray,
+1
View File
@@ -1,3 +1,4 @@
#![allow(unused)] // TODO
use anyhow::Result; use anyhow::Result;
use crossterm::event::Event; use crossterm::event::Event;
use ratatui::Frame; use ratatui::Frame;
+43 -35
View File
@@ -41,7 +41,8 @@ any-arch = [
"dep:regex", "dep:regex",
"dep:similar", "dep:similar",
"dep:syn", "dep:syn",
"dep:encoding_rs" "dep:encoding_rs",
"demangler",
] ]
bindings = [ bindings = [
"dep:prost", "dep:prost",
@@ -62,7 +63,10 @@ config = [
"dep:semver", "dep:semver",
"dep:typed-path", "dep:typed-path",
] ]
dwarf = ["dep:gimli"] dwarf = [
"dep:gimli",
"dep:typed-arena",
]
serde = [ serde = [
"dep:pbjson", "dep:pbjson",
"dep:pbjson-build", "dep:pbjson-build",
@@ -78,44 +82,44 @@ std = [
"prost?/std", "prost?/std",
"serde?/std", "serde?/std",
"similar?/std", "similar?/std",
"typed-arena?/std",
"typed-path?/std", "typed-path?/std",
"dep:filetime", "dep:filetime",
"dep:memmap2", "dep:memmap2",
] ]
mips = [ mips = [
"any-arch", "any-arch",
"dep:cpp_demangle",
"dep:cwdemangle",
"dep:rabbitizer", "dep:rabbitizer",
] ]
ppc = [ ppc = [
"any-arch", "any-arch",
"dep:cwdemangle",
"dep:cwextab", "dep:cwextab",
"dep:ppc750cl", "dep:powerpc",
"dep:rlwinmdec", "dep:rlwinmdec",
] ]
x86 = [ x86 = [
"any-arch", "any-arch",
"dep:cpp_demangle",
"dep:iced-x86", "dep:iced-x86",
"dep:msvc-demangler",
] ]
arm = [ arm = [
"any-arch", "any-arch",
"dep:arm-attr", "dep:arm-attr",
"dep:cpp_demangle",
"dep:unarm", "dep:unarm",
] ]
arm64 = [ arm64 = [
"any-arch", "any-arch",
"dep:cpp_demangle",
"dep:yaxpeax-arch", "dep:yaxpeax-arch",
"dep:yaxpeax-arm", "dep:yaxpeax-arm",
] ]
superh = [ superh = [
"any-arch", "any-arch",
] ]
demangler = [
"dep:cpp_demangle",
"dep:cwdemangle",
"dep:gnuv2_demangle",
"dep:msvc-demangler",
]
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["all"] features = ["all"]
@@ -123,18 +127,18 @@ features = ["all"]
[dependencies] [dependencies]
anyhow = { version = "1.0", default-features = false } anyhow = { version = "1.0", default-features = false }
filetime = { version = "0.2", optional = true } filetime = { version = "0.2", optional = true }
flagset = { version = "0.4", default-features = false, optional = true, git = "https://github.com/enarx/flagset.git", rev = "a1fe9369b3741e43fec45da1998e83b9d78966a2" } flagset = { version = "0.4", default-features = false, optional = true }
itertools = { version = "0.14", default-features = false, features = ["use_alloc"] } itertools = { version = "0.15", default-features = false, features = ["use_alloc"] }
log = { version = "0.4", default-features = false, optional = true } log = { version = "0.4", default-features = false, optional = true }
memmap2 = { version = "0.9", optional = true } memmap2 = { version = "0.9", optional = true }
num-traits = { version = "0.2", default-features = false, optional = true } num-traits = { version = "0.2", default-features = false, optional = true }
object = { git = "https://github.com/gimli-rs/object", rev = "a74579249e21ab8fcd3a86be588de336f18297cb", default-features = false, features = ["read_core", "elf", "pe"] } object = { version = "0.39.1", default-features = false, features = ["read_core", "elf", "coff"] }
pbjson = { version = "0.7", default-features = false, optional = true } pbjson = { version = "0.9", default-features = false, optional = true }
prost = { version = "0.13", default-features = false, features = ["prost-derive"], optional = true } prost = { version = "0.14", default-features = false, features = ["derive"], optional = true }
regex = { version = "1.11", default-features = false, features = [], optional = true } regex = { version = "1.12", default-features = false, features = [], optional = true }
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
similar = { version = "2.7", default-features = false, features = ["hashbrown"], optional = true, git = "https://github.com/encounter/similar.git", branch = "no_std" } similar = { version = "3.1", default-features = false, features = ["hashbrown"], optional = true }
typed-path = { version = "0.11", default-features = false, optional = true } typed-path = { version = "0.12", default-features = false, optional = true }
# config # config
globset = { version = "0.4", default-features = false, optional = true } globset = { version = "0.4", default-features = false, optional = true }
@@ -142,24 +146,22 @@ semver = { version = "1.0", default-features = false, optional = true }
serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true } serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true }
# dwarf # dwarf
gimli = { version = "0.31", default-features = false, features = ["read"], optional = true } gimli = { version = "0.34.0", default-features = false, features = ["read"], optional = true }
typed-arena = { version = "2.0", default-features = false, optional = true }
# ppc # ppc
cwdemangle = { version = "1.0", optional = true } cwextab = { version = "1.1", optional = true }
cwextab = { version = "1.0", optional = true } powerpc = { version = "0.4", optional = true }
ppc750cl = { version = "0.3", optional = true }
rlwinmdec = { version = "1.1", optional = true } rlwinmdec = { version = "1.1", optional = true }
# mips # mips
rabbitizer = { version = "2.0.0-alpha.1", default-features = false, features = ["all_extensions"], optional = true } rabbitizer = { version = "2.0.0-alpha.9", default-features = false, features = ["all_extensions"], optional = true }
# x86 # x86
cpp_demangle = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true } iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true }
msvc-demangler = { version = "0.11", optional = true }
# arm # arm
unarm = { version = "1.8", optional = true } unarm = { version = "2.1", optional = true }
arm-attr = { version = "0.2", optional = true } arm-attr = { version = "0.2", optional = true }
# arm64 # arm64
@@ -167,32 +169,38 @@ yaxpeax-arch = { version = "0.3", default-features = false, optional = true }
yaxpeax-arm = { version = "0.3", default-features = false, optional = true } yaxpeax-arm = { version = "0.3", default-features = false, optional = true }
# build # build
notify = { version = "8.0.0", optional = true } notify = { version = "8.2.0", optional = true }
notify-debouncer-full = { version = "0.5.0", optional = true } notify-debouncer-full = { version = "0.6.0", optional = true }
shell-escape = { version = "0.1", optional = true } shell-escape = { version = "0.1", optional = true }
tempfile = { version = "3.19", optional = true } tempfile = { version = "3.23", optional = true }
time = { version = "0.3", optional = true } time = { version = "0.3", optional = true }
encoding_rs = { version = "0.8.35", optional = true } encoding_rs = { version = "0.8.35", optional = true }
# demangler
cpp_demangle = { version = "0.5", optional = true, default-features = false, features = ["alloc"] }
cwdemangle = { version = "1.0", optional = true }
gnuv2_demangle = { version = "0.4", optional = true }
msvc-demangler = { version = "0.11", optional = true }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", optional = true, features = ["winbase"] } winapi = { version = "0.3", optional = true, features = ["winbase"] }
# For Linux static binaries, use rustls # For Linux static binaries, use rustls
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "rustls-tls"], optional = true } reqwest = { version = "0.13", default-features = false, features = ["blocking", "json", "multipart", "rustls"], optional = true }
self_update = { version = "0.42", default-features = false, features = ["rustls"], optional = true } self_update = { version = "1.0.0-rc.3", default-features = false, features = ["reqwest", "rustls", "github"], optional = true }
# For all other platforms, use native TLS # For all other platforms, use native TLS
[target.'cfg(not(target_os = "linux"))'.dependencies] [target.'cfg(not(target_os = "linux"))'.dependencies]
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "default-tls"], optional = true } reqwest = { version = "0.13", default-features = false, features = ["blocking", "json", "multipart", "default-tls"], optional = true }
self_update = { version = "0.42", optional = true } self_update = { version = "1.0.0-rc.3", default-features = false, features = ["reqwest", "native-tls", "github"], optional = true }
[build-dependencies] [build-dependencies]
heck = { version = "0.5", optional = true } heck = { version = "0.5", optional = true }
pbjson-build = { version = "0.7", optional = true } pbjson-build = { version = "0.9", optional = true }
prettyplease = { version = "0.2", optional = true } prettyplease = { version = "0.2", optional = true }
proc-macro2 = { version = "1.0", optional = true } proc-macro2 = { version = "1.0", optional = true }
prost-build = { version = "0.13", optional = true } prost-build = { version = "0.14", optional = true }
quote = { version = "1.0", optional = true } quote = { version = "1.0", optional = true }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" } serde_json = { version = "1.0" }
+1 -1
View File
@@ -11,6 +11,6 @@ objdiff-core contains the core functionality of [objdiff](https://github.com/enc
- **`arm64`**: Enables the ARM64 backend powered by [yaxpeax-arm](https://github.com/iximeow/yaxpeax-arm). - **`arm64`**: Enables the ARM64 backend powered by [yaxpeax-arm](https://github.com/iximeow/yaxpeax-arm).
- **`arm`**: Enables the ARM backend powered by [unarm](https://github.com/AetiasHax/unarm). - **`arm`**: Enables the ARM backend powered by [unarm](https://github.com/AetiasHax/unarm).
- **`mips`**: Enables the MIPS backend powered by [rabbitizer](https://github.com/Decompollaborate/rabbitizer). - **`mips`**: Enables the MIPS backend powered by [rabbitizer](https://github.com/Decompollaborate/rabbitizer).
- **`ppc`**: Enables the PowerPC backend powered by [ppc750cl](https://github.com/encounter/ppc750cl). - **`ppc`**: Enables the PowerPC backend powered by [powerpc](https://github.com/encounter/powerpc-rs).
- **`superh`**: Enables the SuperH backend powered by an included disassembler. - **`superh`**: Enables the SuperH backend powered by an included disassembler.
- **`x86`**: Enables the x86 backend powered by [iced-x86](https://crates.io/crates/iced-x86). - **`x86`**: Enables the x86 backend powered by [iced-x86](https://crates.io/crates/iced-x86).
+151 -12
View File
@@ -25,6 +25,42 @@
} }
] ]
}, },
{
"id": "demangler",
"type": "choice",
"default": "auto",
"name": "Demangler",
"description": "Which demangler should be used to demangle each symbol.",
"items": [
{
"value": "auto",
"name": "Auto",
"description": "Try to automatically guess the mangling format."
},
{
"value": "none",
"name": "None",
"description": "Disable demangling."
},
{
"value": "codewarrior",
"name": "CodeWarrior"
},
{
"value": "itanium",
"name": "Itanium"
},
{
"value": "msvc",
"name": "MSVC"
},
{
"value": "gnu_legacy",
"name": "GNU g++ (Legacy)",
"description": "Use the old GNU mangling ABI. Used up to g++ 2.9.x"
}
]
},
{ {
"id": "analyzeDataFlow", "id": "analyzeDataFlow",
"type": "boolean", "type": "boolean",
@@ -46,6 +82,27 @@
"name": "Space between args", "name": "Space between args",
"description": "Adds a space between arguments in the diff output." "description": "Adds a space between arguments in the diff output."
}, },
{
"id": "showSymbolSizes",
"type": "choice",
"default": "off",
"name": "Show symbol sizes",
"description": "Shows symbol sizes in the symbol view.",
"items": [
{
"value": "off",
"name": "Off"
},
{
"value": "hex",
"name": "Hex"
},
{
"value": "decimal",
"name": "Decimal"
}
]
},
{ {
"id": "combineDataSections", "id": "combineDataSections",
"type": "boolean", "type": "boolean",
@@ -60,6 +117,51 @@
"name": "Combine text sections", "name": "Combine text sections",
"description": "Combines all text sections into one." "description": "Combines all text sections into one."
}, },
{
"id": "preferredStringEncoding",
"type": "choice",
"default": "auto",
"name": "Preferred string encoding",
"description": "Which encoding to use when diffing string literals used in functions.",
"items": [
{
"value": "auto",
"name": "Auto"
},
{
"value": "ascii",
"name": "ASCII"
},
{
"value": "utf_8",
"name": "UTF-8"
},
{
"value": "shift_jis",
"name": "Shift JIS"
},
{
"value": "windows_1252",
"name": "Windows-1252"
},
{
"value": "euc_jp",
"name": "EUC-JP"
},
{
"value": "big5",
"name": "Big5"
},
{
"value": "utf_16be",
"name": "UTF-16BE"
},
{
"value": "utf_16le",
"name": "UTF-16LE"
}
]
},
{ {
"id": "arm.archVersion", "id": "arm.archVersion",
"type": "choice", "type": "choice",
@@ -71,20 +173,43 @@
"value": "auto", "value": "auto",
"name": "Auto" "name": "Auto"
}, },
{
"value": "v4",
"name": "ARMv4"
},
{ {
"value": "v4t", "value": "v4t",
"name": "ARMv4T (GBA)" "name": "ARMv4T (GBA)"
}, },
{
"value": "v5t",
"name": "ARMv5T"
},
{ {
"value": "v5te", "value": "v5te",
"name": "ARMv5TE (DS)" "name": "ARMv5TE (DS)"
}, },
{
"value": "v5tej",
"name": "ARMv5TEJ"
},
{
"value": "v6",
"name": "ARMv6"
},
{ {
"value": "v6k", "value": "v6k",
"name": "ARMv6K (3DS)" "name": "ARMv6K (3DS)"
} }
] ]
}, },
{
"id": "arm.vfpV2",
"type": "boolean",
"default": true,
"name": "VFPv2 instructions",
"description": "Adds floating-point instructions from VFPv2."
},
{ {
"id": "arm.unifiedSyntax", "id": "arm.unifiedSyntax",
"type": "boolean", "type": "boolean",
@@ -158,6 +283,10 @@
"value": "o32", "value": "o32",
"name": "O32" "name": "O32"
}, },
{
"value": "o64",
"name": "O64"
},
{ {
"value": "n32", "value": "n32",
"name": "N32" "name": "N32"
@@ -165,6 +294,14 @@
{ {
"value": "n64", "value": "n64",
"name": "N64" "name": "N64"
},
{
"value": "eabi32",
"name": "eabi32"
},
{
"value": "eabi64",
"name": "eabi64"
} }
] ]
}, },
@@ -208,6 +345,13 @@
"name": "Register '$' prefix", "name": "Register '$' prefix",
"description": "Display MIPS register names with a '$' prefix." "description": "Display MIPS register names with a '$' prefix."
}, },
{
"id": "mips.prodgSnAsInvertedRegs",
"type": "boolean",
"default": false,
"name": "ProDG SN AS VU register order",
"description": "Changes the register order for specific R5900EE instructions to match the syntax used by the SN AS from ProDG (-snas flag)."
},
{ {
"id": "ppc.calculatePoolRelocations", "id": "ppc.calculatePoolRelocations",
"type": "boolean", "type": "boolean",
@@ -246,7 +390,10 @@
"id": "general", "id": "general",
"name": "General", "name": "General",
"properties": [ "properties": [
"preferredStringEncoding",
"functionRelocDiffs", "functionRelocDiffs",
"demangler",
"showSymbolSizes",
"spaceBetweenArgs", "spaceBetweenArgs",
"combineDataSections", "combineDataSections",
"combineTextSections" "combineTextSections"
@@ -257,6 +404,7 @@
"name": "ARM", "name": "ARM",
"properties": [ "properties": [
"arm.archVersion", "arm.archVersion",
"arm.vfpV2",
"arm.unifiedSyntax", "arm.unifiedSyntax",
"arm.avRegisters", "arm.avRegisters",
"arm.r9Usage", "arm.r9Usage",
@@ -268,26 +416,17 @@
{ {
"id": "mips", "id": "mips",
"name": "MIPS", "name": "MIPS",
"properties": [ "properties": ["mips.abi", "mips.instrCategory", "mips.registerPrefix", "mips.prodgSnAsInvertedRegs"]
"mips.abi",
"mips.instrCategory",
"mips.registerPrefix"
]
}, },
{ {
"id": "ppc", "id": "ppc",
"name": "PowerPC", "name": "PowerPC",
"properties": [ "properties": ["ppc.calculatePoolRelocations", "analyzeDataFlow"]
"ppc.calculatePoolRelocations",
"analyzeDataFlow"
]
}, },
{ {
"id": "x86", "id": "x86",
"name": "x86", "name": "x86",
"properties": [ "properties": ["x86.formatter"]
"x86.formatter"
]
} }
] ]
} }
+5 -5
View File
@@ -60,10 +60,10 @@ pub struct ConfigGroup {
} }
fn build_doc(name: &str, description: Option<&str>) -> TokenStream { fn build_doc(name: &str, description: Option<&str>) -> TokenStream {
let mut doc = format!(" {}", name); let mut doc = format!(" {name}");
let mut out = quote! { #[doc = #doc] }; let mut out = quote! { #[doc = #doc] };
if let Some(description) = description { if let Some(description) = description {
doc = format!(" {}", description); doc = format!(" {description}");
out.extend(quote! { #[doc = ""] }); out.extend(quote! { #[doc = ""] });
out.extend(quote! { #[doc = #doc] }); out.extend(quote! { #[doc = #doc] });
} }
@@ -443,9 +443,9 @@ pub fn generate_diff_config() {
} }
impl core::fmt::Display for ConfigPropertyValue { impl core::fmt::Display for ConfigPropertyValue {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self { match *self {
ConfigPropertyValue::Boolean(value) => write!(f, "{}", value), ConfigPropertyValue::Boolean(value) => write!(f, "{value}"),
ConfigPropertyValue::Choice(value) => write!(f, "{}", value), ConfigPropertyValue::Choice(value) => f.write_str(value),
} }
} }
} }
+127 -121
View File
@@ -2,107 +2,148 @@ syntax = "proto3";
package objdiff.diff; package objdiff.diff;
// A symbol // Top-level diff result containing left and right object diffs
message Symbol { message DiffResult {
// Name of the symbol optional DiffObject left = 1;
optional DiffObject right = 2;
}
// Diff information for a single object file
message DiffObject {
repeated DiffSection sections = 1;
repeated DiffSymbol symbols = 2;
}
// Diff information for a section
message DiffSection {
string name = 1;
DiffSectionKind kind = 2;
uint64 size = 3;
uint64 address = 4;
optional float match_percent = 5;
repeated DiffDataSegment data_diff = 6;
repeated DiffDataRelocation reloc_diff = 7;
}
enum DiffSectionKind {
SECTION_UNKNOWN = 0;
SECTION_CODE = 1;
SECTION_DATA = 2;
SECTION_BSS = 3;
SECTION_COMMON = 4;
}
// Symbol with diff information
message DiffSymbol {
string name = 1; string name = 1;
// Demangled name of the symbol
optional string demangled_name = 2; optional string demangled_name = 2;
// Symbol address
uint64 address = 3; uint64 address = 3;
// Symbol size
uint64 size = 4; uint64 size = 4;
// Bitmask of SymbolFlag DiffSymbolFlags flags = 5;
uint32 flags = 5; DiffSymbolKind kind = 10;
// The symbol index in the _other_ object that this symbol was diffed against
optional uint32 target_symbol = 6;
optional float match_percent = 7;
// Instruction diff rows (for code symbols)
repeated DiffInstructionRow instructions = 8;
// Data diff (for data symbols)
repeated DiffDataSegment data_diff = 9;
} }
// Symbol visibility flags enum DiffSymbolKind {
enum SymbolFlag { SYMBOL_UNKNOWN = 0;
SYMBOL_NONE = 0; SYMBOL_FUNCTION = 1;
SYMBOL_GLOBAL = 1; SYMBOL_OBJECT = 2;
SYMBOL_LOCAL = 2; SYMBOL_SECTION = 3;
SYMBOL_WEAK = 4;
SYMBOL_COMMON = 8;
SYMBOL_HIDDEN = 16;
} }
// A single parsed instruction // Symbol flags
message Instruction { message DiffSymbolFlags {
// Instruction address bool global = 1;
bool local = 2;
bool weak = 3;
bool common = 4;
bool hidden = 5;
bool ignored = 6;
bool size_inferred = 7;
}
// A single instruction diff row
message DiffInstructionRow {
DiffKind diff_kind = 1;
optional DiffInstruction instruction = 2;
repeated DiffInstructionArgDiff arg_diff = 3;
}
// Parsed instruction information
message DiffInstruction {
uint64 address = 1; uint64 address = 1;
// Instruction size
uint32 size = 2; uint32 size = 2;
// Instruction opcode // Formatted instruction string
uint32 opcode = 3; string formatted = 3;
// Instruction mnemonic // Formatted instruction parts
string mnemonic = 4; repeated DiffInstructionPart parts = 4;
// Instruction formatted string // Relocation information (if present)
string formatted = 5; optional DiffRelocation relocation = 5;
// Original (unsimplified) instruction string // Branch destination address
optional string original = 6; optional uint64 branch_dest = 6;
// Instruction arguments // Source line number (if present)
repeated Argument arguments = 7; optional uint32 line_number = 7;
// Instruction relocation }
optional Relocation relocation = 8;
// Instruction branch destination // An instruction part
optional uint64 branch_dest = 9; message DiffInstructionPart {
// Instruction line number oneof part {
optional uint32 line_number = 10; // Basic text (whitespace, punctuation, etc.)
string basic = 1;
// Opcode/mnemonic
DiffOpcode opcode = 2;
// Argument
DiffInstructionArg arg = 3;
// Separator between arguments (comma)
bool separator = 4;
}
}
message DiffOpcode {
// Mnemonic string
string mnemonic = 1;
// Base opcode ID
uint32 opcode = 2;
} }
// An instruction argument // An instruction argument
message Argument { message DiffInstructionArg {
oneof value { oneof arg {
// Plain text // Signed immediate value
string plain_text = 1; sint64 signed = 1;
// Value // Unsigned immediate value
ArgumentValue argument = 2;
// Relocation
ArgumentRelocation relocation = 3;
// Branch destination
uint64 branch_dest = 4;
}
}
// An instruction argument value
message ArgumentValue {
oneof value {
// Signed integer
int64 signed = 1;
// Unsigned integer
uint64 unsigned = 2; uint64 unsigned = 2;
// Opaque value // Opaque string value (register names, etc.)
string opaque = 3; string opaque = 3;
// Relocation
bool reloc = 4;
// Branch destination address
uint64 branch_dest = 5;
} }
} }
// Marker type for relocation arguments // Relocation information
message ArgumentRelocation { message DiffRelocation {
}
message Relocation {
uint32 type = 1; uint32 type = 1;
string type_name = 2; string type_name = 2;
RelocationTarget target = 3; uint32 target_symbol = 3;
int64 addend = 4;
} }
message RelocationTarget { // Argument diff information
uint32 symbol_index = 1; message DiffInstructionArgDiff {
int64 addend = 2; // If set, this argument differs from the other side.
} // The value is a rotating index for coloring.
message InstructionDiffRow {
DiffKind diff_kind = 1;
optional Instruction instruction = 2;
optional InstructionBranchFrom branch_from = 3;
optional InstructionBranchTo branch_to = 4;
repeated ArgumentDiff arg_diff = 5;
}
message ArgumentDiff {
optional uint32 diff_index = 1; optional uint32 diff_index = 1;
} }
// Diff kind for instructions and data
enum DiffKind { enum DiffKind {
DIFF_NONE = 0; DIFF_NONE = 0;
DIFF_REPLACE = 1; DIFF_REPLACE = 1;
@@ -112,54 +153,19 @@ enum DiffKind {
DIFF_ARG_MISMATCH = 5; DIFF_ARG_MISMATCH = 5;
} }
message InstructionBranchFrom { // Data diff segment
repeated uint32 instruction_index = 1; message DiffDataSegment {
uint32 branch_index = 2;
}
message InstructionBranchTo {
uint32 instruction_index = 1;
uint32 branch_index = 2;
}
message SymbolDiff {
Symbol symbol = 1;
repeated InstructionDiffRow instruction_rows = 2;
optional float match_percent = 3;
// The symbol index in the _other_ object that this symbol was diffed against
optional uint32 target_symbol = 5;
}
message DataDiff {
DiffKind kind = 1; DiffKind kind = 1;
bytes data = 2; bytes data = 2;
// May be larger than data // Size may be larger than data length for BSS
uint64 size = 3; uint64 size = 3;
} }
message SectionDiff { // Data relocation diff
string name = 1; message DiffDataRelocation {
SectionKind kind = 2; DiffRelocation relocation = 1;
uint64 size = 3; DiffKind kind = 2;
uint64 address = 4; // Address range this relocation covers
reserved 5; uint64 start = 3;
repeated DataDiff data = 6; uint64 end = 4;
optional float match_percent = 7;
}
enum SectionKind {
SECTION_UNKNOWN = 0;
SECTION_TEXT = 1;
SECTION_DATA = 2;
SECTION_BSS = 3;
}
message ObjectDiff {
repeated SectionDiff sections = 1;
repeated SymbolDiff symbols = 2;
}
message DiffResult {
optional ObjectDiff left = 1;
optional ObjectDiff right = 2;
} }
Binary file not shown.
File diff suppressed because it is too large Load Diff

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