Compare commits

...

144 Commits
v2.7.0 ... main

Author SHA1 Message Date
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
678210d58a Change file watcher log message to debug 2025-08-15 16:38:06 -06:00
4302821615 Fix Windows build 2025-08-15 16:31:33 -06:00
c4b4244b59 Version v3.0.0 2025-08-15 16:27:27 -06:00
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
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
0f0aaab795 Fix WSL path handling
Resolves #170
2025-08-15 15:34:58 -06:00
b21892be31 Add CLI args to objdiff-gui (incl. --project-dir/-p)
Resolves #41
Resolves #211
2025-08-15 15:25:55 -06:00
247d6da94b Restore extab diff view 2025-08-15 15:06:16 -06:00
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
2c57e4960f Add ARM logic for inferred function size padding
Resolves #237
2025-08-15 14:48:26 -06:00
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
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
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
91bc23edfc Fix objdiff-wasm build 2025-08-02 11:32:04 -06:00
c9c3b32376 Use let chains (a.k.a. cargo clippy --fix) 2025-08-02 11:27:28 -06:00
0dc123b064 Don't fail on line info parsing; use gimli::RelocateReader
Workaround for #228
2025-08-02 11:27:28 -06:00
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
1205e8ceb4 Version v3.0.0-beta.12 2025-07-29 21:31:12 -06:00
c917cad5f0 Strip zeros from end of inferred function sizes
Resolves #3
2025-07-29 21:20:47 -06:00
dd653329f5 Fix reading IMAGE_REL_PPC_REFHI/REFLO without PAIR 2025-07-29 20:58:34 -06:00
f5d3d5f10a gui: Split OpenGL into OpenGL (glow), OpenGL ES (wgpu) 2025-07-29 20:49:09 -06:00
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
00ad0d8094 Support PPC64 ELFs (PS3); refactor relocation processing 2025-07-21 21:01:03 -06:00
8fac63c42c Remove temporary test 2025-07-17 21:23:12 -06:00
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
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
5f48e69775 More clippy fixes 2025-07-07 15:29:30 -06:00
8756eee07b clippy fixes & version bump 2025-07-07 14:56:41 -06:00
bd3ed0d5ad Version v3.0.0-beta.10 2025-06-17 13:03:14 -06:00
Mark Langen
e638d0b17a
Implementation of basic data flow analysis for PowerPC (#212)
* WIP implementation

* * Move flow analysis to dedicated file
* Show string constants inline
* Handle calls to MWCC "sled" helpers which otherwise disrupt flow analysis

* Run cargo insta review

* Apply clippy feedback

* Update more tests.

* Remove std use from ppc flow analysis

* Try to make wasm build work again

* More test changes

* Probably last wasm fix

* Formatting

* Fix WASM

* One more clippy thing

* Fixed display of float constants in a LFS or LFD instruction in case where there is a branch to the subsequent instruction with a different register value.

* On lines with a reloc, only hide Symbol type data flow values rather than all data flow values.

* Formatting
2025-06-17 12:59:04 -06:00
Ryan Burns
f58616b6dd
Use symbol name when comparing against an externed reloc (#214)
* Use symbol name when comparing against an externed reloc

For partial matching files, often a symbol is externed even though it
should exist in the target object. We can still compare the symbol name,
instead of always returning a mismatch.

* Combine cases, and apply change reloc_eq in code.rs
2025-05-30 15:00:02 -06:00
Alex Page
e9762e24c2
Add support for x86 ELF object files (#213) 2025-05-30 13:19:06 -06:00
dab79d96a1 Version v3.0.0-beta.9 2025-05-27 21:32:57 -06:00
a57e5db983 WASM API updates, support symbol mapping 2025-05-27 21:31:29 -06:00
LagoLunatic
d0afd3b83e
Fix scroll hotkeys not working in data diff view (#208) 2025-05-27 09:27:00 -06:00
Anghelo Carvajal
a367af612b
Make encoding_rs an optional dependency (#205) 2025-05-17 23:14:15 -06:00
LagoLunatic
22052ea10b
Data diff view: Show bytes with relocations as ?? instead of 00 (#204)
* Data diff view: Show bytes with relocations as `xx`

* xx -> ??
2025-05-14 21:12:59 -06:00
f7c3501eae Version v3.0.0-beta.8 2025-05-13 23:15:46 -06:00
07ef93f16a Ignore extern symbols with symbol name lookups
When searching for a symbol by name, only look at
symbols that are defined within the object,
ignoring extern symbols (symbols without section).

Fixes #180
Fixes #181
2025-05-13 22:51:26 -06:00
8e8ab6bef8 Skip label symbols when inferring symbol sizes
COFF objects in particular don't contain the size of
symbols. We infer the size of these symbols by
extending them to the next symbol. If a tool emits
symbols for branch targets, this causes the inferred
size to be too small.

This checks if a symbol starts with a certain prefix
(right now, just .L or LAB_), and skips over it
during symbol size inference.

Resolves #174
2025-05-13 22:36:02 -06:00
e865f3d598 Fix symbol mapping mismatched match %
We have specific diff logic that relies on knowing
which object is the target object, and which is the
base. generate_mapping_symbols was designed in such
a way that it would reverse the target/base, leading
to a match percent shown that's different when it
gets applied.

Fixes #200
2025-05-13 21:57:16 -06:00
2b13e9886a Fix hidden symbol regression
The flagset .contains check doesn't work like this.

Fixes #199
2025-05-13 21:37:29 -06:00
1750af736a Try target-feature=+crt-static 2025-05-13 21:28:57 -06:00
LagoLunatic
731b604c24
Fix highlighting of signed vs unsigned arguments (#202)
* Fix signed and unsigned arguments not being considered equal when highlighting

* Remove unused Eq derive
2025-05-13 14:03:00 -06:00
2d643eb071 Add scratch.preset_id to config.schema.json 2025-05-09 12:51:18 -06:00
0c48d711c7 Improve local branch relocation handling
Reworks the local-branch handling logic to be more
unified: scan_instructions does all the work up front,
and process_instruction / display_instruction can
simply use the calculated branch destination instead
of performing their own is-relocation-target-
function-local checks.

(Hopefully) Fixes #192
2025-05-07 22:53:10 -06:00
34220a8e26 Add address to ReportItem, stabilize sections/functions ordering 2025-05-07 17:36:49 -06:00
0c9e5526d4 Combine sections when generating report 2025-05-07 16:47:20 -06:00
3db0727469 Omit match % for right sections, improve multi-section diffing
Fixes #120
2025-05-07 16:47:20 -06:00
8b5bf21f38 Mark combined sections as SectionKind::Unknown 2025-05-07 16:45:00 -06:00
b77df77000 Minor cleanup, remove Section::symbol_data 2025-05-07 16:43:34 -06:00
7e08f9715b Update dependencies 2025-05-07 16:42:02 -06:00
3c05852d00 Document SuperH support 2025-05-06 23:25:29 -06:00
a51ff44be1 Fix superh wasm (no_std) build 2025-05-06 23:21:07 -06:00
d225cac205 Add superh feature to wasm build 2025-05-06 23:14:38 -06:00
737b3782db ci: Add id-token write permission 2025-05-06 22:51:11 -06:00
sozud
1d782243e0
Add data info for superh (#198)
* Add data info for superh

* fmt

* Fetch symbol data dynamically from resolved.section

* Remove unused var

---------

Co-authored-by: Luke Street <luke@street.dev>
2025-05-06 22:30:53 -06:00
a1499f475d Update test snapshot 2025-05-06 22:07:21 -06:00
LagoLunatic
f263e490e3
Combine data/text sections: Pad sections to alignment (#197)
* Combine data/text sections: Pad all sections to 4-byte minimum alignment

* Update x86 test snapshot

* Read and store object section alignment

* Combine data/text sections: Pad sections to more than 4-byte alignment if they have alignment specified
2025-05-06 21:47:08 -06:00
Aetias
d0e6c5c057
Display branch destinations after PC-relative loads (#194) 2025-05-06 21:45:03 -06:00
LagoLunatic
e1c51ac297
ARM: Fix "Combine text sections" confusing code and data (#195)
* ARM: Fix parsing of mapping symbols when "Combine text sections" is enabled

* Add test
2025-04-26 11:14:16 -06:00
39b1b49985 cli: Add --config arg to report generate 2025-04-22 18:13:12 -06:00
6c7160ab7e Use rabbitizer 2.0.0-alpha.1 2025-04-22 18:13:12 -06:00
sozud
644d4762f0
Preliminary SuperH support (#186)
* Preliminary SuperH support

* fixes

* clippy

* clippy
2025-04-17 14:20:30 -06:00
LagoLunatic
b40fae5140
Allow copying Shift JIS encoded string literals (#189)
* Update openssl and tokio for Cargo deny

* Allow copying Shift JIS encoded strings

* Override data type label for Shift JIS strings
2025-04-17 10:07:05 -06:00
LagoLunatic
fbf85632ab
PPC: Detect unpooled string literal references (#188)
* Update openssl and tokio for Cargo deny

* PPC: Detect unpooled string literal references
2025-04-16 23:33:55 -06:00
73a89d2768 Version v3.0.0-beta.6 2025-04-02 11:03:40 -06:00
a162c2f840 Fix to_string_in_format_args warning 2025-04-01 23:36:47 -06:00
a474b27d55 Update dependencies 2025-04-01 23:33:17 -06:00
3d7f2b70dc Replace some git dependencies with cargo registry 2025-04-01 22:56:16 -06:00
fe886f862d ci: Auto cargo publish / npm publish 2025-04-01 21:49:01 -06:00
2bcbc34850 wasm: Improve API error handling 2025-04-01 21:45:13 -06:00
Steven Casper
9b557e4c8e
Limit left-panel scrollview to the file tree (#185)
* Limit left-panel scrollview to the file tree

Removes the redundant build button

* Expand ScrollArea to full side panel width

* Use auto_shrink(false) instead of set_width

---------

Co-authored-by: Luke Street <luke@street.dev>
2025-04-01 12:35:17 -06:00
LagoLunatic
b9ba5796ed
PPC: Fix pooled relocation addends being added twice sometimes (#184) 2025-03-30 22:00:36 -06:00
LagoLunatic
e101610416
ARM: Fix subtract with overflow error when no mapping symbol at address 0 (#183) 2025-03-30 22:00:06 -06:00
LagoLunatic
196c003a92
Reimplement colorized data relocation hover diffs (#182)
* Reimplement colorized data relocation hover diffs

* Fix objdiff-wasm build

Data diffing doesn't seem to be fully implemented in objdiff-wasm yet, so just putting placeholders in so it compiles.

* Reloc hover: Add separators, override special color too
2025-03-28 21:48:14 -06:00
7b00a9e9f2 wasm: Cache objects via data hash (XXH3) 2025-03-21 08:27:19 -06:00
311de887ec Update test snapshot 2025-03-19 19:00:57 -06:00
485b259c32 Apply clippy suggestion 2025-03-19 19:00:01 -06:00
d8fdfaa2c0 Fix no_std build 2025-03-19 18:57:41 -06:00
2612cda1fb objdiff-cli report: Skip unknown sections
Regression in v3.0.0-alpha.1

Fixes #171
2025-03-19 18:54:34 -06:00
bc46e17824 x86_64: Fix relocation placement in instruction 2025-03-19 18:54:34 -06:00
e735adbd3d x86: Support inline data for jumptables 2025-03-19 18:54:34 -06:00
6768df9d80 Version v3.0.0-beta.4 2025-03-18 21:36:37 -06:00
eaba2391e0 arm: Fix thumb & data decoding, add tests 2025-03-18 21:26:28 -06:00
bd8f5ede23 Add pre-commit hooks 2025-03-18 21:26:28 -06:00
acf46c6b54 Fix no_std build 2025-03-14 09:49:31 -06:00
9358d8ec60 mips: Add C++ symbol demangling (CW & modern GCC) 2025-03-14 09:44:15 -06:00
809e2ffddc cargo fmt 2025-03-11 21:43:32 -06:00
87fa29e8b0 objdiff-wasm: Fix symbol filtering
regex crate needed the `unicode-case` feature
2025-03-11 21:42:14 -06:00
42d4c38079 mips: Ignore .NON_MATCHING functions from INLINE_ASM macros 2025-03-11 21:39:17 -06:00
fa26200ed7 Silence warning 2025-03-10 22:22:05 -06:00
a0e7f9bc37 Fix x86 mov relocations with uint32 2025-03-10 22:09:58 -06:00
5898d7aebf Fix section ordering with many same-named sections
Before, this was comparing, for example, `.text-2`
with `.text-10` with standard string comparison,
yielding `.text-10` before `.text-2`.

Instead, this simply uses a stable sort by name,
which preserves the relative ordering of sections.
2025-03-10 21:51:54 -06:00
ffb38d1bb0 Fix cargo deny advisories 2025-03-09 22:59:42 -06:00
d56dda72f0 Version v3.0.0-beta.2 2025-03-09 22:56:28 -06:00
c6971f3f2d Add initial support for x86-64 relocations 2025-03-09 22:51:43 -06:00
3965a035fa objdiff-cli diff: Show build errors/log 2025-03-09 22:51:43 -06:00
f1fc29f77e Split report changes into separate proto 2025-03-08 14:39:15 -07:00
7c4f1c5d13 Fix left/right arch mismatches in diff code 2025-03-08 10:44:44 -07:00
fa4a6cadbb Downgrade objdiff-wasm Rust edition temporarily 2025-03-04 23:27:17 -07:00
799971d54e Migrate to Rust edition 2024 2025-03-04 22:31:38 -07:00
8eef37e8df Version v3.0.0-beta.1 2025-03-04 22:24:55 -07:00
5f36916087 Fix unintended unwrap in load_font_if_needed 2025-03-04 22:24:20 -07:00
ee667a2dde Update config-schema.json: PPC -> PowerPC 2025-03-04 22:23:45 -07:00
LagoLunatic
cf5fc54cfa
PPC: Reimplement pooled data reference calculation (#167)
* PPC: Calculate pooled relocations

Reimplements #140

The relocations are now generated when the object is first read in `parse`, right after the real relocations are read.

`resolve_relocation` was changed to take `obj.symbols` instead of `obj` as an argument, because `obj` itself doesn't exist yet at the time the relocations are being read.

* Improve readability of PPC pool relocs code

* Fix regression causing extern pool relocs to be ignored

* Fix showing incorrect diff when diffing weak stripped symbol with an addend

This is a regression that was introduced by #158 diffing addends in addition to symbol names. But it's not really a bug in that PR, rather it seems like I simply never added the offset into the addend when creating a fake pool relocation for an extern symbol. So this commit fixes that root issue instead.

* Add PPC "Calculate pooled data references" option

* Fix objdiff-wasm compilation errors

* Update PPC test snapshots
2025-03-04 20:40:34 -07:00
1cdfa1e857 Update rabbitizer & utilize use_dollar option 2025-03-04 09:38:59 -07:00
3f157f33a5 Reorder tooltip/context items slightly 2025-03-04 09:10:54 -07:00
LagoLunatic
a1ea2919f8
Reimplement function data value tooltips, function data value diffing, and data relocation diffing (#166)
* Show data literal values on instruction hover

Reimplements #108

* Show reloc diffs in func view when data's content differs

Reimplements #153

* Data diff view: Show relocs on hover and in context menu

This reimplements #154

Note that colorizing the text depending on the kind of diff has still not been reimplemented yet

* Fix up some comments
2025-03-04 08:56:46 -07:00
9c31c82a37 cargo fmt 2025-03-03 21:08:36 -07:00
4f34dfa194 Don't infer sizes for labels within another symbol 2025-03-03 21:01:22 -07:00
ae6d37a10b Version v3.0.0-alpha.2 2025-03-03 18:25:38 -07:00
06e54f3456 cargo fmt 2025-03-03 17:16:35 -07:00
6ed07bfaf1 MIPS relocation pairing, '$' prefix option & fixes
Implements MIPS relocation pairing logic.
New option for "Register '$' prefix", off by default.
Fixes various regressions introduced by refactoring.

Resolves #122
Resolves #156
2025-03-03 17:14:32 -07:00
80e939653a Build objdiff-gui against older glibc
Resolves #165
2025-03-03 13:34:05 -07:00
48305e0380 Add hover/context APIs to wasm component 2025-03-02 21:51:47 -07:00
2eafbb218b Update all dependencies 2025-03-02 16:13:12 -07:00
a1f2a535e5 Unify context menu / hover tooltip code + UI improvements 2025-03-02 16:13:12 -07:00
8461b35cd7 Fix WASM CI build 2025-03-02 16:13:12 -07:00
95868f1d19 Reimplement x86 arch, MSVC section group combining
Plus display_row/DiffText refactoring
2025-03-02 16:13:12 -07:00
506c251d68 Various fixes 2025-02-26 22:47:42 -07:00
f3c157ff06 WIP objdiff 3.0 refactor 2025-02-26 22:09:39 -07:00
angie
6d3c63ccd8 Use rabbitizer 2
Co-Authored-By: Luke Street <luke@street.dev>
2025-02-09 22:28:04 -07:00
bbd8d9714f Adjust CI build features 2025-02-09 22:28:04 -07:00
3c66ac3d54 Fix run_make on Windows 2025-02-09 22:28:04 -07:00
561a9107e2 clippy & deny fixes 2025-02-09 22:28:04 -07:00
e8de35b78e Make objdiff-core no_std + huge WASM rework 2025-02-09 22:28:02 -07:00
d938988d43 Diff view refactor 2025-02-09 22:27:42 -07:00
LagoLunatic
3e6efb7736
Check relocation addends when diffing functions (#158)
* Check relocation addends when diffing functions

* Also highlight addend when reloc differs
2025-02-09 22:26:49 -07:00
Steven Casper
674c942d7d
Implement context menu copy functionality for data values (#163)
* Implement context menu copy functionality for data values

* Clippy fixes
2025-02-09 22:24:52 -07:00
LagoLunatic
6b7dcabbed
Fix added/removed bytes being visually misaligned in data diff view (#159) 2025-01-24 17:12:44 -07:00
e202c3ef95 Version v2.7.1 2025-01-21 22:59:20 -07:00
LagoLunatic
b7730b3d00
Refactor data relocation diffing to improve accuracy and fix bugs (#157)
* Data reloc hover tooltip: Show relocation source address

* Refactor data relocation diffing to improve accuracy and fix bugs
2025-01-21 22:54:31 -07:00
162 changed files with 47567 additions and 11908 deletions

View File

@ -1,5 +1,4 @@
[target.x86_64-pc-windows-msvc] # statically link the C runtime so the executable does not depend on
linker = "rust-lld" # that shared/dynamic library.
[target.'cfg(all(target_env = "msvc", target_os = "windows"))']
[target.aarch64-pc-windows-msvc] rustflags = ["-C", "target-feature=+crt-static"]
linker = "rust-lld"

2
.editorconfig Normal file
View File

@ -0,0 +1,2 @@
[*.md]
trim_trailing_whitespace = false

View File

@ -4,13 +4,16 @@ on:
pull_request: pull_request:
push: push:
paths-ignore: paths-ignore:
- '*.md' - "*.md"
- 'LICENSE*' - "LICENSE*"
workflow_dispatch: workflow_dispatch:
permissions:
# For npm publish provenance
id-token: write
env: env:
BUILD_PROFILE: release-lto BUILD_PROFILE: release-lto
CARGO_TARGET_DIR: target
CARGO_INCREMENTAL: 0 CARGO_INCREMENTAL: 0
jobs: jobs:
@ -33,9 +36,9 @@ jobs:
- name: Cache Rust workspace - name: Cache Rust workspace
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
- name: Cargo check - name: Cargo check
run: cargo check --all-features --all-targets run: cargo check --all-targets --all-features
- name: Cargo clippy - name: Cargo clippy
run: cargo clippy --all-features --all-targets run: cargo clippy --all-targets --all-features
fmt: fmt:
name: Format name: Format
@ -71,7 +74,6 @@ jobs:
test: test:
name: Test name: Test
if: 'false' # No tests yet
strategy: strategy:
matrix: matrix:
platform: [ ubuntu-latest, windows-latest, macos-latest ] platform: [ ubuntu-latest, windows-latest, macos-latest ]
@ -90,7 +92,7 @@ jobs:
- name: Cache Rust workspace - name: Cache Rust workspace
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
- name: Cargo test - name: Cargo test
run: cargo test --release --all-features run: cargo test --release --features all
build-cli: build-cli:
name: Build objdiff-cli name: Build objdiff-cli
@ -150,7 +152,7 @@ jobs:
python3 -m venv .venv python3 -m venv .venv
. .venv/bin/activate . .venv/bin/activate
echo PATH=$PATH >> $GITHUB_ENV echo PATH=$PATH >> $GITHUB_ENV
pip install ziglang==0.13.0 cargo-zigbuild==0.19.1 pip install ziglang==0.13.0.post1 cargo-zigbuild==0.19.8
- name: Setup Rust toolchain - name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
with: with:
@ -161,15 +163,15 @@ jobs:
key: ${{ matrix.target }} key: ${{ matrix.target }}
- name: Cargo build - name: Cargo build
run: > run: >
cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }} cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }} --bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }} name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
path: | path: |
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }} target/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe target/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
if-no-files-found: error if-no-files-found: error
build-gui: build-gui:
@ -180,21 +182,26 @@ jobs:
matrix: matrix:
include: include:
- platform: ubuntu-latest - platform: ubuntu-latest
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu.2.31
target_base: x86_64-unknown-linux-gnu
name: linux-x86_64 name: linux-x86_64
packages: libgtk-3-dev packages: libgtk-3-dev
build: zigbuild
features: default features: default
- platform: windows-latest - platform: windows-latest
target: x86_64-pc-windows-msvc target: x86_64-pc-windows-msvc
name: windows-x86_64 name: windows-x86_64
build: build
features: default features: default
- platform: macos-latest - platform: macos-latest
target: x86_64-apple-darwin target: x86_64-apple-darwin
name: macos-x86_64 name: macos-x86_64
build: build
features: default features: default
- platform: macos-latest - platform: macos-latest
target: aarch64-apple-darwin target: aarch64-apple-darwin
name: macos-arm64 name: macos-arm64
build: build
features: default features: default
fail-fast: false fail-fast: false
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
@ -206,38 +213,70 @@ jobs:
sudo apt-get -y install ${{ matrix.packages }} sudo apt-get -y install ${{ matrix.packages }}
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install cargo-zigbuild
if: matrix.build == 'zigbuild'
run: |
python3 -m venv .venv
. .venv/bin/activate
echo PATH=$PATH >> $GITHUB_ENV
pip install ziglang==0.13.0.post1 cargo-zigbuild==0.19.8
- name: Setup Rust toolchain - name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
with: with:
targets: ${{ matrix.target }} targets: ${{ matrix.target_base || matrix.target }}
- name: Cache Rust workspace - name: Cache Rust workspace
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
with: with:
key: ${{ matrix.target }} key: ${{ matrix.target }}
- name: Cargo build - name: Cargo build
run: > run: >
cargo build --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }} cargo ${{ matrix.build }} --profile ${{ env.BUILD_PROFILE }} --target ${{ matrix.target }}
--bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }} --bin ${{ env.CARGO_BIN_NAME }} --features ${{ matrix.features }}
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }} name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
path: | path: |
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }} target/${{ matrix.target_base || matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe target/${{ matrix.target_base || matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
if-no-files-found: error if-no-files-found: error
release: build-wasm:
name: Release name: Build objdiff-wasm
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [ build-cli, build-gui ]
permissions:
contents: write
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Check git tag against Cargo version - name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm -C objdiff-wasm ci
- name: Build
run: npm -C objdiff-wasm run build
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: wasm
path: objdiff-wasm/dist/
if-no-files-found: error
check-version:
name: Check package versions
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [ build-cli, build-gui, build-wasm ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check git tag against package versions
shell: bash shell: bash
run: | run: |
set -eou pipefail set -eou pipefail
@ -249,9 +288,24 @@ jobs:
echo "::error::Git tag doesn't match the Cargo version! ($tag != $version)" echo "::error::Git tag doesn't match the Cargo version! ($tag != $version)"
exit 1 exit 1
fi fi
version="v$(jq -r .version objdiff-wasm/package.json)"
if [ "$tag" != "$version" ]; then
echo "::error::Git tag doesn't match the npm version! ($tag != $version)"
exit 1
fi
release-github:
name: Release (GitHub)
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [ check-version ]
permissions:
contents: write
steps:
- name: Download artifacts - name: Download artifacts
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
pattern: objdiff-*
path: artifacts path: artifacts
- name: Rename artifacts - name: Rename artifacts
working-directory: artifacts working-directory: artifacts
@ -281,3 +335,53 @@ jobs:
files: out/* files: out/*
draft: true draft: true
generate_release_notes: true generate_release_notes: true
release-cargo:
name: Release (Cargo)
if: 'false' # TODO re-enable when all dependencies are published
runs-on: ubuntu-latest
needs: [ check-version ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Publish
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish -p objdiff-core
release-npm:
name: Release (npm)
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
needs: [ check-version ]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: lts/*
registry-url: 'https://registry.npmjs.org'
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: wasm
path: objdiff-wasm/dist
- name: Publish
working-directory: objdiff-wasm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
set -euo pipefail
version=$(jq -r '.version' package.json)
tag="latest"
# Check for prerelease by looking for a dash
case "$version" in
*-*)
tag=$(echo "$version" | sed -e 's/^[^-]*-//' -e 's/\..*$//')
;;
esac
echo "Publishing version $version with tag '$tag'..."
npm publish --provenance --access public --tag "$tag"

36
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,36 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
- id: end-of-file-fixer
- id: fix-byte-order-marker
- id: check-yaml
- id: check-added-large-files
- repo: local
hooks:
- id: cargo-fmt
name: cargo fmt
description: Run cargo fmt on all project files.
language: system
entry: cargo
args: ["+nightly", "fmt", "--all"]
pass_filenames: false
- id: cargo clippy
name: cargo clippy
description: Run cargo clippy on all project files.
language: system
entry: cargo
args: ["+nightly", "clippy", "--all-targets", "--all-features"]
pass_filenames: false
- id: cargo-deny
name: cargo deny
description: Run cargo deny on all project files.
language: system
entry: cargo
args: ["deny", "check"]
pass_filenames: false
always_run: true

3342
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -3,8 +3,9 @@ members = [
"objdiff-cli", "objdiff-cli",
"objdiff-core", "objdiff-core",
"objdiff-gui", "objdiff-gui",
"objdiff-wasm",
] ]
resolver = "2" resolver = "3"
[profile.release-lto] [profile.release-lto]
inherits = "release" inherits = "release"
@ -13,9 +14,9 @@ strip = "debuginfo"
codegen-units = 1 codegen-units = 1
[workspace.package] [workspace.package]
version = "2.7.0" version = "3.0.0"
authors = ["Luke Street <luke@street.dev>"] authors = ["Luke Street <luke@street.dev>"]
edition = "2021" edition = "2024"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
repository = "https://github.com/encounter/objdiff" repository = "https://github.com/encounter/objdiff"
rust-version = "1.81" rust-version = "1.88"

View File

@ -16,11 +16,12 @@ Features:
Supports: Supports:
- PowerPC 750CL (GameCube, Wii)
- MIPS (N64, PS1, PS2, PSP)
- x86 (COFF only at the moment)
- ARM (GBA, DS, 3DS) - ARM (GBA, DS, 3DS)
- ARM64 (Switch, experimental) - ARM64 (Switch)
- MIPS (N64, PS1, PS2, PSP)
- PowerPC (GameCube, Wii, PS3, Xbox 360)
- SuperH (Saturn, Dreamcast)
- x86, x86_64 (PC)
See [Usage](#usage) for more information. See [Usage](#usage) for more information.
@ -105,6 +106,9 @@ file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it
"*.txt", "*.txt",
"*.json" "*.json"
], ],
"ignore_patterns": [
"build/**/*"
],
"units": [ "units": [
{ {
"name": "main/MetroTRK/mslsupp", "name": "main/MetroTRK/mslsupp",
@ -140,18 +144,22 @@ It's unlikely you'll want to disable this, unless you're using an external tool
If any of these files change, objdiff will automatically rebuild the objects and re-compare them. If any of these files change, objdiff will automatically rebuild the objects and re-compare them.
If not specified, objdiff will use the default patterns listed above. If not specified, objdiff will use the default patterns listed above.
`ignore_patterns` _(optional)_: A list of glob patterns to explicitly ignore when watching for changes.
([Supported syntax](https://docs.rs/globset/latest/globset/#syntax))
If not specified, objdiff will use the default patterns listed above.
`units` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation. `units` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
> `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used. > `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.
> >
> `target_path`: Path to the "target" or "expected" object from the project root. > `target_path`: Path to the "target" or "expected" object from the project root.
> This object is the **intended result** of the match. > This object is the **intended result** of the match.
> >
> `base_path`: Path to the "base" or "actual" object from the project root. > `base_path`: Path to the "base" or "actual" object from the project root.
> This object is built from the **current source code**. > This object is built from the **current source code**.
> >
> `metadata.auto_generated` _(optional)_: Hides the object from the object list, but still includes it in reports. > `metadata.auto_generated` _(optional)_: Hides the object from the object list, but still includes it in reports.
> >
> `metadata.complete` _(optional)_: Marks the object as "complete" (or "linked") in the object list. > `metadata.complete` _(optional)_: Marks the object as "complete" (or "linked") in the object list.
> This is useful for marking objects that are fully decompiled. A value of `false` will mark the object as "incomplete". > This is useful for marking objects that are fully decompiled. A value of `false` will mark the object as "incomplete".
@ -173,6 +181,21 @@ $ cargo install --locked --git https://github.com/encounter/objdiff.git objdiff-
The binaries will be installed to `~/.cargo/bin` as `objdiff` and `objdiff-cli`. The binaries will be installed to `~/.cargo/bin` as `objdiff` and `objdiff-cli`.
## Installing `pre-commit`
When contributing, it's recommended to install `pre-commit` to automatically run the linter and formatter before a commit.
[`uv`](https://github.com/astral-sh/uv#installation) is recommended to manage Python version and tools.
Rust nightly is required for `cargo +nightly fmt` and `cargo +nightly clippy`.
```shell
$ cargo install --locked cargo-deny
$ rustup toolchain install nightly
$ uv tool install pre-commit
$ pre-commit install
```
## License ## License
Licensed under either of Licensed under either of

View File

@ -74,6 +74,16 @@
"*.json" "*.json"
] ]
}, },
"ignore_patterns": {
"type": "array",
"description": "List of glob patterns to explicitly ignore when watching for changes.\nFiles matching these patterns will not trigger a rebuild.\nSupported syntax: https://docs.rs/globset/latest/globset/#syntax",
"items": {
"type": "string"
},
"default": [
"build/**/*"
]
},
"objects": { "objects": {
"type": "array", "type": "array",
"description": "Use units instead.", "description": "Use units instead.",
@ -175,6 +185,10 @@
"type": "boolean", "type": "boolean",
"description": "If true, objdiff will run the build command with the context file as an argument to generate it.", "description": "If true, objdiff will run the build command with the context file as an argument to generate it.",
"default": false "default": false
},
"preset_id": {
"type": "number",
"description": "The decomp.me preset ID to use for the scratch.\nCompiler and flags in the config will take precedence over the preset, but the preset is useful for organizational purposes."
} }
}, },
"required": [ "required": [

View File

@ -73,6 +73,7 @@ ignore = [
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
{ id = "RUSTSEC-2024-0436", reason = "Unmaintained paste crate is an indirect dependency" },
] ]
# If this is true, then cargo deny will use the git executable to fetch advisory database. # If this is true, then cargo deny will use the git executable to fetch advisory database.
# If this is false, then it uses a built-in git library. # If this is false, then it uses a built-in git library.
@ -101,8 +102,8 @@ allow = [
"Zlib", "Zlib",
"0BSD", "0BSD",
"OFL-1.1", "OFL-1.1",
"LicenseRef-UFL-1.0", "Ubuntu-font-1.0",
"OpenSSL", "CDLA-Permissive-2.0",
] ]
# The confidence threshold for detecting a license from license text. # The confidence threshold for detecting a license from license text.
# The higher the value, the more closely the license text must be to the # The higher the value, the more closely the license text must be to the
@ -239,7 +240,10 @@ allow-git = []
[sources.allow-org] [sources.allow-org]
# github.com organizations to allow git sources for # github.com organizations to allow git sources for
github = [] github = [
"enarx", # flagset
"encounter",
]
# gitlab.com organizations to allow git sources for # gitlab.com organizations to allow git sources for
gitlab = [] gitlab = []
# bitbucket.org organizations to allow git sources for # bitbucket.org organizations to allow git sources for

View File

@ -15,11 +15,11 @@ 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.2"
memmap2 = "0.9" memmap2 = "0.9"
objdiff-core = { path = "../objdiff-core", features = ["all"] } objdiff-core = { path = "../objdiff-core", features = ["all"] }
prost = "0.13" prost = "0.14"
ratatui = "0.29" ratatui = "0.29"
rayon = "1.10" rayon = "1.10"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
@ -28,6 +28,7 @@ supports-color = "3.0"
time = { version = "0.3", features = ["formatting", "local-offset"] } time = { version = "0.3", features = ["formatting", "local-offset"] }
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
typed-path = "0.11"
[target.'cfg(target_env = "musl")'.dependencies] [target.'cfg(target_env = "musl")'.dependencies]
mimalloc = "0.1" mimalloc = "0.1"

View File

@ -4,7 +4,7 @@
//! For now, this only adds a --version/-V option which causes early-exit. //! For now, this only adds a --version/-V option which causes early-exit.
use std::ffi::OsStr; use std::ffi::OsStr;
use argp::{parser::ParseGlobalOptions, EarlyExit, FromArgs, TopLevelCommand}; use argp::{EarlyExit, FromArgs, TopLevelCommand, parser::ParseGlobalOptions};
struct ArgsOrVersion<T>(T) struct ArgsOrVersion<T>(T)
where T: FromArgs; where T: FromArgs;

View File

@ -1,76 +1,64 @@
use std::{ use std::{
fs,
io::stdout, io::stdout,
mem, mem,
path::{Path, PathBuf},
str::FromStr,
sync::{ sync::{
atomic::{AtomicBool, Ordering},
Arc, Arc,
atomic::{AtomicBool, Ordering},
}, },
task::{Wake, Waker}, task::{Wake, Waker},
time::Duration, time::Duration,
}; };
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{Context, Result, anyhow, bail};
use argp::FromArgs; use argp::FromArgs;
use crossterm::{ use crossterm::{
event, event,
event::{DisableMouseCapture, EnableMouseCapture}, event::{DisableMouseCapture, EnableMouseCapture},
terminal::{ terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, SetTitle, EnterAlternateScreen, LeaveAlternateScreen, SetTitle, disable_raw_mode, enable_raw_mode,
}, },
}; };
use objdiff_core::{ use objdiff_core::{
bindings::diff::DiffResult,
build::{ build::{
watcher::{create_watcher, Watcher}, BuildConfig, BuildStatus,
BuildConfig, watcher::{Watcher, create_watcher},
}, },
config::{build_globset, ProjectConfig, ProjectObject}, config::{
diff, ProjectConfig, ProjectObject, ProjectObjectMetadata, build_globset,
diff::{ path::{check_path_buf, platform_path, platform_path_serde_option},
ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig, MappingConfig, ObjDiff,
}, },
diff::{DiffObjConfig, MappingConfig, ObjectDiff},
jobs::{ jobs::{
objdiff::{start_build, ObjDiffConfig},
Job, JobQueue, JobResult, Job, JobQueue, JobResult,
objdiff::{ObjDiffConfig, start_build},
}, },
obj, obj::{self, Object},
obj::ObjInfo,
}; };
use ratatui::prelude::*; use ratatui::prelude::*;
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
use crate::{ use crate::{
util::{ cmd::apply_config_args,
output::{write_output, OutputFormat}, util::term::crossterm_panic_handler,
term::crossterm_panic_handler, views::{EventControlFlow, EventResult, UiView, function_diff::FunctionDiffUi},
},
views::{function_diff::FunctionDiffUi, EventControlFlow, EventResult, UiView},
}; };
#[derive(FromArgs, PartialEq, Debug)] #[derive(FromArgs, PartialEq, Debug)]
/// Diff two object files. (Interactive or one-shot mode) /// Diff two object files. (Interactive or one-shot mode)
#[argp(subcommand, name = "diff")] #[argp(subcommand, name = "diff")]
pub struct Args { pub struct Args {
#[argp(option, short = '1')] #[argp(option, short = '1', from_str_fn(platform_path))]
/// Target object file /// Target object file
target: Option<PathBuf>, target: Option<Utf8PlatformPathBuf>,
#[argp(option, short = '2')] #[argp(option, short = '2', from_str_fn(platform_path))]
/// Base object file /// Base object file
base: Option<PathBuf>, base: Option<Utf8PlatformPathBuf>,
#[argp(option, short = 'p')] #[argp(option, short = 'p', from_str_fn(platform_path))]
/// Project directory /// Project directory
project: Option<PathBuf>, project: Option<Utf8PlatformPathBuf>,
#[argp(option, short = 'u')] #[argp(option, short = 'u')]
/// Unit name within project /// Unit name within project
unit: Option<String>, unit: Option<String>,
#[argp(option, short = 'o')]
/// Output file (one-shot mode) ("-" for stdout)
output: Option<PathBuf>,
#[argp(option)]
/// Output format (json, json-pretty, proto) (default: json)
format: Option<String>,
#[argp(positional)] #[argp(positional)]
/// Function symbol to diff /// Function symbol to diff
symbol: Option<String>, symbol: Option<String>,
@ -89,86 +77,61 @@ pub struct Args {
} }
pub fn run(args: Args) -> Result<()> { pub fn run(args: Args) -> Result<()> {
let (target_path, base_path, project_config) = match ( let (target_path, base_path, project_config) =
&args.target, match (&args.target, &args.base, &args.project, &args.unit) {
&args.base, (Some(_), Some(_), None, None)
&args.project, | (Some(_), None, None, None)
&args.unit, | (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None),
) { (None, None, p, u) => {
(Some(_), Some(_), None, None) let project = match p {
| (Some(_), None, None, None) Some(project) => project.clone(),
| (None, Some(_), None, None) => (args.target.clone(), args.base.clone(), None), _ => check_path_buf(
(None, None, p, u) => { std::env::current_dir().context("Failed to get the current directory")?,
let project = match p {
Some(project) => project.clone(),
_ => std::env::current_dir().context("Failed to get the current directory")?,
};
let Some((project_config, project_config_info)) =
objdiff_core::config::try_project_config(&project)
else {
bail!("Project config not found in {}", &project.display())
};
let mut project_config = project_config.with_context(|| {
format!("Reading project config {}", project_config_info.path.display())
})?;
let object = {
let resolve_paths = |o: &mut ProjectObject| {
o.resolve_paths(
&project,
project_config.target_dir.as_deref(),
project_config.base_dir.as_deref(),
) )
.context("Current directory is not valid UTF-8")?,
}; };
if let Some(u) = u { let Some((project_config, project_config_info)) =
let unit_path = objdiff_core::config::try_project_config(project.as_ref())
PathBuf::from_str(u).ok().and_then(|p| fs::canonicalize(p).ok()); else {
bail!("Project config not found in {}", &project)
let Some(object) = project_config };
.units let project_config = project_config.with_context(|| {
.as_deref_mut() format!("Reading project config {}", project_config_info.path.display())
.unwrap_or_default() })?;
.iter_mut() let target_obj_dir = project_config
.find_map(|obj| { .target_dir
if obj.name.as_deref() == Some(u) { .as_ref()
resolve_paths(obj); .map(|p| project.join(p.with_platform_encoding()));
return Some(obj); let base_obj_dir = project_config
} .base_dir
.as_ref()
let up = unit_path.as_deref()?; .map(|p| project.join(p.with_platform_encoding()));
let objects = project_config
resolve_paths(obj); .units
.iter()
if [&obj.base_path, &obj.target_path] .flatten()
.into_iter() .map(|o| {
.filter_map(|p| p.as_ref().and_then(|p| p.canonicalize().ok())) ObjectConfig::new(
.any(|p| p == up) o,
{ &project,
return Some(obj); target_obj_dir.as_deref(),
} base_obj_dir.as_deref(),
)
None })
}) .collect::<Vec<_>>();
else { let object = if let Some(u) = u {
bail!("Unit not found: {}", u) objects
}; .iter()
.find(|obj| obj.name == *u)
object .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 project_config for (i, obj) in objects.iter().enumerate() {
.units
.as_deref_mut()
.unwrap_or_default()
.iter_mut()
.enumerate()
{
resolve_paths(obj);
if obj if obj
.target_path .target_path
.as_deref() .as_deref()
.map(|o| obj::read::has_function(o, symbol_name)) .map(|o| obj::read::has_function(o.as_ref(), symbol_name))
.transpose()? .transpose()?
.unwrap_or(false) .unwrap_or(false)
{ {
@ -181,7 +144,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)) => &mut project_config.units_mut()[i], (1, Some(i)) => &objects[i],
(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
@ -190,46 +153,20 @@ 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 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))
(target_path, base_path, Some(project_config)) }
} _ => 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 { run_interactive(args, target_path, base_path, project_config)
run_oneshot(&args, output, target_path.as_deref(), base_path.as_deref())
} else {
run_interactive(args, target_path, base_path, project_config)
}
} }
fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)> { fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)> {
let mut diff_config = DiffObjConfig::default(); let mut diff_config = DiffObjConfig::default();
for config in &args.config { apply_config_args(&mut diff_config, &args.config)?;
let (key, value) = config.split_once('=').context("--config expects \"key=value\"")?;
let property_id = ConfigPropertyId::from_str(key)
.map_err(|()| anyhow!("Invalid configuration property: {}", key))?;
diff_config.set_property_value_str(property_id, value).map_err(|()| {
let mut options = String::new();
match property_id.kind() {
ConfigPropertyKind::Boolean => {
options = "true, false".to_string();
}
ConfigPropertyKind::Choice(variants) => {
for (i, variant) in variants.iter().enumerate() {
if i > 0 {
options.push_str(", ");
}
options.push_str(variant.value);
}
}
}
anyhow!("Invalid value for {}. Expected one of: {}", property_id.name(), options)
})?;
}
let mut mapping_config = MappingConfig { let mut mapping_config = MappingConfig {
mappings: Default::default(), mappings: Default::default(),
selecting_left: args.selecting_left.clone(), selecting_left: args.selecting_left.clone(),
@ -243,42 +180,18 @@ fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)>
Ok((diff_config, mapping_config)) Ok((diff_config, mapping_config))
} }
fn run_oneshot(
args: &Args,
output: &Path,
target_path: Option<&Path>,
base_path: Option<&Path>,
) -> Result<()> {
let output_format = OutputFormat::from_option(args.format.as_deref())?;
let (diff_config, mapping_config) = build_config_from_args(args)?;
let target = target_path
.map(|p| {
obj::read::read(p, &diff_config).with_context(|| format!("Loading {}", p.display()))
})
.transpose()?;
let base = base_path
.map(|p| {
obj::read::read(p, &diff_config).with_context(|| format!("Loading {}", p.display()))
})
.transpose()?;
let result =
diff::diff_objs(&diff_config, &mapping_config, target.as_ref(), base.as_ref(), None)?;
let left = target.as_ref().and_then(|o| result.left.as_ref().map(|d| (o, d)));
let right = base.as_ref().and_then(|o| result.right.as_ref().map(|d| (o, d)));
write_output(&DiffResult::new(left, right), Some(output), output_format)?;
Ok(())
}
pub struct AppState { pub struct AppState {
pub jobs: JobQueue, pub jobs: JobQueue,
pub waker: Arc<TermWaker>, pub waker: Arc<TermWaker>,
pub project_dir: Option<PathBuf>, pub project_dir: Option<Utf8PlatformPathBuf>,
pub project_config: Option<ProjectConfig>, pub project_config: Option<ProjectConfig>,
pub target_path: Option<PathBuf>, pub target_path: Option<Utf8PlatformPathBuf>,
pub base_path: Option<PathBuf>, pub base_path: Option<Utf8PlatformPathBuf>,
pub left_obj: Option<(ObjInfo, ObjDiff)>, pub left_status: Option<BuildStatus>,
pub right_obj: Option<(ObjInfo, ObjDiff)>, pub right_status: Option<BuildStatus>,
pub prev_obj: Option<(ObjInfo, ObjDiff)>, pub left_obj: Option<(Object, ObjectDiff)>,
pub right_obj: Option<(Object, ObjectDiff)>,
pub prev_obj: Option<(Object, ObjectDiff)>,
pub reload_time: Option<time::OffsetDateTime>, pub reload_time: Option<time::OffsetDateTime>,
pub time_format: Vec<time::format_description::FormatItem<'static>>, pub time_format: Vec<time::format_description::FormatItem<'static>>,
pub watcher: Option<Watcher>, pub watcher: Option<Watcher>,
@ -315,6 +228,49 @@ fn create_objdiff_config(state: &AppState) -> ObjDiffConfig {
} }
} }
/// The configuration for a single object file.
#[derive(Default, Clone, serde::Deserialize, serde::Serialize)]
pub struct ObjectConfig {
pub name: String,
#[serde(default, with = "platform_path_serde_option")]
pub target_path: Option<Utf8PlatformPathBuf>,
#[serde(default, with = "platform_path_serde_option")]
pub base_path: Option<Utf8PlatformPathBuf>,
pub metadata: ProjectObjectMetadata,
pub complete: Option<bool>,
}
impl ObjectConfig {
pub fn new(
object: &ProjectObject,
project_dir: &Utf8PlatformPath,
target_obj_dir: Option<&Utf8PlatformPath>,
base_obj_dir: Option<&Utf8PlatformPath>,
) -> Self {
let target_path = if let (Some(target_obj_dir), Some(path), None) =
(target_obj_dir, &object.path, &object.target_path)
{
Some(target_obj_dir.join(path.with_platform_encoding()))
} else {
object.target_path.as_ref().map(|path| project_dir.join(path.with_platform_encoding()))
};
let base_path = if let (Some(base_obj_dir), Some(path), None) =
(base_obj_dir, &object.path, &object.base_path)
{
Some(base_obj_dir.join(path.with_platform_encoding()))
} else {
object.base_path.as_ref().map(|path| project_dir.join(path.with_platform_encoding()))
};
Self {
name: object.name().to_string(),
target_path,
base_path,
metadata: object.metadata.clone().unwrap_or_default(),
complete: object.complete(),
}
}
}
impl AppState { impl AppState {
fn reload(&mut self) -> Result<()> { fn reload(&mut self) -> Result<()> {
let config = create_objdiff_config(self); let config = create_objdiff_config(self);
@ -330,6 +286,8 @@ impl AppState {
JobResult::None => unreachable!("Unexpected JobResult::None"), JobResult::None => unreachable!("Unexpected JobResult::None"),
JobResult::ObjDiff(result) => { JobResult::ObjDiff(result) => {
let result = result.unwrap(); let result = result.unwrap();
self.left_status = Some(result.first_status);
self.right_status = Some(result.second_status);
self.left_obj = result.first_obj; self.left_obj = result.first_obj;
self.right_obj = result.second_obj; self.right_obj = result.second_obj;
self.reload_time = Some(result.time); self.reload_time = Some(result.time);
@ -355,8 +313,8 @@ impl Wake for TermWaker {
fn run_interactive( fn run_interactive(
args: Args, args: Args,
target_path: Option<PathBuf>, target_path: Option<Utf8PlatformPathBuf>,
base_path: Option<PathBuf>, base_path: Option<Utf8PlatformPathBuf>,
project_config: Option<ProjectConfig>, project_config: Option<ProjectConfig>,
) -> 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") };
@ -370,6 +328,8 @@ fn run_interactive(
project_config, project_config,
target_path, target_path,
base_path, base_path,
left_status: None,
right_status: None,
left_obj: None, left_obj: None,
right_obj: None, right_obj: None,
prev_obj: None, prev_obj: None,
@ -382,10 +342,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, 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 +361,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)?;
@ -407,15 +369,17 @@ fn run_interactive(
let mut result = EventResult { redraw: true, ..Default::default() }; let mut result = EventResult { redraw: true, ..Default::default() };
'outer: loop { 'outer: loop {
if result.redraw { if result.redraw {
terminal.draw(|f| loop { terminal.draw(|f| {
result.redraw = false; loop {
view.draw(&state, f, &mut result); result.redraw = false;
result.click_xy = None; view.draw(&state, f, &mut result);
if !result.redraw { result.click_xy = None;
break; if !result.redraw {
break;
}
// Clear buffer on redraw
f.buffer_mut().reset();
} }
// Clear buffer on redraw
f.buffer_mut().reset();
})?; })?;
} }
loop { loop {

View File

@ -1,2 +1,33 @@
pub mod diff; pub mod diff;
pub mod report; pub mod report;
use std::str::FromStr;
use anyhow::{Context, Result, anyhow};
use objdiff_core::diff::{ConfigEnum, ConfigPropertyId, ConfigPropertyKind, DiffObjConfig};
pub fn apply_config_args(diff_config: &mut DiffObjConfig, args: &[String]) -> Result<()> {
for config in args {
let (key, value) = config.split_once('=').context("--config expects \"key=value\"")?;
let property_id = ConfigPropertyId::from_str(key)
.map_err(|()| anyhow!("Invalid configuration property: {}", key))?;
diff_config.set_property_value_str(property_id, value).map_err(|()| {
let mut options = String::new();
match property_id.kind() {
ConfigPropertyKind::Boolean => {
options = "true, false".to_string();
}
ConfigPropertyKind::Choice(variants) => {
for (i, variant) in variants.iter().enumerate() {
if i > 0 {
options.push_str(", ");
}
options.push_str(variant.value);
}
}
}
anyhow!("Invalid value for {}. Expected one of: {}", property_id.name(), options)
})?;
}
Ok(())
}

View File

@ -1,28 +1,25 @@
use std::{ use std::{collections::HashSet, fs::File, io::Read, time::Instant};
collections::HashSet,
fs::File,
io::Read,
path::{Path, PathBuf},
time::Instant,
};
use anyhow::{bail, Context, Result}; use anyhow::{Context, Result, bail};
use argp::FromArgs; use argp::FromArgs;
use objdiff_core::{ use objdiff_core::{
bindings::report::{ bindings::report::{
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, Report, ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, REPORT_VERSION,
ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata, Report, ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
REPORT_VERSION,
}, },
config::ProjectObject, config::path::platform_path,
diff, obj, diff, obj,
obj::{ObjSectionKind, ObjSymbolFlags}, obj::{SectionKind, SymbolFlag},
}; };
use prost::Message; use prost::Message;
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use tracing::{info, warn}; use tracing::{info, warn};
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
use crate::util::output::{write_output, OutputFormat}; use crate::{
cmd::{apply_config_args, diff::ObjectConfig},
util::output::{OutputFormat, write_output},
};
#[derive(FromArgs, PartialEq, Debug)] #[derive(FromArgs, PartialEq, Debug)]
/// Generate a progress report for a project. /// Generate a progress report for a project.
@ -43,33 +40,36 @@ pub enum SubCommand {
/// Generate a progress report for a project. /// Generate a progress report for a project.
#[argp(subcommand, name = "generate")] #[argp(subcommand, name = "generate")]
pub struct GenerateArgs { pub struct GenerateArgs {
#[argp(option, short = 'p')] #[argp(option, short = 'p', from_str_fn(platform_path))]
/// Project directory /// Project directory
project: Option<PathBuf>, project: Option<Utf8PlatformPathBuf>,
#[argp(option, short = 'o')] #[argp(option, short = 'o', from_str_fn(platform_path))]
/// Output file /// Output file
output: Option<PathBuf>, output: Option<Utf8PlatformPathBuf>,
#[argp(switch, short = 'd')] #[argp(switch, short = 'd')]
/// Deduplicate global and weak symbols (runs single-threaded) /// Deduplicate global and weak symbols (runs single-threaded)
deduplicate: bool, deduplicate: bool,
#[argp(option, short = 'f')] #[argp(option, short = 'f')]
/// Output format (json, json-pretty, proto) (default: json) /// Output format (json, json-pretty, proto) (default: json)
format: Option<String>, format: Option<String>,
#[argp(option, short = 'c')]
/// Configuration property (key=value)
config: Vec<String>,
} }
#[derive(FromArgs, PartialEq, Debug)] #[derive(FromArgs, PartialEq, Debug)]
/// List any changes from a previous report. /// List any changes from a previous report.
#[argp(subcommand, name = "changes")] #[argp(subcommand, name = "changes")]
pub struct ChangesArgs { pub struct ChangesArgs {
#[argp(positional)] #[argp(positional, from_str_fn(platform_path))]
/// Previous report file /// Previous report file
previous: PathBuf, previous: Utf8PlatformPathBuf,
#[argp(positional)] #[argp(positional, from_str_fn(platform_path))]
/// Current report file /// Current report file
current: PathBuf, current: Utf8PlatformPathBuf,
#[argp(option, short = 'o')] #[argp(option, short = 'o', from_str_fn(platform_path))]
/// Output file /// Output file
output: Option<PathBuf>, output: Option<Utf8PlatformPathBuf>,
#[argp(option, short = 'f')] #[argp(option, short = 'f')]
/// Output format (json, json-pretty, proto) (default: json) /// Output format (json, json-pretty, proto) (default: json)
format: Option<String>, format: Option<String>,
@ -83,11 +83,20 @@ pub fn run(args: Args) -> Result<()> {
} }
fn generate(args: GenerateArgs) -> Result<()> { fn generate(args: GenerateArgs) -> Result<()> {
let output_format = OutputFormat::from_option(args.format.as_deref())?; let mut diff_config = diff::DiffObjConfig {
let project_dir = args.project.as_deref().unwrap_or_else(|| Path::new(".")); function_reloc_diffs: diff::FunctionRelocDiffs::None,
info!("Loading project {}", project_dir.display()); combine_data_sections: true,
combine_text_sections: true,
ppc_calculate_pool_relocations: false,
..Default::default()
};
apply_config_args(&mut diff_config, &args.config)?;
let mut project = match objdiff_core::config::try_project_config(project_dir) { let output_format = OutputFormat::from_option(args.format.as_deref())?;
let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new("."));
info!("Loading project {}", project_dir);
let project = match objdiff_core::config::try_project_config(project_dir.as_ref()) {
Some((Ok(config), _)) => config, Some((Ok(config), _)) => config,
Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err), Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err),
None => bail!("No project configuration found"), None => bail!("No project configuration found"),
@ -98,37 +107,34 @@ fn generate(args: GenerateArgs) -> Result<()> {
if args.deduplicate { 1 } else { rayon::current_num_threads() } if args.deduplicate { 1 } else { rayon::current_num_threads() }
); );
let target_obj_dir =
project.target_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
let base_obj_dir =
project.base_dir.as_ref().map(|p| project_dir.join(p.with_platform_encoding()));
let objects = project
.units
.iter()
.flatten()
.map(|o| {
ObjectConfig::new(o, project_dir, target_obj_dir.as_deref(), base_obj_dir.as_deref())
})
.collect::<Vec<_>>();
let start = Instant::now(); let start = Instant::now();
let mut units = vec![]; let mut units = vec![];
let mut existing_functions: HashSet<String> = HashSet::new(); let mut existing_functions: HashSet<String> = HashSet::new();
if args.deduplicate { if args.deduplicate {
// If deduplicating, we need to run single-threaded // If deduplicating, we need to run single-threaded
for object in project.units.as_deref_mut().unwrap_or_default() { for object in &objects {
if let Some(unit) = report_object( if let Some(unit) = report_object(object, &diff_config, Some(&mut existing_functions))?
object, {
project_dir,
project.target_dir.as_deref(),
project.base_dir.as_deref(),
Some(&mut existing_functions),
)? {
units.push(unit); units.push(unit);
} }
} }
} else { } else {
let vec = project let vec = objects
.units .par_iter()
.as_deref_mut() .map(|object| report_object(object, &diff_config, None))
.unwrap_or_default()
.par_iter_mut()
.map(|object| {
report_object(
object,
project_dir,
project.target_dir.as_deref(),
project.base_dir.as_deref(),
None,
)
})
.collect::<Result<Vec<Option<ReportUnit>>>>()?; .collect::<Result<Vec<Option<ReportUnit>>>>()?;
units = vec.into_iter().flatten().collect(); units = vec.into_iter().flatten().collect();
} }
@ -151,62 +157,49 @@ fn generate(args: GenerateArgs) -> Result<()> {
} }
fn report_object( fn report_object(
object: &mut ProjectObject, object: &ObjectConfig,
project_dir: &Path, diff_config: &diff::DiffObjConfig,
target_dir: Option<&Path>,
base_dir: Option<&Path>,
mut existing_functions: Option<&mut HashSet<String>>, mut existing_functions: Option<&mut HashSet<String>>,
) -> Result<Option<ReportUnit>> { ) -> Result<Option<ReportUnit>> {
object.resolve_paths(project_dir, target_dir, base_dir);
match (&object.target_path, &object.base_path) { match (&object.target_path, &object.base_path) {
(None, Some(_)) if !object.complete().unwrap_or(false) => { (None, Some(_)) if !object.complete.unwrap_or(false) => {
warn!("Skipping object without target: {}", object.name()); warn!("Skipping object without target: {}", object.name);
return Ok(None); return Ok(None);
} }
(None, None) => { (None, None) => {
warn!("Skipping object without target or base: {}", object.name()); warn!("Skipping object without target or base: {}", object.name);
return Ok(None); return Ok(None);
} }
_ => {} _ => {}
} }
let diff_config = diff::DiffObjConfig {
function_reloc_diffs: diff::FunctionRelocDiffs::None,
..Default::default()
};
let mapping_config = diff::MappingConfig::default(); let mapping_config = diff::MappingConfig::default();
let target = object let target = object
.target_path .target_path
.as_ref() .as_ref()
.map(|p| { .map(|p| {
obj::read::read(p, &diff_config) obj::read::read(p.as_ref(), diff_config).with_context(|| format!("Failed to open {p}"))
.with_context(|| format!("Failed to open {}", p.display()))
}) })
.transpose()?; .transpose()?;
let base = object let base = object
.base_path .base_path
.as_ref() .as_ref()
.map(|p| { .map(|p| {
obj::read::read(p, &diff_config) obj::read::read(p.as_ref(), diff_config).with_context(|| format!("Failed to open {p}"))
.with_context(|| format!("Failed to open {}", p.display()))
}) })
.transpose()?; .transpose()?;
let result = let result =
diff::diff_objs(&diff_config, &mapping_config, target.as_ref(), base.as_ref(), None)?; diff::diff_objs(target.as_ref(), base.as_ref(), None, diff_config, &mapping_config)?;
let metadata = ReportUnitMetadata { let metadata = ReportUnitMetadata {
complete: object.complete(), complete: object.metadata.complete,
module_name: target module_name: target
.as_ref() .as_ref()
.and_then(|o| o.split_meta.as_ref()) .and_then(|o| o.split_meta.as_ref())
.and_then(|m| m.module_name.clone()), .and_then(|m| m.module_name.clone()),
module_id: target.as_ref().and_then(|o| o.split_meta.as_ref()).and_then(|m| m.module_id), module_id: target.as_ref().and_then(|o| o.split_meta.as_ref()).and_then(|m| m.module_id),
source_path: object.metadata.as_ref().and_then(|m| m.source_path.clone()), source_path: object.metadata.source_path.as_ref().map(|p| p.to_string()),
progress_categories: object progress_categories: object.metadata.progress_categories.clone().unwrap_or_default(),
.metadata auto_generated: object.metadata.auto_generated,
.as_ref()
.and_then(|m| m.progress_categories.clone())
.unwrap_or_default(),
auto_generated: object.metadata.as_ref().and_then(|m| m.auto_generated),
}; };
let mut measures = Measures { total_units: 1, ..Default::default() }; let mut measures = Measures { total_units: 1, ..Default::default() };
let mut sections = vec![]; let mut sections = vec![];
@ -214,15 +207,16 @@ fn report_object(
let obj = target.as_ref().or(base.as_ref()).unwrap(); let obj = target.as_ref().or(base.as_ref()).unwrap();
let obj_diff = result.left.as_ref().or(result.right.as_ref()).unwrap(); let obj_diff = result.left.as_ref().or(result.right.as_ref()).unwrap();
for (section, section_diff) in obj.sections.iter().zip(&obj_diff.sections) { for ((section_idx, section), section_diff) in
obj.sections.iter().enumerate().zip(&obj_diff.sections)
{
if section.kind == SectionKind::Unknown {
continue;
}
let section_match_percent = section_diff.match_percent.unwrap_or_else(|| { let section_match_percent = section_diff.match_percent.unwrap_or_else(|| {
// Support cases where we don't have a target object, // Support cases where we don't have a target object,
// assume complete means 100% match // assume complete means 100% match
if object.complete().unwrap_or(false) { if object.complete.unwrap_or(false) { 100.0 } else { 0.0 }
100.0
} else {
0.0
}
}); });
sections.push(ReportItem { sections.push(ReportItem {
name: section.name.clone(), name: section.name.clone(),
@ -232,39 +226,39 @@ fn report_object(
demangled_name: None, demangled_name: None,
virtual_address: section.virtual_address, virtual_address: section.virtual_address,
}), }),
address: None,
}); });
match section.kind { match section.kind {
ObjSectionKind::Data | ObjSectionKind::Bss => { SectionKind::Data | SectionKind::Bss => {
measures.total_data += section.size; measures.total_data += section.size;
if section_match_percent == 100.0 { if section_match_percent == 100.0 {
measures.matched_data += section.size; measures.matched_data += section.size;
} }
continue; continue;
} }
ObjSectionKind::Code => (), _ => {}
} }
for (symbol, symbol_diff) in section.symbols.iter().zip(&section_diff.symbols) { for (symbol, symbol_diff) in obj.symbols.iter().zip(&obj_diff.symbols) {
if symbol.size == 0 || symbol.flags.0.contains(ObjSymbolFlags::Hidden) { if symbol.section != Some(section_idx)
|| symbol.size == 0
|| symbol.flags.contains(SymbolFlag::Hidden)
|| symbol.flags.contains(SymbolFlag::Ignored)
{
continue; continue;
} }
if let Some(existing_functions) = &mut existing_functions { if let Some(existing_functions) = &mut existing_functions
if (symbol.flags.0.contains(ObjSymbolFlags::Global) && (symbol.flags.contains(SymbolFlag::Global)
|| symbol.flags.0.contains(ObjSymbolFlags::Weak)) || symbol.flags.contains(SymbolFlag::Weak))
&& !existing_functions.insert(symbol.name.clone()) && !existing_functions.insert(symbol.name.clone())
{ {
continue; continue;
}
} }
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| { let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
// Support cases where we don't have a target object, // Support cases where we don't have a target object,
// assume complete means 100% match // assume complete means 100% match
if object.complete().unwrap_or(false) { if object.complete.unwrap_or(false) { 100.0 } else { 0.0 }
100.0
} else {
0.0
}
}); });
measures.fuzzy_match_percent += match_percent * symbol.size as f32; measures.fuzzy_match_percent += match_percent * symbol.size as f32;
measures.total_code += symbol.size; measures.total_code += symbol.size;
@ -279,6 +273,7 @@ fn report_object(
demangled_name: symbol.demangled_name.clone(), demangled_name: symbol.demangled_name.clone(),
virtual_address: symbol.virtual_address, virtual_address: symbol.virtual_address,
}), }),
address: symbol.address.checked_sub(section.address),
}); });
if match_percent == 100.0 { if match_percent == 100.0 {
measures.matched_functions += 1; measures.matched_functions += 1;
@ -286,6 +281,16 @@ fn report_object(
measures.total_functions += 1; measures.total_functions += 1;
} }
} }
sections.sort_by(|a, b| a.name.cmp(&b.name));
let reverse_fn_order = object.metadata.reverse_fn_order.unwrap_or(false);
functions.sort_by(|a, b| {
if reverse_fn_order {
b.address.unwrap_or(0).cmp(&a.address.unwrap_or(0))
} else {
a.address.unwrap_or(u64::MAX).cmp(&b.address.unwrap_or(u64::MAX))
}
.then_with(|| a.size.cmp(&b.size))
});
if metadata.complete.unwrap_or(false) { if metadata.complete.unwrap_or(false) {
measures.complete_code = measures.total_code; measures.complete_code = measures.total_code;
measures.complete_data = measures.total_data; measures.complete_data = measures.total_data;
@ -294,7 +299,7 @@ fn report_object(
measures.calc_fuzzy_match_percent(); measures.calc_fuzzy_match_percent();
measures.calc_matched_percent(); measures.calc_matched_percent();
Ok(Some(ReportUnit { Ok(Some(ReportUnit {
name: object.name().to_string(), name: object.name.clone(),
measures: Some(measures), measures: Some(measures),
sections, sections,
functions, functions,
@ -304,7 +309,7 @@ fn report_object(
fn changes(args: ChangesArgs) -> Result<()> { fn changes(args: ChangesArgs) -> Result<()> {
let output_format = OutputFormat::from_option(args.format.as_deref())?; let output_format = OutputFormat::from_option(args.format.as_deref())?;
let (previous, current) = if args.previous == Path::new("-") && args.current == Path::new("-") { let (previous, current) = if args.previous == "-" && args.current == "-" {
// Special case for comparing two reports from stdin // Special case for comparing two reports from stdin
let mut data = vec![]; let mut data = vec![];
std::io::stdin().read_to_end(&mut data)?; std::io::stdin().read_to_end(&mut data)?;
@ -419,15 +424,14 @@ fn process_new_items(items: &[ReportItem]) -> Vec<ChangeItem> {
.collect() .collect()
} }
fn read_report(path: &Path) -> Result<Report> { fn read_report(path: &Utf8PlatformPath) -> Result<Report> {
if path == Path::new("-") { if path == Utf8PlatformPath::new("-") {
let mut data = vec![]; let mut data = vec![];
std::io::stdin().read_to_end(&mut data)?; std::io::stdin().read_to_end(&mut data)?;
return Report::parse(&data).with_context(|| "Failed to load report from stdin"); return Report::parse(&data).with_context(|| "Failed to load report from stdin");
} }
let file = File::open(path).with_context(|| format!("Failed to open {}", path.display()))?; let file = File::open(path).with_context(|| format!("Failed to open {path}"))?;
let mmap = unsafe { memmap2::Mmap::map(&file) } let mmap =
.with_context(|| format!("Failed to map {}", path.display()))?; unsafe { memmap2::Mmap::map(&file) }.with_context(|| format!("Failed to map {path}"))?;
Report::parse(mmap.as_ref()) Report::parse(mmap.as_ref()).with_context(|| format!("Failed to load report {path}"))
.with_context(|| format!("Failed to load report {}", path.display()))
} }

View File

@ -1,3 +1,5 @@
#![allow(clippy::too_many_arguments)]
mod argp_version; mod argp_version;
mod cmd; mod cmd;
mod util; mod util;
@ -15,7 +17,7 @@ use anyhow::{Error, Result};
use argp::{FromArgValue, FromArgs}; use argp::{FromArgValue, FromArgs};
use enable_ansi_support::enable_ansi_support; use enable_ansi_support::enable_ansi_support;
use supports_color::Stream; use supports_color::Stream;
use tracing_subscriber::{filter::LevelFilter, EnvFilter}; use tracing_subscriber::{EnvFilter, filter::LevelFilter};
#[derive(Debug, Eq, PartialEq, Copy, Clone)] #[derive(Debug, Eq, PartialEq, Copy, Clone)]
enum LogLevel { enum LogLevel {

View File

@ -5,7 +5,7 @@ use std::{
path::Path, path::Path,
}; };
use anyhow::{bail, Context, Result}; use anyhow::{Context, Result, bail};
use tracing::info; use tracing::info;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
@ -34,9 +34,12 @@ impl OutputFormat {
} }
} }
pub fn write_output<T>(input: &T, output: Option<&Path>, format: OutputFormat) -> Result<()> pub fn write_output<T, P>(input: &T, output: Option<P>, format: OutputFormat) -> Result<()>
where T: serde::Serialize + prost::Message { where
match output { T: serde::Serialize + prost::Message,
P: AsRef<Path>,
{
match output.as_ref().map(|p| p.as_ref()) {
Some(output) if output != Path::new("-") => { Some(output) if output != Path::new("-") => {
info!("Writing to {}", output.display()); info!("Writing to {}", output.display());
let file = File::options() let file = File::options()

View File

@ -3,7 +3,7 @@ use std::{io::stdout, panic};
use crossterm::{ use crossterm::{
cursor::Show, cursor::Show,
event::DisableMouseCapture, event::DisableMouseCapture,
terminal::{disable_raw_mode, LeaveAlternateScreen}, terminal::{LeaveAlternateScreen, disable_raw_mode},
}; };
pub fn crossterm_panic_handler() { pub fn crossterm_panic_handler() {

View File

@ -1,16 +1,19 @@
use anyhow::{bail, Result}; use core::cmp::Ordering;
use anyhow::Result;
use crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers, MouseButton, MouseEventKind}; use crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers, MouseButton, MouseEventKind};
use objdiff_core::{ use objdiff_core::{
build::BuildStatus,
diff::{ diff::{
display::{display_diff, DiffText, HighlightKind}, DiffObjConfig, FunctionRelocDiffs, InstructionDiffKind, ObjectDiff, SymbolDiff,
FunctionRelocDiffs, ObjDiff, ObjInsDiffKind, ObjSymbolDiff, display::{DiffText, DiffTextColor, HighlightKind, display_row},
}, },
obj::{ObjInfo, ObjSectionKind, ObjSymbol, SymbolRef}, obj::Object,
}; };
use ratatui::{ use ratatui::{
Frame,
prelude::*, prelude::*,
widgets::{Block, Borders, Clear, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState}, widgets::{Block, Borders, Clear, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState},
Frame,
}; };
use super::{EventControlFlow, EventResult, UiView}; use super::{EventControlFlow, EventResult, UiView};
@ -28,9 +31,9 @@ pub struct FunctionDiffUi {
pub scroll_state_y: ScrollbarState, pub scroll_state_y: ScrollbarState,
pub per_page: usize, pub per_page: usize,
pub num_rows: usize, pub num_rows: usize,
pub left_sym: Option<SymbolRef>, pub left_sym: Option<usize>,
pub right_sym: Option<SymbolRef>, pub right_sym: Option<usize>,
pub prev_sym: Option<SymbolRef>, pub prev_sym: Option<usize>,
pub open_options: bool, pub open_options: bool,
pub three_way: bool, pub three_way: bool,
} }
@ -80,11 +83,11 @@ impl UiView for FunctionDiffUi {
f.render_widget(line_l, header_chunks[0]); f.render_widget(line_l, header_chunks[0]);
let mut line_r = Line::default(); let mut line_r = Line::default();
if let Some(percent) = if let Some(percent) = get_symbol(state.right_obj.as_ref(), self.right_sym)
get_symbol(state.right_obj.as_ref(), self.right_sym).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)),
)); ));
} }
@ -94,7 +97,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(
@ -106,13 +109,17 @@ impl UiView for FunctionDiffUi {
let mut left_text = None; let mut left_text = None;
let mut left_highlight = None; let mut left_highlight = None;
let mut max_width = 0; let mut max_width = 0;
if let Some((symbol, symbol_diff)) = get_symbol(state.left_obj.as_ref(), self.left_sym) { if let Some((obj, symbol_idx, symbol_diff)) =
get_symbol(state.left_obj.as_ref(), self.left_sym)
{
let mut text = Text::default(); let mut text = Text::default();
let rect = content_chunks[0].inner(Margin::new(0, 1)); let rect = content_chunks[0].inner(Margin::new(0, 1));
left_highlight = self.print_sym( left_highlight = self.print_sym(
&mut text, &mut text,
symbol, obj,
symbol_idx,
symbol_diff, symbol_diff,
&state.diff_obj_config,
rect, rect,
&self.left_highlight, &self.left_highlight,
result, result,
@ -120,18 +127,27 @@ impl UiView for FunctionDiffUi {
); );
max_width = max_width.max(text.width()); max_width = max_width.max(text.width());
left_text = Some(text); left_text = Some(text);
} else if let Some(status) = &state.left_status {
let mut text = Text::default();
self.print_build_status(&mut text, status);
max_width = max_width.max(text.width());
left_text = Some(text);
} }
let mut right_text = None; let mut right_text = None;
let mut right_highlight = None; let mut right_highlight = None;
let mut margin_text = None; let mut margin_text = None;
if let Some((symbol, symbol_diff)) = get_symbol(state.right_obj.as_ref(), self.right_sym) { if let Some((obj, symbol_idx, symbol_diff)) =
get_symbol(state.right_obj.as_ref(), self.right_sym)
{
let mut text = Text::default(); let mut text = Text::default();
let rect = content_chunks[2].inner(Margin::new(0, 1)); let rect = content_chunks[2].inner(Margin::new(0, 1));
right_highlight = self.print_sym( right_highlight = self.print_sym(
&mut text, &mut text,
symbol, obj,
symbol_idx,
symbol_diff, symbol_diff,
&state.diff_obj_config,
rect, rect,
&self.right_highlight, &self.right_highlight,
result, result,
@ -145,33 +161,40 @@ impl UiView for FunctionDiffUi {
let rect = content_chunks[1].inner(Margin::new(1, 1)); let rect = content_chunks[1].inner(Margin::new(1, 1));
self.print_margin(&mut text, symbol_diff, rect); self.print_margin(&mut text, symbol_diff, rect);
margin_text = Some(text); margin_text = Some(text);
} else if let Some(status) = &state.right_status {
let mut text = Text::default();
self.print_build_status(&mut text, status);
max_width = max_width.max(text.width());
right_text = Some(text);
} }
let mut prev_text = None; let mut prev_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((symbol, symbol_diff)) = get_symbol(state.prev_obj.as_ref(), self.prev_sym) && let Some((obj, symbol_idx, symbol_diff)) =
{ get_symbol(state.prev_obj.as_ref(), self.prev_sym)
let mut text = Text::default(); {
let rect = content_chunks[4].inner(Margin::new(0, 1)); let mut text = Text::default();
self.print_sym( let rect = content_chunks[4].inner(Margin::new(0, 1));
&mut text, self.print_sym(
symbol, &mut text,
symbol_diff, obj,
rect, symbol_idx,
&self.right_highlight, symbol_diff,
result, &state.diff_obj_config,
true, rect,
); &self.right_highlight,
max_width = max_width.max(text.width()); result,
prev_text = Some(text); true,
);
max_width = max_width.max(text.width());
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 =
@ -426,19 +449,21 @@ impl UiView for FunctionDiffUi {
fn reload(&mut self, state: &AppState) -> Result<()> { fn reload(&mut self, state: &AppState) -> Result<()> {
let left_sym = let left_sym =
state.left_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name)); state.left_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
let right_sym = let right_sym =
state.right_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name)); state.right_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
let prev_sym = let prev_sym =
state.prev_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name)); state.prev_obj.as_ref().and_then(|(o, _)| o.symbol_by_name(&self.symbol_name));
self.num_rows = match ( self.num_rows = match (
get_symbol(state.left_obj.as_ref(), left_sym), get_symbol(state.left_obj.as_ref(), left_sym),
get_symbol(state.right_obj.as_ref(), right_sym), get_symbol(state.right_obj.as_ref(), right_sym),
) { ) {
(Some((_l, ld)), Some((_r, rd))) => ld.instructions.len().max(rd.instructions.len()), (Some((_l, _ls, ld)), Some((_r, _rs, rd))) => {
(Some((_l, ld)), None) => ld.instructions.len(), ld.instruction_rows.len().max(rd.instruction_rows.len())
(None, Some((_r, rd))) => rd.instructions.len(), }
(None, None) => bail!("Symbol not found: {}", self.symbol_name), (Some((_l, _ls, ld)), None) => ld.instruction_rows.len(),
(None, Some((_r, _rs, rd))) => rd.instruction_rows.len(),
(None, None) => 0,
}; };
self.left_sym = left_sym; self.left_sym = left_sym;
self.right_sym = right_sym; self.right_sym = right_sym;
@ -480,113 +505,85 @@ impl FunctionDiffUi {
self.scroll_y += self.per_page / if half { 2 } else { 1 }; self.scroll_y += self.per_page / if half { 2 } else { 1 };
} }
#[allow(clippy::too_many_arguments)]
fn print_sym( fn print_sym(
&self, &self,
out: &mut Text<'static>, out: &mut Text<'static>,
symbol: &ObjSymbol, obj: &Object,
symbol_diff: &ObjSymbolDiff, symbol_index: usize,
symbol_diff: &SymbolDiff,
diff_config: &DiffObjConfig,
rect: Rect, rect: Rect,
highlight: &HighlightKind, highlight: &HighlightKind,
result: &EventResult, result: &EventResult,
only_changed: bool, only_changed: bool,
) -> Option<HighlightKind> { ) -> Option<HighlightKind> {
let base_addr = symbol.address;
let mut new_highlight = None; let mut new_highlight = None;
for (y, ins_diff) in symbol_diff for (y, ins_row) in symbol_diff
.instructions .instruction_rows
.iter() .iter()
.skip(self.scroll_y) .skip(self.scroll_y)
.take(rect.height as usize) .take(rect.height as usize)
.enumerate() .enumerate()
{ {
if only_changed && ins_diff.kind == ObjInsDiffKind::None { if only_changed && ins_row.kind == InstructionDiffKind::None {
out.lines.push(Line::default()); out.lines.push(Line::default());
continue; continue;
} }
let mut sx = rect.x; let mut sx = rect.x;
let sy = rect.y + y as u16; let sy = rect.y + y as u16;
let mut line = Line::default(); let mut line = Line::default();
display_diff(ins_diff, base_addr, |text| -> Result<()> { display_row(obj, symbol_index, ins_row, diff_config, |segment| {
let label_text; let highlight_kind = HighlightKind::from(&segment.text);
let mut base_color = match ins_diff.kind { let label_text = match segment.text {
ObjInsDiffKind::None DiffText::Basic(text) => text.to_string(),
| ObjInsDiffKind::OpMismatch DiffText::Line(num) => format!("{num} "),
| ObjInsDiffKind::ArgMismatch => Color::Gray, DiffText::Address(addr) => format!("{addr:x}:"),
ObjInsDiffKind::Replace => Color::Cyan, DiffText::Opcode(mnemonic, _op) => format!("{mnemonic} "),
ObjInsDiffKind::Delete => Color::Red, DiffText::Argument(arg) => arg.to_string(),
ObjInsDiffKind::Insert => Color::Green, DiffText::BranchDest(addr) => format!("{addr:x}"),
}; DiffText::Symbol(sym) => {
let mut pad_to = 0; sym.demangled_name.as_ref().unwrap_or(&sym.name).clone()
match text {
DiffText::Basic(text) => {
label_text = text.to_string();
}
DiffText::BasicColor(s, idx) => {
label_text = s.to_string();
base_color = COLOR_ROTATION[idx % COLOR_ROTATION.len()];
}
DiffText::Line(num) => {
label_text = format!("{num} ");
base_color = Color::DarkGray;
pad_to = 5;
}
DiffText::Address(addr) => {
label_text = format!("{:x}:", addr);
pad_to = 5;
}
DiffText::Opcode(mnemonic, _op) => {
label_text = mnemonic.to_string();
if ins_diff.kind == ObjInsDiffKind::OpMismatch {
base_color = Color::Blue;
}
pad_to = 8;
}
DiffText::Argument(arg, diff) => {
label_text = arg.to_string();
if let Some(diff) = diff {
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
}
}
DiffText::BranchDest(addr, diff) => {
label_text = format!("{addr:x}");
if let Some(diff) = diff {
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
}
}
DiffText::Symbol(sym, diff) => {
let name = sym.demangled_name.as_ref().unwrap_or(&sym.name);
label_text = name.clone();
if let Some(diff) = diff {
base_color = COLOR_ROTATION[diff.idx % COLOR_ROTATION.len()]
} else {
base_color = Color::White;
}
} }
DiffText::Addend(addend) => match addend.cmp(&0i64) {
Ordering::Greater => format!("+{addend:#x}"),
Ordering::Less => format!("-{:#x}", -addend),
_ => String::new(),
},
DiffText::Spacing(n) => { DiffText::Spacing(n) => {
line.spans.push(Span::raw(" ".repeat(n))); line.spans.push(Span::raw(" ".repeat(n as usize)));
sx += n as u16; sx += n as u16;
return Ok(()); return Ok(());
} }
DiffText::Eol => { DiffText::Eol => return Ok(()),
return Ok(()); };
}
}
let len = label_text.len(); let len = label_text.len();
let highlighted = *highlight == text; let highlighted =
if let Some((cx, cy)) = result.click_xy { highlight_kind != HighlightKind::None && *highlight == highlight_kind;
if cx >= sx && cx < sx + len as u16 && cy == sy { if let Some((cx, cy)) = result.click_xy
new_highlight = Some(text.into()); && cx >= sx
} && cx < sx + len as u16
&& cy == sy
{
new_highlight = Some(highlight_kind);
} }
let mut style = Style::new().fg(base_color); let mut style = Style::new().fg(match segment.color {
DiffTextColor::Normal => Color::Gray,
DiffTextColor::Dim => Color::DarkGray,
DiffTextColor::Bright => Color::White,
DiffTextColor::DataFlow => Color::LightCyan,
DiffTextColor::Replace => Color::Cyan,
DiffTextColor::Delete => Color::Red,
DiffTextColor::Insert => Color::Green,
DiffTextColor::Rotating(i) => COLOR_ROTATION[i as usize % COLOR_ROTATION.len()],
});
if highlighted { if highlighted {
style = style.bg(Color::DarkGray); style = style.bg(Color::DarkGray);
} }
line.spans.push(Span::styled(label_text, style)); line.spans.push(Span::styled(label_text, style));
sx += len as u16; sx += len as u16;
if pad_to > len { if segment.pad_to as usize > len {
let pad = (pad_to - len) as u16; let pad = (segment.pad_to as usize - len) as u16;
line.spans.push(Span::raw(" ".repeat(pad as usize))); line.spans.push(Span::raw(" ".repeat(pad as usize)));
sx += pad; sx += pad;
} }
@ -598,12 +595,13 @@ impl FunctionDiffUi {
new_highlight new_highlight
} }
fn print_margin(&self, out: &mut Text, symbol: &ObjSymbolDiff, rect: Rect) { fn print_margin(&self, out: &mut Text, symbol: &SymbolDiff, rect: Rect) {
for ins_diff in symbol.instructions.iter().skip(self.scroll_y).take(rect.height as usize) { for ins_row in symbol.instruction_rows.iter().skip(self.scroll_y).take(rect.height as usize)
if ins_diff.kind != ObjInsDiffKind::None { {
out.lines.push(Line::raw(match ins_diff.kind { if ins_row.kind != InstructionDiffKind::None {
ObjInsDiffKind::Delete => "<", out.lines.push(Line::raw(match ins_row.kind {
ObjInsDiffKind::Insert => ">", InstructionDiffKind::Delete => "<",
InstructionDiffKind::Insert => ">",
_ => "|", _ => "|",
})); }));
} else { } else {
@ -611,6 +609,18 @@ impl FunctionDiffUi {
} }
} }
} }
fn print_build_status<'a>(&self, out: &mut Text<'a>, status: &'a BuildStatus) {
if !status.cmdline.is_empty() {
out.lines.push(Line::styled(status.cmdline.clone(), Style::new().fg(Color::LightBlue)));
}
for line in status.stdout.lines() {
out.lines.push(Line::styled(line, Style::new().fg(Color::White)));
}
for line in status.stderr.lines() {
out.lines.push(Line::styled(line, Style::new().fg(Color::Red)));
}
}
} }
pub const COLOR_ROTATION: [Color; 7] = [ pub const COLOR_ROTATION: [Color; 7] = [
@ -635,24 +645,10 @@ pub fn match_percent_color(match_percent: f32) -> Color {
#[inline] #[inline]
fn get_symbol( fn get_symbol(
obj: Option<&(ObjInfo, ObjDiff)>, obj: Option<&(Object, ObjectDiff)>,
sym: Option<SymbolRef>, sym: Option<usize>,
) -> Option<(&ObjSymbol, &ObjSymbolDiff)> { ) -> Option<(&Object, usize, &SymbolDiff)> {
let (obj, diff) = obj?; let (obj, diff) = obj?;
let sym = sym?; let sym = sym?;
Some((obj.section_symbol(sym).1, diff.symbol_diff(sym))) Some((obj, sym, &diff.symbols[sym]))
}
fn find_function(obj: &ObjInfo, name: &str) -> Option<SymbolRef> {
for (section_idx, section) in obj.sections.iter().enumerate() {
if section.kind != ObjSectionKind::Code {
continue;
}
for (symbol_idx, symbol) in section.symbols.iter().enumerate() {
if symbol.name == name {
return Some(SymbolRef { section_idx, symbol_idx });
}
}
}
None
} }

View File

@ -12,54 +12,44 @@ A local diffing tool for decompilation projects.
""" """
documentation = "https://docs.rs/objdiff-core" documentation = "https://docs.rs/objdiff-core"
[lib]
crate-type = ["cdylib", "rlib"]
[features] [features]
default = ["std"]
all = [ all = [
# Features # Features
"bindings", "bindings",
"build", "build",
"config", "config",
"dwarf", "dwarf",
"serde",
# Architectures # Architectures
"arm",
"arm64",
"mips", "mips",
"ppc", "ppc",
"x86", "x86",
"arm", "superh"
"arm64",
] ]
# Implicit, used to check if any arch is enabled # Implicit, used to check if any arch is enabled
any-arch = [ any-arch = [
"config",
"dep:bimap",
"dep:byteorder",
"dep:flagset", "dep:flagset",
"dep:heck", "dep:heck",
"dep:log", "dep:log",
"dep:memmap2",
"dep:num-traits", "dep:num-traits",
"dep:prettyplease", "dep:prettyplease",
"dep:proc-macro2", "dep:proc-macro2",
"dep:quote", "dep:quote",
"dep:serde", "dep:regex",
"dep:serde_json",
"dep:similar", "dep:similar",
"dep:strum",
"dep:syn", "dep:syn",
"dep:encoding_rs"
] ]
bindings = [ bindings = [
"dep:pbjson",
"dep:pbjson-build",
"dep:prost", "dep:prost",
"dep:prost-build", "dep:prost-build",
"dep:serde",
"dep:serde_json",
] ]
build = [ build = [
"dep:notify", "dep:notify",
"dep:notify-debouncer-full", "dep:notify-debouncer-full",
"dep:path-slash",
"dep:reqwest", "dep:reqwest",
"dep:self_update", "dep:self_update",
"dep:shell-escape", "dep:shell-escape",
@ -68,24 +58,46 @@ build = [
"dep:winapi", "dep:winapi",
] ]
config = [ config = [
"dep:bimap",
"dep:filetime",
"dep:globset", "dep:globset",
"dep:semver", "dep:semver",
"dep:typed-path",
]
dwarf = [
"dep:gimli",
"dep:typed-arena",
]
serde = [
"dep:pbjson",
"dep:pbjson-build",
"dep:serde", "dep:serde",
"dep:serde_json", "dep:serde_json",
"dep:serde_yaml",
] ]
dwarf = ["dep:gimli"] std = [
"anyhow/std",
"flagset?/std",
"log?/std",
"num-traits?/std",
"object/std",
"prost?/std",
"serde?/std",
"similar?/std",
"typed-arena?/std",
"typed-path?/std",
"dep:filetime",
"dep:memmap2",
]
mips = [ mips = [
"any-arch", "any-arch",
"dep:cpp_demangle",
"dep:cwdemangle",
"dep:rabbitizer", "dep:rabbitizer",
] ]
ppc = [ ppc = [
"any-arch", "any-arch",
"dep:cwdemangle", "dep:cwdemangle",
"dep:cwextab", "dep:cwextab",
"dep:ppc750cl", "dep:powerpc",
"dep:rlwinmdec",
] ]
x86 = [ x86 = [
"any-arch", "any-arch",
@ -105,79 +117,70 @@ arm64 = [
"dep:yaxpeax-arch", "dep:yaxpeax-arch",
"dep:yaxpeax-arm", "dep:yaxpeax-arm",
] ]
wasm = [ superh = [
"any-arch", "any-arch",
"bindings",
"dep:console_error_panic_hook",
"dep:console_log",
"dep:log",
"dep:tsify-next",
"dep:wasm-bindgen",
] ]
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["all"] features = ["all"]
[dependencies] [dependencies]
anyhow = "1.0" anyhow = { version = "1.0", default-features = false }
bimap = { version = "0.6", features = ["serde"], optional = true }
byteorder = { version = "1.5", optional = true }
filetime = { version = "0.2", optional = true } filetime = { version = "0.2", optional = true }
flagset = { version = "0.4", optional = true } flagset = { version = "0.4", default-features = false, optional = true }
log = { version = "0.4", optional = true } itertools = { version = "0.14", default-features = false, features = ["use_alloc"] }
log = { version = "0.4", default-features = false, optional = true }
memmap2 = { version = "0.9", optional = true } memmap2 = { version = "0.9", optional = true }
num-traits = { version = "0.2", optional = true } num-traits = { version = "0.2", default-features = false, optional = true }
object = { version = "0.36", features = ["read_core", "std", "elf", "pe"], default-features = false } object = { version = "0.37", default-features = false, features = ["read_core", "elf", "coff"] }
pbjson = { version = "0.7", optional = true } pbjson = { version = "0.8", default-features = false, optional = true }
prost = { version = "0.13", optional = true } prost = { version = "0.14", default-features = false, features = ["derive"], optional = true }
serde = { version = "1.0", features = ["derive"], optional = true } regex = { version = "1.11", default-features = false, features = [], optional = true }
similar = { version = "2.6", default-features = false, optional = true } serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
strum = { version = "0.26", features = ["derive"], optional = true } similar = { git = "https://github.com/encounter/similar.git", branch = "no_std", default-features = false, features = ["hashbrown"], optional = true }
wasm-bindgen = { version = "0.2", optional = true } typed-path = { version = "0.11", default-features = false, optional = true }
tsify-next = { version = "0.5", default-features = false, features = ["js"], optional = true }
console_log = { version = "1.0", optional = true }
console_error_panic_hook = { version = "0.1", optional = true }
# config # config
globset = { version = "0.4", features = ["serde1"], optional = true } globset = { version = "0.4", default-features = false, optional = true }
semver = { version = "1.0", optional = true } semver = { version = "1.0", default-features = false, optional = true }
serde_json = { version = "1.0", optional = true } serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true }
serde_yaml = { version = "0.9", optional = true }
# dwarf # dwarf
gimli = { version = "0.31", default-features = false, features = ["read-all"], optional = true } gimli = { git = "https://github.com/gimli-rs/gimli", rev = "7335f00e7c39fd501511584fefb0ba974117c950", default-features = false, features = ["read"], optional = true }
typed-arena = { version = "2.0", default-features = false, optional = true }
# ppc # ppc
cwdemangle = { version = "1.0", optional = true } cwdemangle = { version = "1.0", optional = true }
cwextab = { version = "1.0", optional = true } cwextab = { version = "1.1", optional = true }
ppc750cl = { version = "0.3", optional = true } powerpc = { version = "0.4", optional = true }
rlwinmdec = { version = "1.1", optional = true }
# mips # mips
rabbitizer = { version = "1.12", optional = true } rabbitizer = { version = "2.0.0-alpha.4", default-features = false, features = ["all_extensions"], optional = true }
# x86 # x86
cpp_demangle = { version = "0.4", optional = true } cpp_demangle = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
iced-x86 = { version = "1.21", default-features = false, features = ["std", "decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums"], optional = true } iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true }
msvc-demangler = { version = "0.10", optional = true } msvc-demangler = { version = "0.11", optional = true }
# arm # arm
unarm = { version = "1.6", optional = true } unarm = { version = "1.9", optional = true }
arm-attr = { version = "0.1", optional = true } arm-attr = { version = "0.2", optional = true }
# arm64 # arm64
yaxpeax-arch = { version = "0.3", default-features = false, features = ["std"], optional = true } yaxpeax-arch = { version = "0.3", default-features = false, optional = true }
yaxpeax-arm = { version = "0.3", default-features = false, features = ["std"], optional = true } yaxpeax-arm = { version = "0.3", default-features = false, optional = true }
# build # build
notify = { version = "8.0.0", optional = true } notify = { version = "8.1.0", optional = true }
notify-debouncer-full = { version = "0.5.0", optional = true } notify-debouncer-full = { version = "0.5.0", optional = true }
shell-escape = { version = "0.1", optional = true } shell-escape = { version = "0.1", optional = true }
tempfile = { version = "3.15", optional = true } tempfile = { version = "3.20", optional = true }
time = { version = "0.3", optional = true } time = { version = "0.3", optional = true }
encoding_rs = { version = "0.8.35", optional = true }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
path-slash = { version = "0.2", optional = true } winapi = { version = "0.3", optional = true, features = ["winbase"] }
winapi = { version = "0.3", optional = true }
# For Linux static binaries, use rustls # For Linux static binaries, use rustls
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
@ -191,11 +194,16 @@ self_update = { version = "0.42", 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.8", 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"], optional = true } serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", optional = true } serde_json = { version = "1.0" }
syn = { version = "2.0", optional = true } syn = { version = "2.0", optional = true }
[dev-dependencies]
# Enable all features for tests
objdiff-core = { path = ".", features = ["all"] }
insta = "1.43"

View File

@ -5,11 +5,12 @@ objdiff-core contains the core functionality of [objdiff](https://github.com/enc
## Crate feature flags ## Crate feature flags
- **`all`**: Enables all main features. - **`all`**: Enables all main features.
- **`bindings`**: Enables serialization and deserialization of objdiff data structures.
- **`config`**: Enables objdiff configuration file support. - **`config`**: Enables objdiff configuration file support.
- **`dwarf`**: Enables extraction of line number information from DWARF debug sections. - **`dwarf`**: Enables extraction of line number information from DWARF debug sections.
- **`mips`**: Enables the MIPS backend powered by [rabbitizer](https://github.com/Decompollaborate/rabbitizer). (Note: C library with Rust bindings)
- **`ppc`**: Enables the PowerPC backend powered by [ppc750cl](https://github.com/encounter/ppc750cl).
- **`x86`**: Enables the x86 backend powered by [iced-x86](https://crates.io/crates/iced-x86).
- **`arm`**: Enables the ARM backend powered by [unarm](https://github.com/AetiasHax/unarm).
- **`arm64`**: Enables the ARM64 backend powered by [yaxpeax-arm](https://github.com/iximeow/yaxpeax-arm). - **`arm64`**: Enables the ARM64 backend powered by [yaxpeax-arm](https://github.com/iximeow/yaxpeax-arm).
- **`bindings`**: Enables serialization and deserialization of objdiff data structures. - **`arm`**: Enables the ARM backend powered by [unarm](https://github.com/AetiasHax/unarm).
- **`mips`**: Enables the MIPS backend powered by [rabbitizer](https://github.com/Decompollaborate/rabbitizer).
- **`ppc`**: Enables the PowerPC backend powered by [powerpc](https://github.com/encounter/powerpc-rs).
- **`superh`**: Enables the SuperH backend powered by an included disassembler.
- **`x86`**: Enables the x86 backend powered by [iced-x86](https://crates.io/crates/iced-x86).

View File

@ -1,11 +1,12 @@
#[cfg(feature = "any-arch")] #[cfg(feature = "any-arch")]
mod config_gen; mod config_gen;
fn main() { fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(feature = "bindings")] #[cfg(feature = "bindings")]
compile_protos(); compile_protos();
#[cfg(feature = "any-arch")] #[cfg(feature = "any-arch")]
config_gen::generate_diff_config(); config_gen::generate_diff_config();
Ok(())
} }
#[cfg(feature = "bindings")] #[cfg(feature = "bindings")]
@ -18,7 +19,15 @@ fn compile_protos() {
.map(|m| m.modified().unwrap()) .map(|m| m.modified().unwrap())
.unwrap_or(std::time::SystemTime::UNIX_EPOCH); .unwrap_or(std::time::SystemTime::UNIX_EPOCH);
let mut run_protoc = false; let mut run_protoc = false;
let proto_files = vec![root.join("diff.proto"), root.join("report.proto")]; let proto_files = root
.read_dir()
.unwrap()
.filter_map(|e| {
let e = e.unwrap();
let path = e.path();
(path.extension() == Some(std::ffi::OsStr::new("proto"))).then_some(path)
})
.collect::<Vec<_>>();
for proto_file in &proto_files { for proto_file in &proto_files {
println!("cargo:rerun-if-changed={}", proto_file.display()); println!("cargo:rerun-if-changed={}", proto_file.display());
let mtime = match std::fs::metadata(proto_file) { let mtime = match std::fs::metadata(proto_file) {
@ -54,11 +63,14 @@ fn compile_protos() {
} }
} }
let descriptor_set = std::fs::read(descriptor_path).expect("Failed to read descriptor set"); #[cfg(feature = "serde")]
pbjson_build::Builder::new() {
.register_descriptors(&descriptor_set) let descriptor_set = std::fs::read(descriptor_path).expect("Failed to read descriptor set");
.expect("Failed to register descriptors") pbjson_build::Builder::new()
.preserve_proto_field_names() .register_descriptors(&descriptor_set)
.build(&[".objdiff"]) .expect("Failed to register descriptors")
.expect("Failed to build pbjson"); .preserve_proto_field_names()
.build(&[".objdiff"])
.expect("Failed to build pbjson");
}
} }

View File

@ -25,6 +25,20 @@
} }
] ]
}, },
{
"id": "analyzeDataFlow",
"type": "boolean",
"default": false,
"name": "(Experimental) Perform data flow analysis",
"description": "Use data flow analysis to display known information about register contents where possible"
},
{
"id": "showDataFlow",
"type": "boolean",
"default": true,
"name": "Show data flow",
"description": "Show data flow analysis results in place of register name where present"
},
{ {
"id": "spaceBetweenArgs", "id": "spaceBetweenArgs",
"type": "boolean", "type": "boolean",
@ -39,6 +53,13 @@
"name": "Combine data sections", "name": "Combine data sections",
"description": "Combines data sections with equal names." "description": "Combines data sections with equal names."
}, },
{
"id": "combineTextSections",
"type": "boolean",
"default": false,
"name": "Combine text sections",
"description": "Combines all text sections into one."
},
{ {
"id": "arm.archVersion", "id": "arm.archVersion",
"type": "choice", "type": "choice",
@ -180,6 +201,20 @@
} }
] ]
}, },
{
"id": "mips.registerPrefix",
"type": "boolean",
"default": false,
"name": "Register '$' prefix",
"description": "Display MIPS register names with a '$' prefix."
},
{
"id": "ppc.calculatePoolRelocations",
"type": "boolean",
"default": true,
"name": "Calculate pooled data references",
"description": "Display pooled data references in functions as fake relocations."
},
{ {
"id": "x86.formatter", "id": "x86.formatter",
"type": "choice", "type": "choice",
@ -213,7 +248,8 @@
"properties": [ "properties": [
"functionRelocDiffs", "functionRelocDiffs",
"spaceBetweenArgs", "spaceBetweenArgs",
"combineDataSections" "combineDataSections",
"combineTextSections"
] ]
}, },
{ {
@ -234,7 +270,16 @@
"name": "MIPS", "name": "MIPS",
"properties": [ "properties": [
"mips.abi", "mips.abi",
"mips.instrCategory" "mips.instrCategory",
"mips.registerPrefix"
]
},
{
"id": "ppc",
"name": "PowerPC",
"properties": [
"ppc.calculatePoolRelocations",
"analyzeDataFlow"
] ]
}, },
{ {
@ -245,4 +290,4 @@
] ]
} }
] ]
} }

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] });
} }
@ -100,7 +100,7 @@ pub fn generate_diff_config() {
} }
let value = &item.value; let value = &item.value;
variants.extend(quote! { variants.extend(quote! {
#[serde(rename = #value, alias = #variant_name)] #[cfg_attr(feature = "serde", serde(rename = #value, alias = #variant_name))]
#variant_ident, #variant_ident,
}); });
full_variants.extend(quote! { #enum_ident::#variant_ident, }); full_variants.extend(quote! { #enum_ident::#variant_ident, });
@ -134,8 +134,8 @@ pub fn generate_diff_config() {
}); });
} }
enums.extend(quote! { enums.extend(quote! {
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash, serde::Deserialize, serde::Serialize)] #[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum #enum_ident { pub enum #enum_ident {
#variants #variants
} }
@ -168,7 +168,7 @@ pub fn generate_diff_config() {
} }
} }
} }
impl std::str::FromStr for #enum_ident { impl core::str::FromStr for #enum_ident {
type Err = (); type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
#variant_from_str #variant_from_str
@ -244,7 +244,7 @@ pub fn generate_diff_config() {
let default = b.default; let default = b.default;
if default { if default {
property_fields.extend(quote! { property_fields.extend(quote! {
#[serde(default = "default_true")] #[cfg_attr(feature = "serde", serde(default = "default_true"))]
}); });
} }
property_fields.extend(quote! { property_fields.extend(quote! {
@ -412,7 +412,7 @@ pub fn generate_diff_config() {
} }
} }
} }
impl std::str::FromStr for ConfigPropertyId { impl core::str::FromStr for ConfigPropertyId {
type Err = (); type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
#config_property_id_from_str #config_property_id_from_str
@ -433,6 +433,7 @@ pub fn generate_diff_config() {
Choice(&'static str), Choice(&'static str),
} }
impl ConfigPropertyValue { impl ConfigPropertyValue {
#[cfg(feature = "serde")]
pub fn to_json(&self) -> serde_json::Value { pub fn to_json(&self) -> serde_json::Value {
match self { match self {
ConfigPropertyValue::Boolean(value) => serde_json::Value::Bool(*value), ConfigPropertyValue::Boolean(value) => serde_json::Value::Bool(*value),
@ -440,17 +441,25 @@ pub fn generate_diff_config() {
} }
} }
} }
impl core::fmt::Display for ConfigPropertyValue {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match *self {
ConfigPropertyValue::Boolean(value) => write!(f, "{value}"),
ConfigPropertyValue::Choice(value) => f.write_str(value),
}
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum ConfigPropertyKind { pub enum ConfigPropertyKind {
Boolean, Boolean,
Choice(&'static [ConfigEnumVariantInfo]), Choice(&'static [ConfigEnumVariantInfo]),
} }
#enums #enums
#[cfg(feature = "serde")]
#[inline(always)] #[inline(always)]
fn default_true() -> bool { true } fn default_true() -> bool { true }
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[derive(Clone, Debug)]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(default))]
#[serde(default)]
pub struct DiffObjConfig { pub struct DiffObjConfig {
#property_fields #property_fields
} }

View File

@ -0,0 +1,59 @@
syntax = "proto3";
import "report.proto";
package objdiff.report;
// A pair of reports to compare and generate changes
message ChangesInput {
// The previous report
Report from = 1;
// The current report
Report to = 2;
}
// Changes between two reports
message Changes {
// The progress info for the previous report
Measures from = 1;
// The progress info for the current report
Measures to = 2;
// Units that changed
repeated ChangeUnit units = 3;
}
// A changed unit
message ChangeUnit {
// The name of the unit
string name = 1;
// The previous progress info (omitted if new)
optional Measures from = 2;
// The current progress info (omitted if removed)
optional Measures to = 3;
// Sections that changed
repeated ChangeItem sections = 4;
// Functions that changed
repeated ChangeItem functions = 5;
// Extra metadata for this unit
optional ReportUnitMetadata metadata = 6;
}
// A changed section or function
message ChangeItem {
// The name of the item
string name = 1;
// The previous progress info (omitted if new)
optional ChangeItemInfo from = 2;
// The current progress info (omitted if removed)
optional ChangeItemInfo to = 3;
// Extra metadata for this item
optional ReportItemMetadata metadata = 4;
}
// Progress info for a section or function
message ChangeItemInfo {
// The overall match percent for this item
float fuzzy_match_percent = 1;
// The size of the item in bytes
uint64 size = 2;
}

View File

@ -1,170 +0,0 @@
syntax = "proto3";
package objdiff.diff;
// A symbol
message Symbol {
// Name of the symbol
string name = 1;
// Demangled name of the symbol
optional string demangled_name = 2;
// Symbol address
uint64 address = 3;
// Symbol size
uint64 size = 4;
// Bitmask of SymbolFlag
uint32 flags = 5;
}
// Symbol visibility flags
enum SymbolFlag {
SYMBOL_NONE = 0;
SYMBOL_GLOBAL = 1;
SYMBOL_LOCAL = 2;
SYMBOL_WEAK = 4;
SYMBOL_COMMON = 8;
SYMBOL_HIDDEN = 16;
}
// A single parsed instruction
message Instruction {
// Instruction address
uint64 address = 1;
// Instruction size
uint32 size = 2;
// Instruction opcode
uint32 opcode = 3;
// Instruction mnemonic
string mnemonic = 4;
// Instruction formatted string
string formatted = 5;
// Original (unsimplified) instruction string
optional string original = 6;
// Instruction arguments
repeated Argument arguments = 7;
// Instruction relocation
optional Relocation relocation = 8;
// Instruction branch destination
optional uint64 branch_dest = 9;
// Instruction line number
optional uint32 line_number = 10;
}
// An instruction argument
message Argument {
oneof value {
// Plain text
string plain_text = 1;
// Value
ArgumentValue argument = 2;
// Relocation
ArgumentRelocation relocation = 3;
// Branch destination
uint64 branch_dest = 4;
}
}
// An instruction argument value
message ArgumentValue {
oneof value {
// Signed integer
int64 signed = 1;
// Unsigned integer
uint64 unsigned = 2;
// Opaque value
string opaque = 3;
}
}
// Marker type for relocation arguments
message ArgumentRelocation {
}
message Relocation {
uint32 type = 1;
string type_name = 2;
RelocationTarget target = 3;
}
message RelocationTarget {
Symbol symbol = 1;
int64 addend = 2;
}
message InstructionDiff {
DiffKind diff_kind = 1;
optional Instruction instruction = 2;
optional InstructionBranchFrom branch_from = 3;
optional InstructionBranchTo branch_to = 4;
repeated ArgumentDiff arg_diff = 5;
}
message ArgumentDiff {
optional uint32 diff_index = 1;
}
enum DiffKind {
DIFF_NONE = 0;
DIFF_REPLACE = 1;
DIFF_DELETE = 2;
DIFF_INSERT = 3;
DIFF_OP_MISMATCH = 4;
DIFF_ARG_MISMATCH = 5;
}
message InstructionBranchFrom {
repeated uint32 instruction_index = 1;
uint32 branch_index = 2;
}
message InstructionBranchTo {
uint32 instruction_index = 1;
uint32 branch_index = 2;
}
message SymbolRef {
optional uint32 section_index = 1;
uint32 symbol_index = 2;
}
message SymbolDiff {
Symbol symbol = 1;
repeated InstructionDiff instructions = 2;
optional float match_percent = 3;
// The symbol ref in the _other_ object that this symbol was diffed against
optional SymbolRef target = 5;
}
message DataDiff {
DiffKind kind = 1;
bytes data = 2;
// May be larger than data
uint64 size = 3;
}
message SectionDiff {
string name = 1;
SectionKind kind = 2;
uint64 size = 3;
uint64 address = 4;
repeated SymbolDiff symbols = 5;
repeated DataDiff data = 6;
optional float match_percent = 7;
}
enum SectionKind {
SECTION_UNKNOWN = 0;
SECTION_TEXT = 1;
SECTION_DATA = 2;
SECTION_BSS = 3;
SECTION_COMMON = 4;
}
message ObjectDiff {
repeated SectionDiff sections = 1;
}
message DiffResult {
optional ObjectDiff left = 1;
optional ObjectDiff right = 2;
}

View File

@ -2,6 +2,18 @@ syntax = "proto3";
package objdiff.report; package objdiff.report;
// Project progress report
message Report {
// Overall progress info
Measures measures = 1;
// Units within this report
repeated ReportUnit units = 2;
// Report version
uint32 version = 3;
// Progress categories
repeated ReportCategory categories = 4;
}
// Progress info for a report or unit // Progress info for a report or unit
message Measures { message Measures {
// Overall match percent, including partially matched functions and data // Overall match percent, including partially matched functions and data
@ -38,18 +50,6 @@ message Measures {
uint32 complete_units = 16; uint32 complete_units = 16;
} }
// Project progress report
message Report {
// Overall progress info
Measures measures = 1;
// Units within this report
repeated ReportUnit units = 2;
// Report version
uint32 version = 3;
// Progress categories
repeated ReportCategory categories = 4;
}
message ReportCategory { message ReportCategory {
// The ID of the category // The ID of the category
string id = 1; string id = 1;
@ -99,6 +99,8 @@ message ReportItem {
float fuzzy_match_percent = 3; float fuzzy_match_percent = 3;
// Extra metadata for this item // Extra metadata for this item
optional ReportItemMetadata metadata = 4; optional ReportItemMetadata metadata = 4;
// Address of the item (section-relative offset)
optional uint64 address = 5;
} }
// Extra metadata for an item // Extra metadata for an item
@ -108,57 +110,3 @@ message ReportItemMetadata {
// The virtual address of the function or section // The virtual address of the function or section
optional uint64 virtual_address = 2; optional uint64 virtual_address = 2;
} }
// A pair of reports to compare and generate changes
message ChangesInput {
// The previous report
Report from = 1;
// The current report
Report to = 2;
}
// Changes between two reports
message Changes {
// The progress info for the previous report
Measures from = 1;
// The progress info for the current report
Measures to = 2;
// Units that changed
repeated ChangeUnit units = 3;
}
// A changed unit
message ChangeUnit {
// The name of the unit
string name = 1;
// The previous progress info (omitted if new)
optional Measures from = 2;
// The current progress info (omitted if removed)
optional Measures to = 3;
// Sections that changed
repeated ChangeItem sections = 4;
// Functions that changed
repeated ChangeItem functions = 5;
// Extra metadata for this unit
optional ReportUnitMetadata metadata = 6;
}
// A changed section or function
message ChangeItem {
// The name of the item
string name = 1;
// The previous progress info (omitted if new)
optional ChangeItemInfo from = 2;
// The current progress info (omitted if removed)
optional ChangeItemInfo to = 3;
// Extra metadata for this item
optional ReportItemMetadata metadata = 4;
}
// Progress info for a section or function
message ChangeItemInfo {
// The overall match percent for this item
float fuzzy_match_percent = 1;
// The size of the item in bytes
uint64 size = 2;
}

View File

@ -1,40 +1,39 @@
use std::{ use alloc::{
borrow::Cow, collections::BTreeMap,
collections::{BTreeMap, HashMap}, format,
string::{String, ToString},
vec::Vec,
}; };
use anyhow::{bail, Result}; use anyhow::{Result, bail};
use arm_attr::{enums::CpuArch, tag::Tag, BuildAttrs}; use arm_attr::{BuildAttrs, enums::CpuArch, tag::Tag};
use object::{ use object::{Endian as _, Object as _, ObjectSection as _, ObjectSymbol as _, elf};
elf::{self, SHT_ARM_ATTRIBUTES}, use unarm::{args, arm, thumb};
Endian, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, SectionIndex,
SectionKind, Symbol, SymbolKind,
};
use unarm::{
args::{Argument, OffsetImm, OffsetReg, Register},
parse::{ArmVersion, ParseMode, Parser},
DisplayOptions, ParseFlags, ParsedIns, RegNames,
};
use crate::{ use crate::{
arch::{ObjArch, ProcessCodeResult}, arch::{Arch, RelocationOverride, RelocationOverrideTarget},
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig}, diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig, display::InstructionPart},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection}, obj::{
InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation,
Section, SectionKind, Symbol, SymbolFlag, SymbolFlagSet, SymbolKind,
},
}; };
pub struct ObjArchArm { #[derive(Debug)]
pub struct ArchArm {
/// Maps section index, to list of disasm modes (arm, thumb or data) sorted by address /// Maps section index, to list of disasm modes (arm, thumb or data) sorted by address
disasm_modes: HashMap<SectionIndex, Vec<DisasmMode>>, disasm_modes: BTreeMap<usize, Vec<DisasmMode>>,
detected_version: Option<ArmVersion>, detected_version: Option<unarm::ArmVersion>,
endianness: object::Endianness, endianness: object::Endianness,
} }
impl ObjArchArm { impl ArchArm {
pub fn new(file: &File) -> Result<Self> { pub fn new(file: &object::File) -> Result<Self> {
let endianness = file.endianness(); let endianness = file.endianness();
match file { match file {
File::Elf32(_) => { object::File::Elf32(_) => {
let disasm_modes = Self::elf_get_mapping_symbols(file); // The disasm_modes mapping is populated later in the post_init step so that we have access to merged sections.
let disasm_modes = BTreeMap::new();
let detected_version = Self::elf_detect_arm_version(file)?; let detected_version = Self::elf_detect_arm_version(file)?;
Ok(Self { disasm_modes, detected_version, endianness }) Ok(Self { disasm_modes, detected_version, endianness })
} }
@ -42,10 +41,11 @@ impl ObjArchArm {
} }
} }
fn elf_detect_arm_version(file: &File) -> Result<Option<ArmVersion>> { fn elf_detect_arm_version(file: &object::File) -> Result<Option<unarm::ArmVersion>> {
// Check ARM attributes // Check ARM attributes
if let Some(arm_attrs) = file.sections().find(|s| { if let Some(arm_attrs) = file.sections().find(|s| {
s.kind() == SectionKind::Elf(SHT_ARM_ATTRIBUTES) && s.name() == Ok(".ARM.attributes") s.kind() == object::SectionKind::Elf(elf::SHT_ARM_ATTRIBUTES)
&& s.name() == Ok(".ARM.attributes")
}) { }) {
let attr_data = arm_attrs.uncompressed_data()?; let attr_data = arm_attrs.uncompressed_data()?;
let build_attrs = BuildAttrs::new(&attr_data, match file.endianness() { let build_attrs = BuildAttrs::new(&attr_data, match file.endianness() {
@ -59,16 +59,12 @@ impl ObjArchArm {
} }
// Only checking first CpuArch tag. Others may exist, but that's very unlikely. // Only checking first CpuArch tag. Others may exist, but that's very unlikely.
let cpu_arch = subsection.into_public_tag_iter()?.find_map(|(_, tag)| { let cpu_arch = subsection.into_public_tag_iter()?.find_map(|(_, tag)| {
if let Tag::CpuArch(cpu_arch) = tag { if let Tag::CpuArch(cpu_arch) = tag { Some(cpu_arch) } else { None }
Some(cpu_arch)
} else {
None
}
}); });
match cpu_arch { match cpu_arch {
Some(CpuArch::V4T) => return Ok(Some(ArmVersion::V4T)), Some(CpuArch::V4T) => return Ok(Some(unarm::ArmVersion::V4T)),
Some(CpuArch::V5TE) => return Ok(Some(ArmVersion::V5Te)), Some(CpuArch::V5TE) => return Ok(Some(unarm::ArmVersion::V5Te)),
Some(CpuArch::V6K) => return Ok(Some(ArmVersion::V6K)), Some(CpuArch::V6K) => return Ok(Some(unarm::ArmVersion::V6K)),
Some(arch) => bail!("ARM arch {} not supported", arch), Some(arch) => bail!("ARM arch {} not supported", arch),
None => {} None => {}
}; };
@ -78,193 +74,339 @@ impl ObjArchArm {
Ok(None) Ok(None)
} }
fn elf_get_mapping_symbols(file: &File) -> HashMap<SectionIndex, Vec<DisasmMode>> { fn get_mapping_symbols(
file.sections() sections: &[Section],
.filter(|s| s.kind() == SectionKind::Text) symbols: &[Symbol],
.map(|s| { ) -> BTreeMap<usize, Vec<DisasmMode>> {
let index = s.index(); sections
let mut mapping_symbols: Vec<_> = file .iter()
.symbols() .enumerate()
.filter(|s| s.section_index().map(|i| i == index).unwrap_or(false)) .filter(|(_, section)| section.kind == SectionKind::Code)
.filter_map(|s| DisasmMode::from_symbol(&s)) .map(|(index, _)| {
let mut mapping_symbols: Vec<_> = symbols
.iter()
.filter(|s| s.section.map(|i| i == index).unwrap_or(false))
.filter_map(DisasmMode::from_symbol)
.collect(); .collect();
mapping_symbols.sort_unstable_by_key(|x| x.address); mapping_symbols.sort_unstable_by_key(|x| x.address);
(s.index(), mapping_symbols) (index, mapping_symbols)
}) })
.collect() .collect()
} }
}
impl ObjArch for ObjArchArm { fn parse_flags(&self, diff_config: &DiffObjConfig) -> unarm::ParseFlags {
fn symbol_address(&self, symbol: &Symbol) -> u64 { unarm::ParseFlags {
let address = symbol.address(); ual: diff_config.arm_unified_syntax,
if symbol.kind() == SymbolKind::Text { version: match diff_config.arm_arch_version {
address & !1 ArmArchVersion::Auto => self.detected_version.unwrap_or(unarm::ArmVersion::V5Te),
} else { ArmArchVersion::V4t => unarm::ArmVersion::V4T,
address ArmArchVersion::V5te => unarm::ArmVersion::V5Te,
ArmArchVersion::V6k => unarm::ArmVersion::V6K,
},
} }
} }
fn process_code( fn display_options(&self, diff_config: &DiffObjConfig) -> unarm::DisplayOptions {
&self, unarm::DisplayOptions {
address: u64, reg_names: unarm::RegNames {
code: &[u8], av_registers: diff_config.arm_av_registers,
section_index: usize, r9_use: match diff_config.arm_r9_usage {
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
let start_addr = address as u32;
let end_addr = start_addr + code.len() as u32;
// Mapping symbols decide what kind of data comes after it. $a for ARM code, $t for Thumb code and $d for data.
let fallback_mappings = [DisasmMode { address: start_addr, mapping: ParseMode::Arm }];
let mapping_symbols = self
.disasm_modes
.get(&SectionIndex(section_index))
.map(|x| x.as_slice())
.unwrap_or(&fallback_mappings);
let first_mapping_idx = mapping_symbols
.binary_search_by_key(&start_addr, |x| x.address)
.unwrap_or_else(|idx| idx - 1);
let first_mapping = mapping_symbols[first_mapping_idx].mapping;
let mut mappings_iter =
mapping_symbols.iter().skip(first_mapping_idx + 1).take_while(|x| x.address < end_addr);
let mut next_mapping = mappings_iter.next();
let ins_count = code.len() / first_mapping.instruction_size(start_addr);
let mut ops = Vec::<u16>::with_capacity(ins_count);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
let version = match config.arm_arch_version {
ArmArchVersion::Auto => self.detected_version.unwrap_or(ArmVersion::V5Te),
ArmArchVersion::V4t => ArmVersion::V4T,
ArmArchVersion::V5te => ArmVersion::V5Te,
ArmArchVersion::V6k => ArmVersion::V6K,
};
let endian = match self.endianness {
object::Endianness::Little => unarm::Endian::Little,
object::Endianness::Big => unarm::Endian::Big,
};
let parse_flags = ParseFlags { ual: config.arm_unified_syntax, version };
let mut parser = Parser::new(first_mapping, start_addr, endian, parse_flags, code);
let display_options = DisplayOptions {
reg_names: RegNames {
av_registers: config.arm_av_registers,
r9_use: match config.arm_r9_usage {
ArmR9Usage::GeneralPurpose => unarm::R9Use::GeneralPurpose, ArmR9Usage::GeneralPurpose => unarm::R9Use::GeneralPurpose,
ArmR9Usage::Sb => unarm::R9Use::Pid, ArmR9Usage::Sb => unarm::R9Use::Pid,
ArmR9Usage::Tr => unarm::R9Use::Tls, ArmR9Usage::Tr => unarm::R9Use::Tls,
}, },
explicit_stack_limit: config.arm_sl_usage, explicit_stack_limit: diff_config.arm_sl_usage,
frame_pointer: config.arm_fp_usage, frame_pointer: diff_config.arm_fp_usage,
ip: config.arm_ip_usage, ip: diff_config.arm_ip_usage,
}, },
};
while let Some((address, ins, parsed_ins)) = parser.next() {
if let Some(next) = next_mapping {
let next_address = parser.address;
if next_address >= next.address {
// Change mapping
parser.mode = next.mapping;
next_mapping = mappings_iter.next();
}
}
let line = line_info.range(..=address as u64).last().map(|(_, &b)| b);
let reloc = relocations.iter().find(|r| (r.address as u32 & !1) == address).cloned();
let mut reloc_arg = None;
if let Some(reloc) = &reloc {
match reloc.flags {
// Calls
RelocationFlags::Elf { r_type: elf::R_ARM_THM_XPC22 }
| RelocationFlags::Elf { r_type: elf::R_ARM_THM_PC22 }
| RelocationFlags::Elf { r_type: elf::R_ARM_PC24 }
| RelocationFlags::Elf { r_type: elf::R_ARM_XPC25 }
| RelocationFlags::Elf { r_type: elf::R_ARM_CALL } => {
reloc_arg = parsed_ins
.args
.iter()
.rposition(|a| matches!(a, Argument::BranchDest(_)));
}
// Data
RelocationFlags::Elf { r_type: elf::R_ARM_ABS32 } => {
reloc_arg =
parsed_ins.args.iter().rposition(|a| matches!(a, Argument::UImm(_)));
}
_ => (),
}
};
let (args, branch_dest) = if reloc.is_some() && parser.mode == ParseMode::Data {
(vec![ObjInsArg::Reloc], None)
} else {
push_args(&parsed_ins, config, reloc_arg, address, display_options)?
};
ops.push(ins.opcode_id());
insts.push(ObjIns {
address: address as u64,
size: (parser.address - address) as u8,
op: ins.opcode_id(),
mnemonic: Cow::Borrowed(parsed_ins.mnemonic),
args,
reloc,
branch_dest,
line,
formatted: parsed_ins.display(display_options).to_string(),
orig: None,
});
} }
Ok(ProcessCodeResult { ops, insts })
} }
fn implcit_addend( fn parse_ins_ref(
&self,
ins_ref: InstructionRef,
code: &[u8],
diff_config: &DiffObjConfig,
) -> Result<(unarm::Ins, unarm::ParsedIns)> {
if ins_ref.opcode == thumb::Opcode::BlH as u16 && ins_ref.size == 4 {
// Special case: combined thumb BL instruction
let parse_flags = self.parse_flags(diff_config);
let first_ins = thumb::Ins {
code: match self.endianness {
object::Endianness::Little => u16::from_le_bytes([code[0], code[1]]),
object::Endianness::Big => u16::from_be_bytes([code[0], code[1]]),
} as u32,
op: thumb::Opcode::BlH,
};
let second_ins = thumb::Ins::new(
match self.endianness {
object::Endianness::Little => u16::from_le_bytes([code[2], code[3]]),
object::Endianness::Big => u16::from_be_bytes([code[2], code[3]]),
} as u32,
&parse_flags,
);
let first_parsed = first_ins.parse(&parse_flags);
let second_parsed = second_ins.parse(&parse_flags);
return Ok((
unarm::Ins::Thumb(first_ins),
first_parsed.combine_thumb_bl(&second_parsed),
));
}
let code = match (self.endianness, ins_ref.size) {
(object::Endianness::Little, 2) => u16::from_le_bytes([code[0], code[1]]) as u32,
(object::Endianness::Little, 4) => {
u32::from_le_bytes([code[0], code[1], code[2], code[3]])
}
(object::Endianness::Big, 2) => u16::from_be_bytes([code[0], code[1]]) as u32,
(object::Endianness::Big, 4) => {
u32::from_be_bytes([code[0], code[1], code[2], code[3]])
}
_ => bail!("Invalid instruction size {}", ins_ref.size),
};
let (ins, parsed_ins) = if ins_ref.opcode == u16::MAX {
let mut args = args::Arguments::default();
args[0] = args::Argument::UImm(code);
let mnemonic = if ins_ref.size == 4 { ".word" } else { ".hword" };
(unarm::Ins::Data, unarm::ParsedIns { mnemonic, args })
} else if ins_ref.opcode & (1 << 15) != 0 {
let ins = arm::Ins { code, op: arm::Opcode::from(ins_ref.opcode as u8) };
let parsed = ins.parse(&self.parse_flags(diff_config));
(unarm::Ins::Arm(ins), parsed)
} else {
let ins = thumb::Ins { code, op: thumb::Opcode::from(ins_ref.opcode as u8) };
let parsed = ins.parse(&self.parse_flags(diff_config));
(unarm::Ins::Thumb(ins), parsed)
};
Ok((ins, parsed_ins))
}
}
impl Arch for ArchArm {
fn post_init(&mut self, sections: &[Section], symbols: &[Symbol]) {
self.disasm_modes = Self::get_mapping_symbols(sections, symbols);
}
fn scan_instructions_internal(
&self, &self,
_file: &File<'_>,
section: &ObjSection,
address: u64, address: u64,
reloc: &Relocation, code: &[u8],
) -> Result<i64> { section_index: usize,
let address = address as usize; _relocations: &[Relocation],
Ok(match reloc.flags() { diff_config: &DiffObjConfig,
// ARM calls ) -> Result<Vec<InstructionRef>> {
RelocationFlags::Elf { r_type: elf::R_ARM_PC24 } let start_addr = address as u32;
| RelocationFlags::Elf { r_type: elf::R_ARM_XPC25 } let end_addr = start_addr + code.len() as u32;
| RelocationFlags::Elf { r_type: elf::R_ARM_CALL } => {
let data = section.data[address..address + 4].try_into()?; // Mapping symbols decide what kind of data comes after it. $a for ARM code, $t for Thumb code and $d for data.
let addend = self.endianness.read_i32_bytes(data); let fallback_mappings =
let imm24 = addend & 0xffffff; [DisasmMode { address: start_addr, mapping: unarm::ParseMode::Arm }];
(imm24 << 2) << 8 >> 8 let mapping_symbols = self
.disasm_modes
.get(&section_index)
.map(|x| x.as_slice())
.unwrap_or(&fallback_mappings);
let first_mapping_idx = mapping_symbols
.binary_search_by_key(&start_addr, |x| x.address)
.unwrap_or_else(|idx| idx.saturating_sub(1));
let mut mode = mapping_symbols[first_mapping_idx].mapping;
let mut mappings_iter = mapping_symbols
.iter()
.copied()
.skip(first_mapping_idx + 1)
.take_while(|x| x.address < end_addr);
let mut next_mapping = mappings_iter.next();
let ins_count = code.len() / mode.instruction_size(start_addr);
let mut ops = Vec::<InstructionRef>::with_capacity(ins_count);
let parse_flags = self.parse_flags(diff_config);
let mut address = start_addr;
while address < end_addr {
while let Some(next) = next_mapping.filter(|x| address >= x.address) {
// Change mapping
mode = next.mapping;
next_mapping = mappings_iter.next();
} }
// Thumb calls let mut ins_size = mode.instruction_size(address);
RelocationFlags::Elf { r_type: elf::R_ARM_THM_PC22 } let data = &code[(address - start_addr) as usize..];
| RelocationFlags::Elf { r_type: elf::R_ARM_THM_XPC22 } => { if data.len() < ins_size {
let data = section.data[address..address + 2].try_into()?; // Push the remainder as data
let high = self.endianness.read_i16_bytes(data) as i32; ops.push(InstructionRef {
let data = section.data[address + 2..address + 4].try_into()?; address: address as u64,
let low = self.endianness.read_i16_bytes(data) as i32; size: data.len() as u8,
opcode: u16::MAX,
let imm22 = ((high & 0x7ff) << 11) | (low & 0x7ff); branch_dest: None,
(imm22 << 1) << 9 >> 9 });
break;
} }
let code = match (self.endianness, ins_size) {
(object::Endianness::Little, 2) => u16::from_le_bytes([data[0], data[1]]) as u32,
(object::Endianness::Little, 4) => {
u32::from_le_bytes([data[0], data[1], data[2], data[3]])
}
(object::Endianness::Big, 2) => u16::from_be_bytes([data[0], data[1]]) as u32,
(object::Endianness::Big, 4) => {
u32::from_be_bytes([data[0], data[1], data[2], data[3]])
}
_ => {
// Invalid instruction size
ops.push(InstructionRef {
address: address as u64,
size: ins_size as u8,
opcode: u16::MAX,
branch_dest: None,
});
address += ins_size as u32;
continue;
}
};
// Data let (opcode, branch_dest) = match mode {
RelocationFlags::Elf { r_type: elf::R_ARM_ABS32 } => { unarm::ParseMode::Arm => {
let data = section.data[address..address + 4].try_into()?; let ins = arm::Ins::new(code, &parse_flags);
self.endianness.read_i32_bytes(data) let opcode = ins.op as u16 | (1 << 15);
let branch_dest = match ins.op {
arm::Opcode::B | arm::Opcode::Bl => {
address.checked_add_signed(ins.field_branch_offset())
}
arm::Opcode::BlxI => address.checked_add_signed(ins.field_blx_offset()),
_ => None,
};
(opcode, branch_dest)
}
unarm::ParseMode::Thumb => {
let ins = thumb::Ins::new(code, &parse_flags);
let opcode = ins.op as u16;
let branch_dest = match ins.op {
thumb::Opcode::B | thumb::Opcode::Bl => {
address.checked_add_signed(ins.field_branch_offset_8())
}
thumb::Opcode::BlH if data.len() >= 4 => {
// Combine BL instructions
let second_ins = thumb::Ins::new(
match self.endianness {
object::Endianness::Little => {
u16::from_le_bytes([data[2], data[3]]) as u32
}
object::Endianness::Big => {
u16::from_be_bytes([data[2], data[3]]) as u32
}
},
&parse_flags,
);
if let Some(low) = match second_ins.op {
thumb::Opcode::Bl => Some(second_ins.field_low_branch_offset_11()),
thumb::Opcode::BlxI => Some(second_ins.field_low_blx_offset_11()),
_ => None,
} {
ins_size = 4;
address.checked_add_signed(
(ins.field_high_branch_offset_11() + (low as i32)) << 9 >> 9,
)
} else {
None
}
}
thumb::Opcode::BLong => {
address.checked_add_signed(ins.field_branch_offset_11())
}
_ => None,
};
(opcode, branch_dest)
}
unarm::ParseMode::Data => (u16::MAX, None),
};
ops.push(InstructionRef {
address: address as u64,
size: ins_size as u8,
opcode,
branch_dest: branch_dest.map(|x| x as u64),
});
address += ins_size as u32;
}
Ok(ops)
}
fn display_instruction(
&self,
resolved: ResolvedInstructionRef,
diff_config: &DiffObjConfig,
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
let (ins, parsed_ins) = self.parse_ins_ref(resolved.ins_ref, resolved.code, diff_config)?;
cb(InstructionPart::opcode(parsed_ins.mnemonic, resolved.ins_ref.opcode))?;
if ins == unarm::Ins::Data && resolved.relocation.is_some() {
cb(InstructionPart::reloc())?;
} else {
push_args(
ins,
&parsed_ins,
resolved.relocation,
resolved.ins_ref.address as u32,
self.display_options(diff_config),
cb,
)?;
}
Ok(())
}
fn relocation_override(
&self,
_file: &object::File<'_>,
section: &object::Section,
address: u64,
relocation: &object::Relocation,
) -> Result<Option<RelocationOverride>> {
match relocation.flags() {
// Handle ELF implicit relocations
object::RelocationFlags::Elf { r_type } => {
if relocation.has_implicit_addend() {
let section_data = section.data()?;
let address = address as usize;
let addend = match r_type {
// ARM calls
elf::R_ARM_PC24 | elf::R_ARM_XPC25 | elf::R_ARM_CALL => {
let data = section_data[address..address + 4].try_into()?;
let addend = self.endianness.read_i32_bytes(data);
let imm24 = addend & 0xffffff;
(imm24 << 2) << 8 >> 8
}
// Thumb calls
elf::R_ARM_THM_PC22 | elf::R_ARM_THM_XPC22 => {
let data = section_data[address..address + 2].try_into()?;
let high = self.endianness.read_i16_bytes(data) as i32;
let data = section_data[address + 2..address + 4].try_into()?;
let low = self.endianness.read_i16_bytes(data) as i32;
let imm22 = ((high & 0x7ff) << 11) | (low & 0x7ff);
(imm22 << 1) << 9 >> 9
}
// Data
elf::R_ARM_ABS32 => {
let data = section_data[address..address + 4].try_into()?;
self.endianness.read_i32_bytes(data)
}
flags => bail!("Unsupported ARM implicit relocation {flags:?}"),
};
Ok(Some(RelocationOverride {
target: RelocationOverrideTarget::Keep,
addend: addend as i64,
}))
} else {
Ok(None)
}
} }
_ => Ok(None),
flags => bail!("Unsupported ARM implicit relocation {flags:?}"), }
} as i64)
} }
fn demangle(&self, name: &str) -> Option<String> { fn demangle(&self, name: &str) -> Option<String> {
@ -273,64 +415,120 @@ impl ObjArch for ObjArchArm {
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok()) .and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
} }
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> { fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
Cow::Owned(format!("<{flags:?}>")) match flags {
RelocationFlags::Elf(r_type) => match r_type {
elf::R_ARM_NONE => Some("R_ARM_NONE"),
elf::R_ARM_ABS32 => Some("R_ARM_ABS32"),
elf::R_ARM_REL32 => Some("R_ARM_REL32"),
elf::R_ARM_ABS16 => Some("R_ARM_ABS16"),
elf::R_ARM_ABS8 => Some("R_ARM_ABS8"),
elf::R_ARM_THM_PC22 => Some("R_ARM_THM_PC22"),
elf::R_ARM_THM_XPC22 => Some("R_ARM_THM_XPC22"),
elf::R_ARM_PC24 => Some("R_ARM_PC24"),
elf::R_ARM_XPC25 => Some("R_ARM_XPC25"),
elf::R_ARM_CALL => Some("R_ARM_CALL"),
_ => None,
},
_ => None,
}
} }
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize { fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
match flags { match flags {
RelocationFlags::Elf { r_type } => match r_type { RelocationFlags::Elf(r_type) => match r_type {
elf::R_ARM_NONE => 0,
elf::R_ARM_ABS32 => 4, elf::R_ARM_ABS32 => 4,
elf::R_ARM_REL32 => 4, elf::R_ARM_REL32 => 4,
elf::R_ARM_ABS16 => 2, elf::R_ARM_ABS16 => 2,
elf::R_ARM_ABS8 => 1, elf::R_ARM_ABS8 => 1,
elf::R_ARM_THM_PC22 => 4,
elf::R_ARM_THM_XPC22 => 4,
elf::R_ARM_PC24 => 4,
elf::R_ARM_XPC25 => 4,
elf::R_ARM_CALL => 4,
_ => 1, _ => 1,
}, },
_ => 1, _ => 1,
} }
} }
fn symbol_address(&self, address: u64, kind: SymbolKind) -> u64 {
if kind == SymbolKind::Function { address & !1 } else { address }
}
fn extra_symbol_flags(&self, symbol: &object::Symbol) -> SymbolFlagSet {
let mut flags = SymbolFlagSet::default();
if DisasmMode::from_object_symbol(symbol).is_some() {
flags |= SymbolFlag::Hidden;
}
flags
}
fn infer_function_size(
&self,
symbol: &Symbol,
section: &Section,
mut next_address: u64,
) -> Result<u64> {
// Trim any trailing 4-byte zeroes from the end (padding)
while next_address >= symbol.address + 4
&& let Some(data) = section.data_range(next_address - 4, 4)
&& data == [0u8; 4]
{
next_address -= 4;
}
Ok(next_address.saturating_sub(symbol.address))
}
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
struct DisasmMode { struct DisasmMode {
address: u32, address: u32,
mapping: ParseMode, mapping: unarm::ParseMode,
} }
impl DisasmMode { impl DisasmMode {
fn from_symbol<'a>(sym: &Symbol<'a, '_, &'a [u8]>) -> Option<Self> { fn from_object_symbol<'a>(sym: &object::Symbol<'a, '_, &'a [u8]>) -> Option<Self> {
if let Ok(name) = sym.name() { sym.name()
ParseMode::from_mapping_symbol(name) .ok()
.map(|mapping| DisasmMode { address: sym.address() as u32, mapping }) .and_then(unarm::ParseMode::from_mapping_symbol)
} else { .map(|mapping| DisasmMode { address: sym.address() as u32, mapping })
None }
}
fn from_symbol(sym: &Symbol) -> Option<Self> {
unarm::ParseMode::from_mapping_symbol(&sym.name)
.map(|mapping| DisasmMode { address: sym.address as u32, mapping })
} }
} }
fn push_args( fn push_args(
parsed_ins: &ParsedIns, ins: unarm::Ins,
config: &DiffObjConfig, parsed_ins: &unarm::ParsedIns,
reloc_arg: Option<usize>, relocation: Option<ResolvedRelocation>,
cur_addr: u32, cur_addr: u32,
display_options: DisplayOptions, display_options: unarm::DisplayOptions,
) -> Result<(Vec<ObjInsArg>, Option<u64>)> { mut arg_cb: impl FnMut(InstructionPart) -> Result<()>,
let mut args = vec![]; ) -> Result<()> {
let mut branch_dest = None; let reloc_arg = find_reloc_arg(parsed_ins, relocation);
let mut writeback = false; let mut writeback = false;
let mut deref = false; let mut deref = false;
for (i, arg) in parsed_ins.args_iter().enumerate() { for (i, &arg) in parsed_ins.args_iter().enumerate() {
// Emit punctuation before separator // Emit punctuation before separator
if deref { if deref {
match arg { match arg {
Argument::OffsetImm(OffsetImm { post_indexed: true, value: _ }) args::Argument::OffsetImm(args::OffsetImm { post_indexed: true, value: _ })
| Argument::OffsetReg(OffsetReg { add: _, post_indexed: true, reg: _ }) | args::Argument::OffsetReg(args::OffsetReg {
| Argument::CoOption(_) => { add: _,
post_indexed: true,
reg: _,
})
| args::Argument::CoOption(_) => {
deref = false; deref = false;
args.push(ObjInsArg::PlainText("]".into())); arg_cb(InstructionPart::basic("]"))?;
if writeback { if writeback {
writeback = false; writeback = false;
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into()))); arg_cb(InstructionPart::opaque("!"))?;
} }
} }
_ => {} _ => {}
@ -338,117 +536,169 @@ fn push_args(
} }
if i > 0 { if i > 0 {
args.push(ObjInsArg::PlainText(config.separator().into())); arg_cb(InstructionPart::separator())?;
} }
if reloc_arg == Some(i) { if reloc_arg == Some(i) {
args.push(ObjInsArg::Reloc); arg_cb(InstructionPart::reloc())?;
} else { } else {
match arg { match arg {
Argument::None => {} args::Argument::None => {}
Argument::Reg(reg) => { args::Argument::Reg(reg) => {
if reg.deref { if reg.deref {
deref = true; deref = true;
args.push(ObjInsArg::PlainText("[".into())); arg_cb(InstructionPart::basic("["))?;
} }
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque( arg_cb(InstructionPart::opaque(
reg.reg.display(display_options.reg_names).to_string().into(), reg.reg.display(display_options.reg_names).to_string(),
))); ))?;
if reg.writeback { if reg.writeback {
if reg.deref { if reg.deref {
writeback = true; writeback = true;
} else { } else {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into()))); arg_cb(InstructionPart::opaque("!"))?;
} }
} }
} }
Argument::RegList(reg_list) => { args::Argument::RegList(reg_list) => {
args.push(ObjInsArg::PlainText("{".into())); arg_cb(InstructionPart::basic("{"))?;
let mut first = true; let mut first = true;
for i in 0..16 { for i in 0..16 {
if (reg_list.regs & (1 << i)) != 0 { if (reg_list.regs & (1 << i)) != 0 {
if !first { if !first {
args.push(ObjInsArg::PlainText(config.separator().into())); arg_cb(InstructionPart::separator())?;
} }
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque( arg_cb(InstructionPart::opaque(
Register::parse(i) args::Register::parse(i)
.display(display_options.reg_names) .display(display_options.reg_names)
.to_string() .to_string(),
.into(), ))?;
)));
first = false; first = false;
} }
} }
args.push(ObjInsArg::PlainText("}".into())); arg_cb(InstructionPart::basic("}"))?;
if reg_list.user_mode { if reg_list.user_mode {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("^".to_string().into()))); arg_cb(InstructionPart::opaque("^"))?;
} }
} }
Argument::UImm(value) | Argument::CoOpcode(value) | Argument::SatImm(value) => { args::Argument::UImm(value)
args.push(ObjInsArg::PlainText("#".into())); | args::Argument::CoOpcode(value)
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64))); | args::Argument::SatImm(value) => {
arg_cb(InstructionPart::basic("#"))?;
arg_cb(InstructionPart::unsigned(value))?;
} }
Argument::SImm(value) args::Argument::SImm(value)
| Argument::OffsetImm(OffsetImm { post_indexed: _, value }) => { | args::Argument::OffsetImm(args::OffsetImm { post_indexed: _, value }) => {
args.push(ObjInsArg::PlainText("#".into())); arg_cb(InstructionPart::basic("#"))?;
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(*value as i64))); arg_cb(InstructionPart::signed(value))?;
} }
Argument::BranchDest(value) => { args::Argument::BranchDest(value) => {
let dest = cur_addr.wrapping_add_signed(*value) as u64; arg_cb(InstructionPart::branch_dest(cur_addr.wrapping_add_signed(value)))?;
args.push(ObjInsArg::BranchDest(dest));
branch_dest = Some(dest);
} }
Argument::CoOption(value) => { args::Argument::CoOption(value) => {
args.push(ObjInsArg::PlainText("{".into())); arg_cb(InstructionPart::basic("{"))?;
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64))); arg_cb(InstructionPart::unsigned(value))?;
args.push(ObjInsArg::PlainText("}".into())); arg_cb(InstructionPart::basic("}"))?;
} }
Argument::CoprocNum(value) => { args::Argument::CoprocNum(value) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(format!("p{}", value).into()))); arg_cb(InstructionPart::opaque(format!("p{value}")))?;
} }
Argument::ShiftImm(shift) => { args::Argument::ShiftImm(shift) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into()))); arg_cb(InstructionPart::opaque(shift.op.to_string()))?;
args.push(ObjInsArg::PlainText(" #".into())); arg_cb(InstructionPart::basic(" #"))?;
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(shift.imm as u64))); arg_cb(InstructionPart::unsigned(shift.imm))?;
} }
Argument::ShiftReg(shift) => { args::Argument::ShiftReg(shift) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into()))); arg_cb(InstructionPart::opaque(shift.op.to_string()))?;
args.push(ObjInsArg::PlainText(" ".into())); arg_cb(InstructionPart::basic(" "))?;
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque( arg_cb(InstructionPart::opaque(
shift.reg.display(display_options.reg_names).to_string().into(), shift.reg.display(display_options.reg_names).to_string(),
))); ))?;
} }
Argument::OffsetReg(offset) => { args::Argument::OffsetReg(offset) => {
if !offset.add { if !offset.add {
args.push(ObjInsArg::PlainText("-".into())); arg_cb(InstructionPart::basic("-"))?;
} }
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque( arg_cb(InstructionPart::opaque(
offset.reg.display(display_options.reg_names).to_string().into(), offset.reg.display(display_options.reg_names).to_string(),
))); ))?;
} }
Argument::CpsrMode(mode) => { args::Argument::CpsrMode(mode) => {
args.push(ObjInsArg::PlainText("#".into())); arg_cb(InstructionPart::basic("#"))?;
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(mode.mode as u64))); arg_cb(InstructionPart::unsigned(mode.mode))?;
if mode.writeback { if mode.writeback {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into()))); arg_cb(InstructionPart::opaque("!"))?;
} }
} }
Argument::CoReg(_) args::Argument::CoReg(_)
| Argument::StatusReg(_) | args::Argument::StatusReg(_)
| Argument::StatusMask(_) | args::Argument::StatusMask(_)
| Argument::Shift(_) | args::Argument::Shift(_)
| Argument::CpsrFlags(_) | args::Argument::CpsrFlags(_)
| Argument::Endian(_) => args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque( | args::Argument::Endian(_) => {
arg.display(display_options, None).to_string().into(), arg_cb(InstructionPart::opaque(
))), arg.display(display_options, None).to_string(),
))?;
}
} }
} }
} }
if deref { if deref {
args.push(ObjInsArg::PlainText("]".into())); arg_cb(InstructionPart::basic("]"))?;
if writeback { if writeback {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into()))); arg_cb(InstructionPart::opaque("!"))?;
} }
} }
Ok((args, branch_dest))
let branch_dest = get_pc_relative_load_address(ins, cur_addr);
if let Some(branch_dest) = branch_dest {
arg_cb(InstructionPart::basic(" (->"))?;
arg_cb(InstructionPart::branch_dest(branch_dest))?;
arg_cb(InstructionPart::basic(")"))?;
}
Ok(())
}
fn find_reloc_arg(
parsed_ins: &unarm::ParsedIns,
relocation: Option<ResolvedRelocation>,
) -> Option<usize> {
if let Some(resolved) = relocation {
match resolved.relocation.flags {
// Calls
RelocationFlags::Elf(elf::R_ARM_THM_XPC22)
| RelocationFlags::Elf(elf::R_ARM_THM_PC22)
| RelocationFlags::Elf(elf::R_ARM_PC24)
| RelocationFlags::Elf(elf::R_ARM_XPC25)
| RelocationFlags::Elf(elf::R_ARM_CALL) => {
parsed_ins.args.iter().rposition(|a| matches!(a, args::Argument::BranchDest(_)))
}
// Data
RelocationFlags::Elf(elf::R_ARM_ABS32) => {
parsed_ins.args.iter().rposition(|a| matches!(a, args::Argument::UImm(_)))
}
_ => None,
}
} else {
None
}
}
fn get_pc_relative_load_address(ins: unarm::Ins, address: u32) -> Option<u32> {
match ins {
unarm::Ins::Arm(ins)
if ins.op == arm::Opcode::Ldr
&& ins.modifier_addr_ldr_str() == arm::AddrLdrStr::Imm
&& ins.field_rn_deref().reg == args::Register::Pc =>
{
let offset = ins.field_offset_12().value;
Some(address.wrapping_add_signed(offset + 8))
}
unarm::Ins::Thumb(ins) if ins.op == thumb::Opcode::LdrPc => {
let offset = ins.field_rel_immed_8().value;
Some((address & !3).wrapping_add_signed(offset + 4))
}
_ => None,
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +1,32 @@
use std::{borrow::Cow, collections::BTreeMap, sync::Mutex}; use alloc::{
collections::{BTreeMap, BTreeSet},
use anyhow::{anyhow, bail, Result}; string::{String, ToString},
use object::{ vec::Vec,
elf, Endian, Endianness, File, FileFlags, Object, ObjectSection, ObjectSymbol, Relocation, };
RelocationFlags, RelocationTarget,
use anyhow::{Result, bail};
use object::{Endian as _, Object as _, ObjectSection as _, ObjectSymbol as _, elf};
use rabbitizer::{
IsaExtension, IsaVersion, Vram, abi::Abi, operands::ValuedOperand, registers_meta::Register,
}; };
use rabbitizer::{config, Abi, InstrCategory, Instruction, OperandType};
use crate::{ use crate::{
arch::{ObjArch, ProcessCodeResult}, arch::{Arch, RelocationOverride, RelocationOverrideTarget},
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory}, diff::{DiffObjConfig, MipsAbi, MipsInstrCategory, display::InstructionPart},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection}, obj::{
InstructionArg, InstructionArgValue, InstructionRef, Relocation, RelocationFlags,
ResolvedInstructionRef, ResolvedRelocation, Section, Symbol, SymbolFlag, SymbolFlagSet,
},
}; };
static RABBITIZER_MUTEX: Mutex<()> = Mutex::new(()); #[derive(Debug)]
pub struct ArchMips {
fn configure_rabbitizer(abi: Abi) { pub endianness: object::Endianness,
unsafe {
config::RabbitizerConfig_Cfg.reg_names.fpr_abi_names = abi;
}
}
pub struct ObjArchMips {
pub endianness: Endianness,
pub abi: Abi, pub abi: Abi,
pub instr_category: InstrCategory, pub isa_extension: Option<IsaExtension>,
pub ri_gp_value: i32, pub ri_gp_value: i32,
pub paired_relocations: Vec<BTreeMap<u64, i64>>,
pub ignored_symbols: BTreeSet<usize>,
} }
const EF_MIPS_ABI: u32 = 0x0000F000; const EF_MIPS_ABI: u32 = 0x0000F000;
@ -36,13 +37,13 @@ const EF_MIPS_MACH_5900: u32 = 0x00920000;
const R_MIPS15_S3: u32 = 119; const R_MIPS15_S3: u32 = 119;
impl ObjArchMips { impl ArchMips {
pub fn new(object: &File) -> Result<Self> { pub fn new(object: &object::File) -> Result<Self> {
let mut abi = Abi::NUMERIC; let mut abi = Abi::O32;
let mut instr_category = InstrCategory::CPU; let mut isa_extension = None;
match object.flags() { match object.flags() {
FileFlags::None => {} object::FileFlags::None => {}
FileFlags::Elf { e_flags, .. } => { object::FileFlags::Elf { e_flags, .. } => {
abi = match e_flags & EF_MIPS_ABI { abi = match e_flags & EF_MIPS_ABI {
elf::EF_MIPS_ABI_O32 | elf::EF_MIPS_ABI_O64 => Abi::O32, elf::EF_MIPS_ABI_O32 | elf::EF_MIPS_ABI_O64 => Abi::O32,
elf::EF_MIPS_ABI_EABI32 | elf::EF_MIPS_ABI_EABI64 => Abi::N32, elf::EF_MIPS_ABI_EABI32 | elf::EF_MIPS_ABI_EABI64 => Abi::N32,
@ -50,14 +51,14 @@ impl ObjArchMips {
if e_flags & elf::EF_MIPS_ABI2 != 0 { if e_flags & elf::EF_MIPS_ABI2 != 0 {
Abi::N32 Abi::N32
} else { } else {
Abi::NUMERIC Abi::O32
} }
} }
}; };
instr_category = match e_flags & EF_MIPS_MACH { isa_extension = match e_flags & EF_MIPS_MACH {
EF_MIPS_MACH_ALLEGREX => InstrCategory::R4000ALLEGREX, EF_MIPS_MACH_ALLEGREX => Some(IsaExtension::R4000ALLEGREX),
EF_MIPS_MACH_5900 => InstrCategory::R5900, EF_MIPS_MACH_5900 => Some(IsaExtension::R5900EE),
_ => InstrCategory::CPU, _ => None,
}; };
} }
_ => bail!("Unsupported MIPS file flags"), _ => bail!("Unsupported MIPS file flags"),
@ -65,216 +66,263 @@ impl ObjArchMips {
// Parse the ri_gp_value stored in .reginfo to be able to correctly // Parse the ri_gp_value stored in .reginfo to be able to correctly
// calculate R_MIPS_GPREL16 relocations later. The value is stored // calculate R_MIPS_GPREL16 relocations later. The value is stored
// 0x14 bytes into .reginfo (on 32 bit platforms) // 0x14 bytes into .reginfo (on 32-bit platforms)
let endianness = object.endianness();
let ri_gp_value = object let ri_gp_value = object
.section_by_name(".reginfo") .section_by_name(".reginfo")
.and_then(|section| section.data().ok()) .and_then(|section| section.data().ok())
.and_then(|data| data.get(0x14..0x18)) .and_then(|data| data.get(0x14..0x18))
.and_then(|s| s.try_into().ok()) .and_then(|s| s.try_into().ok())
.map(|bytes| object.endianness().read_i32_bytes(bytes)) .map(|bytes| endianness.read_i32_bytes(bytes))
.unwrap_or(0); .unwrap_or(0);
Ok(Self { endianness: object.endianness(), abi, instr_category, ri_gp_value }) // Parse all relocations to pair R_MIPS_HI16 and R_MIPS_LO16. Since the instructions only
} // have 16-bit immediate fields, the 32-bit addend is split across the two relocations.
} // R_MIPS_LO16 relocations without an immediately preceding R_MIPS_HI16 use the last seen
// R_MIPS_HI16 addend.
// See https://refspecs.linuxfoundation.org/elf/mipsabi.pdf pages 4-17 and 4-18
let mut paired_relocations = Vec::with_capacity(object.sections().count() + 1);
for obj_section in object.sections() {
let data = obj_section.data()?;
let mut last_hi = None;
let mut last_hi_addend = 0;
let mut addends = BTreeMap::new();
for (addr, reloc) in obj_section.relocations() {
if !reloc.has_implicit_addend() {
continue;
}
match reloc.flags() {
object::RelocationFlags::Elf { r_type: elf::R_MIPS_HI16 } => {
let code = data[addr as usize..addr as usize + 4].try_into()?;
let addend = ((endianness.read_u32_bytes(code) & 0x0000FFFF) << 16) as i32;
last_hi = Some(addr);
last_hi_addend = addend;
}
object::RelocationFlags::Elf { r_type: elf::R_MIPS_LO16 } => {
let code = data[addr as usize..addr as usize + 4].try_into()?;
let addend = (endianness.read_u32_bytes(code) & 0x0000FFFF) as i16 as i32;
let full_addend = (last_hi_addend + addend) as i64;
if let Some(hi_addr) = last_hi.take() {
addends.insert(hi_addr, full_addend);
}
addends.insert(addr, full_addend);
}
_ => {
last_hi = None;
}
}
}
let section_index = obj_section.index().0;
if section_index >= paired_relocations.len() {
paired_relocations.resize_with(section_index + 1, BTreeMap::new);
}
paired_relocations[section_index] = addends;
}
impl ObjArch for ObjArchMips { let mut ignored_symbols = BTreeSet::new();
fn process_code( for obj_symbol in object.symbols() {
&self, let Ok(name) = obj_symbol.name() else { continue };
address: u64, if let Some(prefix) = name.strip_suffix(".NON_MATCHING") {
code: &[u8], ignored_symbols.insert(obj_symbol.index().0);
section_index: usize, if let Some(target_symbol) = object.symbol_by_name(prefix) {
relocations: &[ObjReloc], ignored_symbols.insert(target_symbol.index().0);
line_info: &BTreeMap<u64, u32>, }
config: &DiffObjConfig, }
) -> Result<ProcessCodeResult> { }
let _guard = RABBITIZER_MUTEX.lock().map_err(|e| anyhow!("Failed to lock mutex: {e}"))?;
configure_rabbitizer(match config.mips_abi { Ok(Self {
endianness,
abi,
isa_extension,
ri_gp_value,
paired_relocations,
ignored_symbols,
})
}
fn default_instruction_flags(&self) -> rabbitizer::InstructionFlags {
match self.isa_extension {
Some(extension) => rabbitizer::InstructionFlags::new_extension(extension),
None => rabbitizer::InstructionFlags::new(IsaVersion::MIPS_III),
}
.with_abi(self.abi)
}
fn instruction_flags(&self, diff_config: &DiffObjConfig) -> rabbitizer::InstructionFlags {
let isa_extension = match diff_config.mips_instr_category {
MipsInstrCategory::Auto => self.isa_extension,
MipsInstrCategory::Cpu => None,
MipsInstrCategory::Rsp => Some(IsaExtension::RSP),
MipsInstrCategory::R3000gte => Some(IsaExtension::R3000GTE),
MipsInstrCategory::R4000allegrex => Some(IsaExtension::R4000ALLEGREX),
MipsInstrCategory::R5900 => Some(IsaExtension::R5900EE),
};
match isa_extension {
Some(extension) => rabbitizer::InstructionFlags::new_extension(extension),
None => rabbitizer::InstructionFlags::new(IsaVersion::MIPS_III),
}
.with_abi(match diff_config.mips_abi {
MipsAbi::Auto => self.abi, MipsAbi::Auto => self.abi,
MipsAbi::O32 => Abi::O32, MipsAbi::O32 => Abi::O32,
MipsAbi::N32 => Abi::N32, MipsAbi::N32 => Abi::N32,
MipsAbi::N64 => Abi::N64, MipsAbi::N64 => Abi::N64,
});
let instr_category = match config.mips_instr_category {
MipsInstrCategory::Auto => self.instr_category,
MipsInstrCategory::Cpu => InstrCategory::CPU,
MipsInstrCategory::Rsp => InstrCategory::RSP,
MipsInstrCategory::R3000gte => InstrCategory::R3000GTE,
MipsInstrCategory::R4000allegrex => InstrCategory::R4000ALLEGREX,
MipsInstrCategory::R5900 => InstrCategory::R5900,
};
let start_address = address;
let end_address = address + code.len() as u64;
let ins_count = code.len() / 4;
let mut ops = Vec::<u16>::with_capacity(ins_count);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
let mut cur_addr = start_address as u32;
for chunk in code.chunks_exact(4) {
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
let code = self.endianness.read_u32_bytes(chunk.try_into()?);
let instruction = Instruction::new(code, cur_addr, instr_category);
let formatted = instruction.disassemble(None, 0);
let op = instruction.unique_id as u16;
ops.push(op);
let mnemonic = instruction.opcode_name();
let is_branch = instruction.is_branch();
let branch_offset = instruction.branch_offset();
let mut branch_dest = if is_branch {
cur_addr.checked_add_signed(branch_offset).map(|a| a as u64)
} else {
None
};
let operands = instruction.get_operands_slice();
let mut args = Vec::with_capacity(operands.len() + 1);
for (idx, op) in operands.iter().enumerate() {
if idx > 0 {
args.push(ObjInsArg::PlainText(config.separator().into()));
}
match op {
OperandType::cpu_immediate
| OperandType::cpu_label
| OperandType::cpu_branch_target_label => {
if let Some(reloc) = reloc {
// If the relocation target is within the current function, we can
// convert it into a relative branch target. Note that we check
// target_address > start_address instead of >= so that recursive
// tail calls are not considered branch targets.
let target_address =
reloc.target.address.checked_add_signed(reloc.addend);
if reloc.target.orig_section_index == Some(section_index)
&& matches!(target_address, Some(addr) if addr > start_address && addr < end_address)
{
let target_address = target_address.unwrap();
args.push(ObjInsArg::BranchDest(target_address));
branch_dest = Some(target_address);
} else {
push_reloc(&mut args, reloc)?;
branch_dest = None;
}
} else if let Some(branch_dest) = branch_dest {
args.push(ObjInsArg::BranchDest(branch_dest));
} else {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
op.disassemble(&instruction, None).into(),
)));
}
}
OperandType::cpu_immediate_base => {
if let Some(reloc) = reloc {
push_reloc(&mut args, reloc)?;
} else {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
OperandType::cpu_immediate.disassemble(&instruction, None).into(),
)));
}
args.push(ObjInsArg::PlainText("(".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
OperandType::cpu_rs.disassemble(&instruction, None).into(),
)));
args.push(ObjInsArg::PlainText(")".into()));
}
// OperandType::r5900_immediate15 => match reloc {
// Some(reloc)
// if reloc.flags == RelocationFlags::Elf { r_type: R_MIPS15_S3 } =>
// {
// push_reloc(&mut args, reloc)?;
// }
// _ => {
// args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
// op.disassemble(&instruction, None).into(),
// )));
// }
// },
_ => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
op.disassemble(&instruction, None).into(),
)));
}
}
}
let line = line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
insts.push(ObjIns {
address: cur_addr as u64,
size: 4,
op,
mnemonic: Cow::Borrowed(mnemonic),
args,
reloc: reloc.cloned(),
branch_dest,
line,
formatted,
orig: None,
});
cur_addr += 4;
}
Ok(ProcessCodeResult { ops, insts })
}
fn implcit_addend(
&self,
file: &File<'_>,
section: &ObjSection,
address: u64,
reloc: &Relocation,
) -> Result<i64> {
let data = section.data[address as usize..address as usize + 4].try_into()?;
let addend = self.endianness.read_u32_bytes(data);
Ok(match reloc.flags() {
RelocationFlags::Elf { r_type: elf::R_MIPS_32 } => addend as i64,
RelocationFlags::Elf { r_type: elf::R_MIPS_26 } => ((addend & 0x03FFFFFF) << 2) as i64,
RelocationFlags::Elf { r_type: elf::R_MIPS_HI16 } => {
((addend & 0x0000FFFF) << 16) as i32 as i64
}
RelocationFlags::Elf {
r_type: elf::R_MIPS_LO16 | elf::R_MIPS_GOT16 | elf::R_MIPS_CALL16,
} => (addend & 0x0000FFFF) as i16 as i64,
RelocationFlags::Elf { r_type: elf::R_MIPS_GPREL16 | elf::R_MIPS_LITERAL } => {
let RelocationTarget::Symbol(idx) = reloc.target() else {
bail!("Unsupported R_MIPS_GPREL16 relocation against a non-symbol");
};
let sym = file.symbol_by_index(idx)?;
// if the symbol we are relocating against is in a local section we need to add
// the ri_gp_value from .reginfo to the addend.
if sym.section().index().is_some() {
((addend & 0x0000FFFF) as i16 as i64) + self.ri_gp_value as i64
} else {
(addend & 0x0000FFFF) as i16 as i64
}
}
RelocationFlags::Elf { r_type: elf::R_MIPS_PC16 } => 0, // PC-relative relocation
RelocationFlags::Elf { r_type: R_MIPS15_S3 } => ((addend & 0x001FFFC0) >> 3) as i64,
flags => bail!("Unsupported MIPS implicit relocation {flags:?}"),
}) })
} }
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> { fn instruction_display_flags(
match flags { &self,
RelocationFlags::Elf { r_type } => match r_type { diff_config: &DiffObjConfig,
elf::R_MIPS_32 => Cow::Borrowed("R_MIPS_32"), ) -> rabbitizer::InstructionDisplayFlags {
elf::R_MIPS_26 => Cow::Borrowed("R_MIPS_26"), rabbitizer::InstructionDisplayFlags::default()
elf::R_MIPS_HI16 => Cow::Borrowed("R_MIPS_HI16"), .with_unknown_instr_comment(false)
elf::R_MIPS_LO16 => Cow::Borrowed("R_MIPS_LO16"), .with_use_dollar(diff_config.mips_register_prefix)
elf::R_MIPS_GPREL16 => Cow::Borrowed("R_MIPS_GPREL16"), }
elf::R_MIPS_LITERAL => Cow::Borrowed("R_MIPS_LITERAL"),
elf::R_MIPS_GOT16 => Cow::Borrowed("R_MIPS_GOT16"), fn parse_ins_ref(
elf::R_MIPS_PC16 => Cow::Borrowed("R_MIPS_PC16"), &self,
elf::R_MIPS_CALL16 => Cow::Borrowed("R_MIPS_CALL16"), ins_ref: InstructionRef,
R_MIPS15_S3 => Cow::Borrowed("R_MIPS15_S3"), code: &[u8],
_ => Cow::Owned(format!("<{flags:?}>")), diff_config: &DiffObjConfig,
}, ) -> Result<rabbitizer::Instruction> {
_ => Cow::Owned(format!("<{flags:?}>")), Ok(rabbitizer::Instruction::new(
self.endianness.read_u32_bytes(code.try_into()?),
Vram::new(ins_ref.address as u32),
self.instruction_flags(diff_config),
))
}
}
impl Arch for ArchMips {
fn scan_instructions_internal(
&self,
address: u64,
code: &[u8],
_section_index: usize,
_relocations: &[Relocation],
diff_config: &DiffObjConfig,
) -> Result<Vec<InstructionRef>> {
let instruction_flags = self.instruction_flags(diff_config);
let mut ops = Vec::<InstructionRef>::with_capacity(code.len() / 4);
let mut cur_addr = address as u32;
for chunk in code.chunks_exact(4) {
let code = self.endianness.read_u32_bytes(chunk.try_into()?);
let instruction =
rabbitizer::Instruction::new(code, Vram::new(cur_addr), instruction_flags);
let opcode = instruction.opcode() as u16;
let branch_dest = instruction.get_branch_vram_generic().map(|v| v.inner() as u64);
ops.push(InstructionRef { address: cur_addr as u64, size: 4, opcode, branch_dest });
cur_addr += 4;
}
Ok(ops)
}
fn display_instruction(
&self,
resolved: ResolvedInstructionRef,
diff_config: &DiffObjConfig,
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
let instruction = self.parse_ins_ref(resolved.ins_ref, resolved.code, diff_config)?;
let display_flags = self.instruction_display_flags(diff_config);
let opcode = instruction.opcode();
cb(InstructionPart::opcode(opcode.name(), opcode as u16))?;
push_args(&instruction, resolved.relocation, &display_flags, cb)?;
Ok(())
}
fn relocation_override(
&self,
file: &object::File<'_>,
section: &object::Section,
address: u64,
relocation: &object::Relocation,
) -> Result<Option<RelocationOverride>> {
match relocation.flags() {
// Handle ELF implicit relocations
object::RelocationFlags::Elf { r_type } => {
if relocation.has_implicit_addend() {
// Check for paired R_MIPS_HI16 and R_MIPS_LO16 relocations.
if let elf::R_MIPS_HI16 | elf::R_MIPS_LO16 = r_type
&& let Some(addend) = self
.paired_relocations
.get(section.index().0)
.and_then(|m| m.get(&address).copied())
{
return Ok(Some(RelocationOverride {
target: RelocationOverrideTarget::Keep,
addend,
}));
}
let data = section.data()?;
let code = self
.endianness
.read_u32_bytes(data[address as usize..address as usize + 4].try_into()?);
let addend = match r_type {
elf::R_MIPS_32 => code as i64,
elf::R_MIPS_26 => ((code & 0x03FFFFFF) << 2) as i64,
elf::R_MIPS_HI16 => ((code & 0x0000FFFF) << 16) as i32 as i64,
elf::R_MIPS_LO16 | elf::R_MIPS_GOT16 | elf::R_MIPS_CALL16 => {
(code & 0x0000FFFF) as i16 as i64
}
elf::R_MIPS_GPREL16 | elf::R_MIPS_LITERAL => {
let object::RelocationTarget::Symbol(idx) = relocation.target() else {
bail!("Unsupported R_MIPS_GPREL16 relocation against a non-symbol");
};
let sym = file.symbol_by_index(idx)?;
// if the symbol we are relocating against is in a local section we need to add
// the ri_gp_value from .reginfo to the addend.
if sym.section().index().is_some() {
((code & 0x0000FFFF) as i16 as i64) + self.ri_gp_value as i64
} else {
(code & 0x0000FFFF) as i16 as i64
}
}
elf::R_MIPS_PC16 => 0, // PC-relative relocation
R_MIPS15_S3 => ((code & 0x001FFFC0) >> 3) as i64,
flags => bail!("Unsupported MIPS implicit relocation {flags:?}"),
};
Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Keep, addend }))
} else {
Ok(None)
}
}
_ => Ok(None),
} }
} }
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize { fn demangle(&self, name: &str) -> Option<String> {
cpp_demangle::Symbol::new(name)
.ok()
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
.or_else(|| cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default()))
}
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
match flags { match flags {
RelocationFlags::Elf { r_type } => match r_type { RelocationFlags::Elf(r_type) => match r_type {
elf::R_MIPS_NONE => Some("R_MIPS_NONE"),
elf::R_MIPS_16 => Some("R_MIPS_16"),
elf::R_MIPS_32 => Some("R_MIPS_32"),
elf::R_MIPS_26 => Some("R_MIPS_26"),
elf::R_MIPS_HI16 => Some("R_MIPS_HI16"),
elf::R_MIPS_LO16 => Some("R_MIPS_LO16"),
elf::R_MIPS_GPREL16 => Some("R_MIPS_GPREL16"),
elf::R_MIPS_LITERAL => Some("R_MIPS_LITERAL"),
elf::R_MIPS_GOT16 => Some("R_MIPS_GOT16"),
elf::R_MIPS_PC16 => Some("R_MIPS_PC16"),
elf::R_MIPS_CALL16 => Some("R_MIPS_CALL16"),
R_MIPS15_S3 => Some("R_MIPS15_S3"),
_ => None,
},
_ => None,
}
}
fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
match flags {
RelocationFlags::Elf(r_type) => match r_type {
elf::R_MIPS_16 => 2, elf::R_MIPS_16 => 2,
elf::R_MIPS_32 => 4, elf::R_MIPS_32 => 4,
_ => 1, _ => 1,
@ -282,42 +330,160 @@ impl ObjArch for ObjArchMips {
_ => 1, _ => 1,
} }
} }
fn extra_symbol_flags(&self, symbol: &object::Symbol) -> SymbolFlagSet {
let mut flags = SymbolFlagSet::default();
if self.ignored_symbols.contains(&symbol.index().0) {
flags |= SymbolFlag::Ignored;
}
flags
}
fn infer_function_size(
&self,
symbol: &Symbol,
section: &Section,
next_address: u64,
) -> Result<u64> {
// Trim any trailing 4-byte zeroes from the end (nops)
let mut new_address = next_address;
while new_address >= symbol.address + 4
&& let Some(data) = section.data_range(new_address - 4, 4)
&& data == [0u8; 4]
{
new_address -= 4;
}
// Check if the last instruction has a delay slot, if so, include the delay slot nop
if new_address + 4 <= next_address
&& new_address >= symbol.address + 4
&& let Some(data) = section.data_range(new_address - 4, 4)
&& let instruction = rabbitizer::Instruction::new(
self.endianness.read_u32_bytes(data.try_into().unwrap()),
Vram::new((new_address - 4) as u32),
self.default_instruction_flags(),
)
&& instruction.opcode().has_delay_slot()
{
new_address += 4;
}
Ok(new_address.saturating_sub(symbol.address))
}
} }
fn push_reloc(args: &mut Vec<ObjInsArg>, reloc: &ObjReloc) -> Result<()> { fn push_args(
instruction: &rabbitizer::Instruction,
relocation: Option<ResolvedRelocation>,
display_flags: &rabbitizer::InstructionDisplayFlags,
mut arg_cb: impl FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
let operands = instruction.valued_operands_iter();
for (idx, op) in operands.enumerate() {
if idx > 0 {
arg_cb(InstructionPart::separator())?;
}
match op {
ValuedOperand::core_imm_i16(imm) => {
if let Some(resolved) = relocation {
push_reloc(resolved.relocation, &mut arg_cb)?;
} else {
arg_cb(InstructionPart::signed(imm))?;
}
}
ValuedOperand::core_imm_u16(imm) => {
if let Some(resolved) = relocation {
push_reloc(resolved.relocation, &mut arg_cb)?;
} else {
arg_cb(InstructionPart::unsigned(imm))?;
}
}
ValuedOperand::core_label(..) | ValuedOperand::core_branch_target_label(..) => {
if let Some(resolved) = relocation {
push_reloc(resolved.relocation, &mut arg_cb)?;
} else if let Some(branch_dest) = instruction
.get_branch_offset_generic()
.map(|o| (instruction.vram() + o).inner() as u64)
{
arg_cb(InstructionPart::branch_dest(branch_dest))?;
} else {
arg_cb(InstructionPart::opaque(
op.display(instruction, display_flags, None::<&str>).to_string(),
))?;
}
}
ValuedOperand::core_imm_rs(imm, base) => {
if let Some(resolved) = relocation {
push_reloc(resolved.relocation, &mut arg_cb)?;
} else {
arg_cb(InstructionPart::Arg(InstructionArg::Value(
InstructionArgValue::Signed(imm as i64),
)))?;
}
arg_cb(InstructionPart::basic("("))?;
arg_cb(InstructionPart::opaque(base.either_name(
instruction.flags().abi(),
display_flags.named_gpr(),
!display_flags.use_dollar(),
)))?;
arg_cb(InstructionPart::basic(")"))?;
}
// ValuedOperand::r5900_immediate15(..) => match relocation {
// Some(resolved)
// if resolved.relocation.flags == RelocationFlags::Elf(R_MIPS15_S3) =>
// {
// push_reloc(&resolved.relocation, &mut arg_cb)?;
// }
// _ => {
// arg_cb(InstructionPart::opaque(op.disassemble(&instruction, None)))?;
// }
// },
_ => {
arg_cb(InstructionPart::opaque(
op.display(instruction, display_flags, None::<&str>).to_string(),
))?;
}
}
}
Ok(())
}
fn push_reloc(
reloc: &Relocation,
mut arg_cb: impl FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
match reloc.flags { match reloc.flags {
RelocationFlags::Elf { r_type } => match r_type { RelocationFlags::Elf(r_type) => match r_type {
elf::R_MIPS_HI16 => { elf::R_MIPS_HI16 => {
args.push(ObjInsArg::PlainText("%hi(".into())); arg_cb(InstructionPart::basic("%hi("))?;
args.push(ObjInsArg::Reloc); arg_cb(InstructionPart::reloc())?;
args.push(ObjInsArg::PlainText(")".into())); arg_cb(InstructionPart::basic(")"))?;
} }
elf::R_MIPS_LO16 => { elf::R_MIPS_LO16 => {
args.push(ObjInsArg::PlainText("%lo(".into())); arg_cb(InstructionPart::basic("%lo("))?;
args.push(ObjInsArg::Reloc); arg_cb(InstructionPart::reloc())?;
args.push(ObjInsArg::PlainText(")".into())); arg_cb(InstructionPart::basic(")"))?;
} }
elf::R_MIPS_GOT16 => { elf::R_MIPS_GOT16 => {
args.push(ObjInsArg::PlainText("%got(".into())); arg_cb(InstructionPart::basic("%got("))?;
args.push(ObjInsArg::Reloc); arg_cb(InstructionPart::reloc())?;
args.push(ObjInsArg::PlainText(")".into())); arg_cb(InstructionPart::basic(")"))?;
} }
elf::R_MIPS_CALL16 => { elf::R_MIPS_CALL16 => {
args.push(ObjInsArg::PlainText("%call16(".into())); arg_cb(InstructionPart::basic("%call16("))?;
args.push(ObjInsArg::Reloc); arg_cb(InstructionPart::reloc())?;
args.push(ObjInsArg::PlainText(")".into())); arg_cb(InstructionPart::basic(")"))?;
} }
elf::R_MIPS_GPREL16 => { elf::R_MIPS_GPREL16 => {
args.push(ObjInsArg::PlainText("%gp_rel(".into())); arg_cb(InstructionPart::basic("%gp_rel("))?;
args.push(ObjInsArg::Reloc); arg_cb(InstructionPart::reloc())?;
args.push(ObjInsArg::PlainText(")".into())); arg_cb(InstructionPart::basic(")"))?;
} }
elf::R_MIPS_32 elf::R_MIPS_32
| elf::R_MIPS_26 | elf::R_MIPS_26
| elf::R_MIPS_LITERAL | elf::R_MIPS_LITERAL
| elf::R_MIPS_PC16 | elf::R_MIPS_PC16
| R_MIPS15_S3 => { | R_MIPS15_S3 => {
args.push(ObjInsArg::Reloc); arg_cb(InstructionPart::reloc())?;
} }
_ => bail!("Unsupported ELF MIPS relocation type {r_type}"), _ => bail!("Unsupported ELF MIPS relocation type {r_type}"),
}, },

View File

@ -1,44 +1,95 @@
use std::{borrow::Cow, collections::BTreeMap, ffi::CStr}; use alloc::{
borrow::Cow,
boxed::Box,
format,
string::{String, ToString},
vec::Vec,
};
use core::{
any::Any,
ffi::CStr,
fmt::{self, Debug},
};
use anyhow::{bail, Result}; use anyhow::{Result, bail};
use byteorder::ByteOrder; use encoding_rs::SHIFT_JIS;
use object::{Architecture, File, Object, ObjectSymbol, Relocation, RelocationFlags, Symbol}; use object::Endian as _;
use crate::{ use crate::{
diff::DiffObjConfig, diff::{
obj::{ObjIns, ObjReloc, ObjSection}, DiffObjConfig,
display::{ContextItem, HoverItem, InstructionPart},
},
obj::{
FlowAnalysisResult, InstructionArg, InstructionRef, Object, ParsedInstruction, Relocation,
RelocationFlags, ResolvedInstructionRef, ResolvedSymbol, Section, Symbol, SymbolFlagSet,
SymbolKind,
},
util::ReallySigned, util::ReallySigned,
}; };
#[cfg(feature = "arm")] #[cfg(feature = "arm")]
mod arm; pub mod arm;
#[cfg(feature = "arm64")] #[cfg(feature = "arm64")]
mod arm64; pub mod arm64;
#[cfg(feature = "mips")] #[cfg(feature = "mips")]
pub mod mips; pub mod mips;
#[cfg(feature = "ppc")] #[cfg(feature = "ppc")]
pub mod ppc; pub mod ppc;
#[cfg(feature = "superh")]
pub mod superh;
#[cfg(feature = "x86")] #[cfg(feature = "x86")]
pub mod x86; pub mod x86;
/// Represents the type of data associated with an instruction /// Represents the type of data associated with an instruction
#[derive(PartialEq)]
pub enum DataType { pub enum DataType {
Int8, Int8,
Int16, Int16,
Int32, Int32,
Int64, Int64,
Int128,
Float, Float,
Double, Double,
Bytes, Bytes,
String, String,
} }
impl fmt::Display for DataType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
DataType::Int8 => "Int8",
DataType::Int16 => "Int16",
DataType::Int32 => "Int32",
DataType::Int64 => "Int64",
DataType::Float => "Float",
DataType::Double => "Double",
DataType::Bytes => "Bytes",
DataType::String => "String",
})
}
}
impl DataType { impl DataType {
pub fn display_bytes<Endian: ByteOrder>(&self, bytes: &[u8]) -> Option<String> { pub fn display_labels(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<String> {
let mut strs = Vec::new();
for (literal, label_override) in self.display_literals(endian, bytes) {
let label = label_override.unwrap_or_else(|| self.to_string());
strs.push(format!("{label}: {literal}"))
}
strs
}
pub fn display_literals(
&self,
endian: object::Endianness,
bytes: &[u8],
) -> Vec<(String, Option<String>)> {
let mut strs = Vec::new();
if self.required_len().is_some_and(|l| bytes.len() < l) { if self.required_len().is_some_and(|l| bytes.len() < l) {
log::warn!("Failed to display a symbol value for a symbol whose size is too small for instruction referencing it."); log::warn!(
return None; "Failed to display a symbol value for a symbol whose size is too small for instruction referencing it."
);
return strs;
} }
let mut bytes = bytes; let mut bytes = bytes;
if self.required_len().is_some_and(|l| bytes.len() > l) { if self.required_len().is_some_and(|l| bytes.len() > l) {
@ -56,58 +107,77 @@ impl DataType {
match self { match self {
DataType::Int8 => { DataType::Int8 => {
let i = i8::from_ne_bytes(bytes.try_into().unwrap()); let i = i8::from_ne_bytes(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None));
if i < 0 { if i < 0 {
format!("Int8: {:#x} ({:#x})", i, ReallySigned(i)) strs.push((format!("{:#x}", ReallySigned(i)), None));
} else {
format!("Int8: {:#x}", i)
} }
} }
DataType::Int16 => { DataType::Int16 => {
let i = Endian::read_i16(bytes); let i = endian.read_i16_bytes(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None));
if i < 0 { if i < 0 {
format!("Int16: {:#x} ({:#x})", i, ReallySigned(i)) strs.push((format!("{:#x}", ReallySigned(i)), None));
} else {
format!("Int16: {:#x}", i)
} }
} }
DataType::Int32 => { DataType::Int32 => {
let i = Endian::read_i32(bytes); let i = endian.read_i32_bytes(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None));
if i < 0 { if i < 0 {
format!("Int32: {:#x} ({:#x})", i, ReallySigned(i)) strs.push((format!("{:#x}", ReallySigned(i)), None));
} else {
format!("Int32: {:#x}", i)
} }
} }
DataType::Int64 => { DataType::Int64 => {
let i = Endian::read_i64(bytes); let i = endian.read_i64_bytes(bytes.try_into().unwrap());
strs.push((format!("{i:#x}"), None));
if i < 0 { if i < 0 {
format!("Int64: {:#x} ({:#x})", i, ReallySigned(i)) strs.push((format!("{:#x}", ReallySigned(i)), None));
} else {
format!("Int64: {:#x}", i)
}
}
DataType::Int128 => {
let i = Endian::read_i128(bytes);
if i < 0 {
format!("Int128: {:#x} ({:#x})", i, ReallySigned(i))
} else {
format!("Int128: {:#x}", i)
} }
} }
DataType::Float => { DataType::Float => {
format!("Float: {:?}f", Endian::read_f32(bytes)) let bytes: [u8; 4] = bytes.try_into().unwrap();
strs.push((
format!("{:?}f", match endian {
object::Endianness::Little => f32::from_le_bytes(bytes),
object::Endianness::Big => f32::from_be_bytes(bytes),
}),
None,
));
} }
DataType::Double => { DataType::Double => {
format!("Double: {:?}", Endian::read_f64(bytes)) let bytes: [u8; 8] = bytes.try_into().unwrap();
strs.push((
format!("{:?}", match endian {
object::Endianness::Little => f64::from_le_bytes(bytes),
object::Endianness::Big => f64::from_be_bytes(bytes),
}),
None,
));
} }
DataType::Bytes => { DataType::Bytes => {
format!("Bytes: {:#?}", bytes) strs.push((format!("{bytes:#?}"), None));
} }
DataType::String => { DataType::String => {
format!("String: {:?}", CStr::from_bytes_until_nul(bytes).ok()?) if let Ok(cstr) = CStr::from_bytes_until_nul(bytes) {
strs.push((format!("{cstr:?}"), None));
}
if let Some(nul_idx) = bytes.iter().position(|&c| c == b'\0') {
let (cow, _, had_errors) = SHIFT_JIS.decode(&bytes[..nul_idx]);
if !had_errors {
let str = format!("{cow:?}");
// Only add the Shift JIS string if it's different from the ASCII string.
if !strs.iter().any(|x| x.0 == str) {
strs.push((str, Some("Shift JIS".into())));
}
}
}
} }
} }
.into()
strs
} }
fn required_len(&self) -> Option<usize> { fn required_len(&self) -> Option<usize> {
@ -116,7 +186,6 @@ impl DataType {
DataType::Int16 => Some(2), DataType::Int16 => Some(2),
DataType::Int32 => Some(4), DataType::Int32 => Some(4),
DataType::Int64 => Some(8), DataType::Int64 => Some(8),
DataType::Int128 => Some(16),
DataType::Float => Some(4), DataType::Float => Some(4),
DataType::Double => Some(8), DataType::Double => Some(8),
DataType::Bytes => None, DataType::Bytes => None,
@ -125,72 +194,294 @@ impl DataType {
} }
} }
pub trait ObjArch: Send + Sync { impl dyn Arch {
fn process_code( /// Generate a list of instructions references (offset, size, opcode) from the given code.
///
/// See [`scan_instructions_internal`] for more details.
pub fn scan_instructions(
&self,
resolved: ResolvedSymbol,
diff_config: &DiffObjConfig,
) -> Result<Vec<InstructionRef>> {
let mut result = self.scan_instructions_internal(
resolved.symbol.address,
resolved.data,
resolved.section_index,
&resolved.section.relocations,
diff_config,
)?;
let function_start = resolved.symbol.address;
let function_end = function_start + resolved.symbol.size;
// Remove any branch destinations that are outside the function range
for ins in result.iter_mut() {
if let Some(branch_dest) = ins.branch_dest
&& (branch_dest < function_start || branch_dest >= function_end)
{
ins.branch_dest = None;
}
}
// Resolve relocation targets within the same function to branch destinations
let mut ins_iter = result.iter_mut().peekable();
'outer: for reloc in resolved
.section
.relocations
.iter()
.skip_while(|r| r.address < function_start)
.take_while(|r| r.address < function_end)
{
let ins = loop {
let Some(ins) = ins_iter.peek_mut() else {
break 'outer;
};
if reloc.address < ins.address {
continue 'outer;
}
let ins = ins_iter.next().unwrap();
if reloc.address >= ins.address && reloc.address < ins.address + ins.size as u64 {
break ins;
}
};
// Clear existing branch destination for instructions with relocations
ins.branch_dest = None;
let Some(target) = resolved.obj.symbols.get(reloc.target_symbol) else {
continue;
};
if target.section != Some(resolved.section_index) {
continue;
}
let Some(target_address) = target.address.checked_add_signed(reloc.addend) else {
continue;
};
// If the target address is within the function range, set it as a branch destination
if target_address >= function_start && target_address < function_end {
ins.branch_dest = Some(target_address);
}
}
Ok(result)
}
/// Parse an instruction to gather its mnemonic and arguments for more detailed comparison.
///
/// This is called only when we need to compare the arguments of an instruction.
pub fn process_instruction(
&self,
resolved: ResolvedInstructionRef,
diff_config: &DiffObjConfig,
) -> Result<ParsedInstruction> {
let mut mnemonic = None;
let mut args = Vec::with_capacity(8);
let mut relocation_emitted = false;
self.display_instruction(resolved, diff_config, &mut |part| {
match part {
InstructionPart::Opcode(m, _) => mnemonic = Some(Cow::Owned(m.into_owned())),
InstructionPart::Arg(arg) => {
if arg == InstructionArg::Reloc {
relocation_emitted = true;
// If the relocation was resolved to a branch destination, emit that instead.
if let Some(dest) = resolved.ins_ref.branch_dest {
args.push(InstructionArg::BranchDest(dest));
return Ok(());
}
}
args.push(arg.into_static());
}
_ => {}
}
Ok(())
})?;
// If the instruction has a relocation, but we didn't format it in the display, add it to
// the end of the arguments list.
if resolved.relocation.is_some() && !relocation_emitted {
args.push(InstructionArg::Reloc);
}
Ok(ParsedInstruction {
ins_ref: resolved.ins_ref,
mnemonic: mnemonic.unwrap_or_default(),
args,
})
}
}
pub trait Arch: Any + Debug + Send + Sync {
/// Finishes arch-specific initialization that must be done after sections have been combined.
fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol]) {}
/// Generate a list of instructions references (offset, size, opcode) from the given code.
///
/// The opcode IDs are used to generate the initial diff. Implementations should do as little
/// parsing as possible here: just enough to identify the base instruction opcode, size, and
/// possible branch destination (for visual representation). As needed, instructions are parsed
/// via `process_instruction` to compare their arguments.
fn scan_instructions_internal(
&self, &self,
address: u64, address: u64,
code: &[u8], code: &[u8],
section_index: usize, section_index: usize,
relocations: &[ObjReloc], relocations: &[Relocation],
line_info: &BTreeMap<u64, u32>, diff_config: &DiffObjConfig,
config: &DiffObjConfig, ) -> Result<Vec<InstructionRef>>;
) -> Result<ProcessCodeResult>;
fn implcit_addend( /// Format an instruction for display.
///
/// Implementations should call the callback for each part of the instruction: usually the
/// mnemonic and arguments, plus any separators and visual formatting.
fn display_instruction(
&self, &self,
file: &File<'_>, resolved: ResolvedInstructionRef,
section: &ObjSection, diff_config: &DiffObjConfig,
address: u64, cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
reloc: &Relocation, ) -> Result<()>;
) -> Result<i64>;
/// Generate a list of fake relocations from the given code that represent pooled data accesses.
fn generate_pooled_relocations(
&self,
_address: u64,
_code: &[u8],
_relocations: &[Relocation],
_symbols: &[Symbol],
) -> Vec<Relocation> {
Vec::new()
}
// Perform detailed data flow analysis
fn data_flow_analysis(
&self,
_obj: &Object,
_symbol: &Symbol,
_code: &[u8],
_relocations: &[Relocation],
) -> Option<Box<dyn FlowAnalysisResult>> {
None
}
fn relocation_override(
&self,
_file: &object::File<'_>,
_section: &object::Section,
_address: u64,
_relocation: &object::Relocation,
) -> Result<Option<RelocationOverride>> {
Ok(None)
}
fn demangle(&self, _name: &str) -> Option<String> { None } fn demangle(&self, _name: &str) -> Option<String> { None }
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str>; fn reloc_name(&self, _flags: RelocationFlags) -> Option<&'static str> { None }
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize; fn data_reloc_size(&self, flags: RelocationFlags) -> usize;
fn symbol_address(&self, symbol: &Symbol) -> u64 { symbol.address() } fn symbol_address(&self, address: u64, _kind: SymbolKind) -> u64 { address }
fn guess_data_type(&self, _instruction: &ObjIns) -> Option<DataType> { None } fn extra_symbol_flags(&self, _symbol: &object::Symbol) -> SymbolFlagSet {
SymbolFlagSet::default()
fn display_data_type(&self, _ty: DataType, bytes: &[u8]) -> Option<String> {
Some(format!("Bytes: {:#x?}", bytes))
} }
fn display_ins_data(&self, ins: &ObjIns) -> Option<String> { fn guess_data_type(
let reloc = ins.reloc.as_ref()?; &self,
if reloc.addend >= 0 && reloc.target.bytes.len() > reloc.addend as usize { _resolved: ResolvedInstructionRef,
self.guess_data_type(ins).and_then(|ty| { _bytes: &[u8],
self.display_data_type(ty, &reloc.target.bytes[reloc.addend as usize..]) ) -> Option<DataType> {
}) None
} else {
None
}
} }
// Downcast methods fn symbol_hover(&self, _obj: &Object, _symbol_index: usize) -> Vec<HoverItem> { Vec::new() }
#[cfg(feature = "ppc")]
fn ppc(&self) -> Option<&ppc::ObjArchPpc> { None } fn symbol_context(&self, _obj: &Object, _symbol_index: usize) -> Vec<ContextItem> { Vec::new() }
fn instruction_hover(
&self,
_obj: &Object,
_resolved: ResolvedInstructionRef,
) -> Vec<HoverItem> {
Vec::new()
}
fn instruction_context(
&self,
_obj: &Object,
_resolved: ResolvedInstructionRef,
) -> Vec<ContextItem> {
Vec::new()
}
fn infer_function_size(
&self,
symbol: &Symbol,
_section: &Section,
next_address: u64,
) -> Result<u64> {
Ok(next_address.saturating_sub(symbol.address))
}
} }
pub struct ProcessCodeResult { pub fn new_arch(object: &object::File) -> Result<Box<dyn Arch>> {
pub ops: Vec<u16>, use object::Object as _;
pub insts: Vec<ObjIns>,
}
pub fn new_arch(object: &File) -> Result<Box<dyn ObjArch>> {
Ok(match object.architecture() { Ok(match object.architecture() {
#[cfg(feature = "ppc")] #[cfg(feature = "ppc")]
Architecture::PowerPc => Box::new(ppc::ObjArchPpc::new(object)?), object::Architecture::PowerPc | object::Architecture::PowerPc64 => {
Box::new(ppc::ArchPpc::new(object)?)
}
#[cfg(feature = "mips")] #[cfg(feature = "mips")]
Architecture::Mips => Box::new(mips::ObjArchMips::new(object)?), object::Architecture::Mips => Box::new(mips::ArchMips::new(object)?),
#[cfg(feature = "x86")] #[cfg(feature = "x86")]
Architecture::I386 | Architecture::X86_64 => Box::new(x86::ObjArchX86::new(object)?), object::Architecture::I386 | object::Architecture::X86_64 => {
Box::new(x86::ArchX86::new(object)?)
}
#[cfg(feature = "arm")] #[cfg(feature = "arm")]
Architecture::Arm => Box::new(arm::ObjArchArm::new(object)?), object::Architecture::Arm => Box::new(arm::ArchArm::new(object)?),
#[cfg(feature = "arm64")] #[cfg(feature = "arm64")]
Architecture::Aarch64 => Box::new(arm64::ObjArchArm64::new(object)?), object::Architecture::Aarch64 => Box::new(arm64::ArchArm64::new(object)?),
#[cfg(feature = "superh")]
object::Architecture::SuperH => Box::new(superh::ArchSuperH::new(object)?),
arch => bail!("Unsupported architecture: {arch:?}"), arch => bail!("Unsupported architecture: {arch:?}"),
}) })
} }
#[derive(Debug, Default)]
pub struct ArchDummy {}
impl ArchDummy {
pub fn new() -> Box<Self> { Box::new(Self {}) }
}
impl Arch for ArchDummy {
fn scan_instructions_internal(
&self,
_address: u64,
_code: &[u8],
_section_index: usize,
_relocations: &[Relocation],
_diff_config: &DiffObjConfig,
) -> Result<Vec<InstructionRef>> {
Ok(Vec::new())
}
fn display_instruction(
&self,
_resolved: ResolvedInstructionRef,
_diff_config: &DiffObjConfig,
_cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
Ok(())
}
fn data_reloc_size(&self, _flags: RelocationFlags) -> usize { 0 }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RelocationOverrideTarget {
Keep,
Skip,
Symbol(object::SymbolIndex),
Section(object::SectionIndex),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RelocationOverride {
pub target: RelocationOverrideTarget,
pub addend: i64,
}

View File

@ -1,741 +0,0 @@
use std::{
borrow::Cow,
collections::{BTreeMap, HashMap, HashSet},
};
use anyhow::{bail, ensure, Result};
use byteorder::BigEndian;
use cwextab::{decode_extab, ExceptionTableData};
use object::{
elf, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, RelocationTarget,
Symbol, SymbolKind,
};
use ppc750cl::{Argument, Arguments, Ins, InsIter, Opcode, ParsedIns, GPR};
use crate::{
arch::{DataType, ObjArch, ProcessCodeResult},
diff::DiffObjConfig,
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection, ObjSymbol},
};
// Relative relocation, can be Simm, Offset or BranchDest
fn is_relative_arg(arg: &Argument) -> bool {
matches!(arg, Argument::Simm(_) | Argument::Offset(_) | Argument::BranchDest(_))
}
// Relative or absolute relocation, can be Uimm, Simm or Offset
fn is_rel_abs_arg(arg: &Argument) -> bool {
matches!(arg, Argument::Uimm(_) | Argument::Simm(_) | Argument::Offset(_))
}
fn is_offset_arg(arg: &Argument) -> bool { matches!(arg, Argument::Offset(_)) }
pub struct ObjArchPpc {
/// Exception info
pub extab: Option<BTreeMap<usize, ExceptionInfo>>,
}
impl ObjArchPpc {
pub fn new(file: &File) -> Result<Self> { Ok(Self { extab: decode_exception_info(file)? }) }
}
impl ObjArch for ObjArchPpc {
fn process_code(
&self,
address: u64,
code: &[u8],
_section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
let ins_count = code.len() / 4;
let mut ops = Vec::<u16>::with_capacity(ins_count);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
let fake_pool_reloc_for_addr =
generate_fake_pool_reloc_for_addr_mapping(address, code, relocations);
for (cur_addr, mut ins) in InsIter::new(code, address as u32) {
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
if let Some(reloc) = reloc {
// Zero out relocations
ins.code = match reloc.flags {
RelocationFlags::Elf { r_type: elf::R_PPC_EMB_SDA21 } => ins.code & !0x1FFFFF,
RelocationFlags::Elf { r_type: elf::R_PPC_REL24 } => ins.code & !0x3FFFFFC,
RelocationFlags::Elf { r_type: elf::R_PPC_REL14 } => ins.code & !0xFFFC,
RelocationFlags::Elf {
r_type: elf::R_PPC_ADDR16_HI | elf::R_PPC_ADDR16_HA | elf::R_PPC_ADDR16_LO,
} => ins.code & !0xFFFF,
_ => ins.code,
};
}
let orig = ins.basic().to_string();
let simplified = ins.simplified();
let formatted = simplified.to_string();
let mut reloc_arg = None;
if let Some(reloc) = reloc {
match reloc.flags {
RelocationFlags::Elf { r_type: elf::R_PPC_EMB_SDA21 } => {
reloc_arg = Some(1);
}
RelocationFlags::Elf { r_type: elf::R_PPC_REL24 | elf::R_PPC_REL14 } => {
reloc_arg = simplified.args.iter().rposition(is_relative_arg);
}
RelocationFlags::Elf {
r_type: elf::R_PPC_ADDR16_HI | elf::R_PPC_ADDR16_HA | elf::R_PPC_ADDR16_LO,
} => {
reloc_arg = simplified.args.iter().rposition(is_rel_abs_arg);
}
_ => {}
}
}
let mut args = vec![];
let mut branch_dest = None;
let mut writing_offset = false;
for (idx, arg) in simplified.args_iter().enumerate() {
if idx > 0 && !writing_offset {
args.push(ObjInsArg::PlainText(config.separator().into()));
}
if reloc_arg == Some(idx) {
let reloc = reloc.unwrap();
push_reloc(&mut args, reloc)?;
// For @sda21, we can omit the register argument
if matches!(reloc.flags, RelocationFlags::Elf { r_type: elf::R_PPC_EMB_SDA21 })
// Sanity check: the next argument should be r0
&& matches!(simplified.args.get(idx + 1), Some(Argument::GPR(GPR(0))))
{
break;
}
} else {
match arg {
Argument::Simm(simm) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(simm.0 as i64)));
}
Argument::Uimm(uimm) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(uimm.0 as u64)));
}
Argument::Offset(offset) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(offset.0 as i64)));
}
Argument::BranchDest(dest) => {
let dest = cur_addr.wrapping_add_signed(dest.0) as u64;
args.push(ObjInsArg::BranchDest(dest));
branch_dest = Some(dest);
}
_ => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
arg.to_string().into(),
)));
}
};
}
if writing_offset {
args.push(ObjInsArg::PlainText(")".into()));
writing_offset = false;
}
if is_offset_arg(arg) {
args.push(ObjInsArg::PlainText("(".into()));
writing_offset = true;
}
}
if reloc.is_none() {
if let Some(fake_pool_reloc) = fake_pool_reloc_for_addr.get(&cur_addr) {
// If this instruction has a fake pool relocation, show it as a fake argument
// at the end of the line.
args.push(ObjInsArg::PlainText(" ".into()));
push_reloc(&mut args, fake_pool_reloc)?;
}
}
ops.push(ins.op as u16);
let line = line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
insts.push(ObjIns {
address: cur_addr as u64,
size: 4,
mnemonic: Cow::Borrowed(simplified.mnemonic),
args,
reloc: reloc.or(fake_pool_reloc_for_addr.get(&cur_addr)).cloned(),
op: ins.op as u16,
branch_dest,
line,
formatted,
orig: Some(orig),
});
}
Ok(ProcessCodeResult { ops, insts })
}
fn implcit_addend(
&self,
_file: &File<'_>,
_section: &ObjSection,
address: u64,
reloc: &Relocation,
) -> Result<i64> {
bail!("Unsupported PPC implicit relocation {:#x}:{:?}", address, reloc.flags())
}
fn demangle(&self, name: &str) -> Option<String> {
cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default())
}
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
match flags {
RelocationFlags::Elf { r_type } => match r_type {
elf::R_PPC_NONE => Cow::Borrowed("R_PPC_NONE"), // We use this for fake pool relocs
elf::R_PPC_ADDR16_LO => Cow::Borrowed("R_PPC_ADDR16_LO"),
elf::R_PPC_ADDR16_HI => Cow::Borrowed("R_PPC_ADDR16_HI"),
elf::R_PPC_ADDR16_HA => Cow::Borrowed("R_PPC_ADDR16_HA"),
elf::R_PPC_EMB_SDA21 => Cow::Borrowed("R_PPC_EMB_SDA21"),
elf::R_PPC_ADDR32 => Cow::Borrowed("R_PPC_ADDR32"),
elf::R_PPC_UADDR32 => Cow::Borrowed("R_PPC_UADDR32"),
elf::R_PPC_REL24 => Cow::Borrowed("R_PPC_REL24"),
elf::R_PPC_REL14 => Cow::Borrowed("R_PPC_REL14"),
_ => Cow::Owned(format!("<{flags:?}>")),
},
_ => Cow::Owned(format!("<{flags:?}>")),
}
}
fn get_reloc_byte_size(&self, flags: RelocationFlags) -> usize {
match flags {
RelocationFlags::Elf { r_type } => match r_type {
elf::R_PPC_ADDR32 => 4,
elf::R_PPC_UADDR32 => 4,
_ => 1,
},
_ => 1,
}
}
fn guess_data_type(&self, instruction: &ObjIns) -> Option<super::DataType> {
if instruction.reloc.as_ref().is_some_and(|r| r.target.name.starts_with("@stringBase")) {
return Some(DataType::String);
}
guess_data_type_from_load_store_inst_op(Opcode::from(instruction.op as u8))
}
fn display_data_type(&self, ty: DataType, bytes: &[u8]) -> Option<String> {
ty.display_bytes::<BigEndian>(bytes)
}
fn ppc(&self) -> Option<&ObjArchPpc> { Some(self) }
}
impl ObjArchPpc {
pub fn extab_for_symbol(&self, symbol: &ObjSymbol) -> Option<&ExceptionInfo> {
symbol.original_index.and_then(|i| self.extab.as_ref()?.get(&i))
}
}
fn push_reloc(args: &mut Vec<ObjInsArg>, reloc: &ObjReloc) -> Result<()> {
match reloc.flags {
RelocationFlags::Elf { r_type } => match r_type {
elf::R_PPC_ADDR16_LO => {
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText("@l".into()));
}
elf::R_PPC_ADDR16_HI => {
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText("@h".into()));
}
elf::R_PPC_ADDR16_HA => {
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText("@ha".into()));
}
elf::R_PPC_EMB_SDA21 => {
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText("@sda21".into()));
}
elf::R_PPC_ADDR32 | elf::R_PPC_UADDR32 | elf::R_PPC_REL24 | elf::R_PPC_REL14 => {
args.push(ObjInsArg::Reloc);
}
elf::R_PPC_NONE => {
// Fake pool relocation.
args.push(ObjInsArg::PlainText("<".into()));
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText(">".into()));
}
_ => bail!("Unsupported ELF PPC relocation type {r_type}"),
},
flags => bail!("Unsupported PPC relocation kind: {flags:?}"),
};
Ok(())
}
#[derive(Debug, Clone)]
pub struct ExtabSymbolRef {
pub original_index: usize,
pub name: String,
pub demangled_name: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ExceptionInfo {
pub eti_symbol: ExtabSymbolRef,
pub etb_symbol: ExtabSymbolRef,
pub data: ExceptionTableData,
pub dtors: Vec<ExtabSymbolRef>,
}
fn decode_exception_info(file: &File<'_>) -> Result<Option<BTreeMap<usize, ExceptionInfo>>> {
let Some(extab_section) = file.section_by_name("extab") else {
return Ok(None);
};
let Some(extabindex_section) = file.section_by_name("extabindex") else {
return Ok(None);
};
let mut result = BTreeMap::new();
let extab_relocations = extab_section.relocations().collect::<BTreeMap<u64, Relocation>>();
let extabindex_relocations =
extabindex_section.relocations().collect::<BTreeMap<u64, Relocation>>();
for extabindex in file.symbols().filter(|symbol| {
symbol.section_index() == Some(extabindex_section.index())
&& symbol.kind() == SymbolKind::Data
}) {
if extabindex.size() != 12 {
log::warn!("Invalid extabindex entry size {}", extabindex.size());
continue;
}
// Each extabindex entry has two relocations:
// - 0x0: The function that the exception table is for
// - 0x8: The relevant entry in extab section
let Some(extab_func_reloc) = extabindex_relocations.get(&extabindex.address()) else {
log::warn!("Failed to find function relocation for extabindex entry");
continue;
};
let Some(extab_reloc) = extabindex_relocations.get(&(extabindex.address() + 8)) else {
log::warn!("Failed to find extab relocation for extabindex entry");
continue;
};
// Resolve the function and extab symbols
let Some(extab_func) = relocation_symbol(file, extab_func_reloc)? else {
log::warn!("Failed to find function symbol for extabindex entry");
continue;
};
let extab_func_name = extab_func.name()?;
let Some(extab) = relocation_symbol(file, extab_reloc)? else {
log::warn!("Failed to find extab symbol for extabindex entry");
continue;
};
let extab_start_addr = extab.address() - extab_section.address();
let extab_end_addr = extab_start_addr + extab.size();
// All relocations in the extab section are dtors
let mut dtors: Vec<ExtabSymbolRef> = vec![];
for (_, reloc) in extab_relocations.range(extab_start_addr..extab_end_addr) {
let Some(symbol) = relocation_symbol(file, reloc)? else {
log::warn!("Failed to find symbol for extab relocation");
continue;
};
dtors.push(make_symbol_ref(&symbol)?);
}
// Decode the extab data
let Some(extab_data) = extab_section.data_range(extab_start_addr, extab.size())? else {
log::warn!("Failed to get extab data for function {}", extab_func_name);
continue;
};
let data = match decode_extab(extab_data) {
Ok(decoded_data) => decoded_data,
Err(e) => {
log::warn!(
"Exception table decoding failed for function {}, reason: {}",
extab_func_name,
e.to_string()
);
return Ok(None);
}
};
//Add the new entry to the list
result.insert(extab_func.index().0, ExceptionInfo {
eti_symbol: make_symbol_ref(&extabindex)?,
etb_symbol: make_symbol_ref(&extab)?,
data,
dtors,
});
}
Ok(Some(result))
}
fn relocation_symbol<'data, 'file>(
file: &'file File<'data>,
relocation: &Relocation,
) -> Result<Option<Symbol<'data, 'file>>> {
let addend = relocation.addend();
match relocation.target() {
RelocationTarget::Symbol(idx) => {
ensure!(addend == 0, "Symbol relocations must have zero addend");
Ok(Some(file.symbol_by_index(idx)?))
}
RelocationTarget::Section(idx) => {
ensure!(addend >= 0, "Section relocations must have non-negative addend");
let addend = addend as u64;
Ok(file
.symbols()
.find(|symbol| symbol.section_index() == Some(idx) && symbol.address() == addend))
}
target => bail!("Unsupported relocation target: {target:?}"),
}
}
fn make_symbol_ref(symbol: &Symbol) -> Result<ExtabSymbolRef> {
let name = symbol.name()?.to_string();
let demangled_name = cwdemangle::demangle(&name, &cwdemangle::DemangleOptions::default());
Ok(ExtabSymbolRef { original_index: symbol.index().0, name, demangled_name })
}
fn guess_data_type_from_load_store_inst_op(inst_op: Opcode) -> Option<DataType> {
match inst_op {
Opcode::Lbz | Opcode::Lbzu | Opcode::Lbzux | Opcode::Lbzx => Some(DataType::Int8),
Opcode::Lhz | Opcode::Lhzu | Opcode::Lhzux | Opcode::Lhzx => Some(DataType::Int16),
Opcode::Lha | Opcode::Lhau | Opcode::Lhaux | Opcode::Lhax => Some(DataType::Int16),
Opcode::Lwz | Opcode::Lwzu | Opcode::Lwzux | Opcode::Lwzx => Some(DataType::Int32),
Opcode::Lfs | Opcode::Lfsu | Opcode::Lfsux | Opcode::Lfsx => Some(DataType::Float),
Opcode::Lfd | Opcode::Lfdu | Opcode::Lfdux | Opcode::Lfdx => Some(DataType::Double),
Opcode::Stb | Opcode::Stbu | Opcode::Stbux | Opcode::Stbx => Some(DataType::Int8),
Opcode::Sth | Opcode::Sthu | Opcode::Sthux | Opcode::Sthx => Some(DataType::Int16),
Opcode::Stw | Opcode::Stwu | Opcode::Stwux | Opcode::Stwx => Some(DataType::Int32),
Opcode::Stfs | Opcode::Stfsu | Opcode::Stfsux | Opcode::Stfsx => Some(DataType::Float),
Opcode::Stfd | Opcode::Stfdu | Opcode::Stfdux | Opcode::Stfdx => Some(DataType::Double),
_ => None,
}
}
// Given an instruction, determine if it could accessing data at the address in a register.
// If so, return the offset added to the register's address, the register containing that address,
// and (optionally) which destination register the address is being copied into.
fn get_offset_and_addr_gpr_for_possible_pool_reference(
opcode: Opcode,
simplified: &ParsedIns,
) -> Option<(i16, GPR, Option<GPR>)> {
let args = &simplified.args;
if guess_data_type_from_load_store_inst_op(opcode).is_some() {
match (args[1], args[2]) {
(Argument::Offset(offset), Argument::GPR(addr_src_gpr)) => {
// e.g. lwz. Immediate offset.
Some((offset.0, addr_src_gpr, None))
}
(Argument::GPR(addr_src_gpr), Argument::GPR(_offset_gpr)) => {
// e.g. lwzx. The offset is in a register and was likely calculated from an index.
// Treat the offset as being 0 in this case to show the first element of the array.
// It may be possible to show all elements by figuring out the stride of the array
// from the calculations performed on the index before it's put into offset_gpr, but
// this would be much more complicated, so it's not currently done.
Some((0, addr_src_gpr, None))
}
_ => None,
}
} else {
// If it's not a load/store instruction, there's two more possibilities we need to handle.
// 1. It could be loading a pointer to a string.
// 2. It could be moving the relocation address plus an offset into a different register to
// load from later.
// If either of these match, we also want to return the destination register that the
// address is being copied into so that we can detect any future references to that new
// register as well.
match (opcode, args[0], args[1], args[2]) {
(
Opcode::Addi,
Argument::GPR(addr_dst_gpr),
Argument::GPR(addr_src_gpr),
Argument::Simm(simm),
) => Some((simm.0, addr_src_gpr, Some(addr_dst_gpr))),
(
// `mr` or `mr.`
Opcode::Or,
Argument::GPR(addr_dst_gpr),
Argument::GPR(addr_src_gpr),
Argument::None,
) => Some((0, addr_src_gpr, Some(addr_dst_gpr))),
(
Opcode::Add,
Argument::GPR(addr_dst_gpr),
Argument::GPR(addr_src_gpr),
Argument::GPR(_offset_gpr),
) => Some((0, addr_src_gpr, Some(addr_dst_gpr))),
_ => None,
}
}
}
// Remove the relocation we're keeping track of in a particular register when an instruction reuses
// that register to hold some other value, unrelated to pool relocation addresses.
fn clear_overwritten_gprs(ins: Ins, gpr_pool_relocs: &mut HashMap<u8, ObjReloc>) {
let mut def_args = Arguments::default();
ins.parse_defs(&mut def_args);
for arg in def_args {
if let Argument::GPR(gpr) = arg {
if ins.op == Opcode::Lmw {
// `lmw` overwrites all registers from rd to r31.
// ppc750cl only returns rd itself, so we manually clear the rest of them.
for reg in gpr.0..31 {
gpr_pool_relocs.remove(&reg);
}
break;
}
gpr_pool_relocs.remove(&gpr.0);
}
}
}
// We create a fake relocation for an instruction, vaguely simulating what the actual relocation
// might have looked like if it wasn't pooled. This is so minimal changes are needed to display
// pooled accesses vs non-pooled accesses. We set the relocation type to R_PPC_NONE to indicate that
// there isn't really a relocation here, as copying the pool relocation's type wouldn't make sense.
// Also, if this instruction is accessing the middle of a symbol instead of the start, we add an
// addend to indicate that.
fn make_fake_pool_reloc(offset: i16, cur_addr: u32, pool_reloc: &ObjReloc) -> Option<ObjReloc> {
let offset_from_pool = pool_reloc.addend + offset as i64;
let target_address = pool_reloc.target.address.checked_add_signed(offset_from_pool)?;
let target;
let addend;
if pool_reloc.target.orig_section_index.is_some() {
// If the target symbol is within this current object, then we also need to create a fake
// target symbol to go inside our fake relocation. This is because we don't have access to
// list of all symbols in this section, so we can't find the real symbol within the pool
// based on its address yet. Instead we make a placeholder that has the correct
// `orig_section_index` and `address` fields, and then later on when this information is
// displayed to the user, we can find the real symbol by searching through the object's
// section's symbols for one that contains this address.
target = ObjSymbol {
name: "".to_string(),
demangled_name: None,
address: target_address,
section_address: 0,
size: 0,
size_known: false,
kind: Default::default(),
flags: Default::default(),
orig_section_index: pool_reloc.target.orig_section_index,
virtual_address: None,
original_index: None,
bytes: vec![],
};
// The addend is also fake because we don't know yet if the `target_address` here is the exact
// start of the symbol or if it's in the middle of it.
addend = 0;
} else {
// But if the target symbol is in a different object (extern), then we simply copy the pool
// relocation's target. This is because it won't be possible to locate the actual symbol
// later on based only off of an offset without knowing the object or section it's in. And
// doing that for external symbols would also be unnecessary, because when the compiler
// generates an instruction that accesses an external "pool" plus some offset, that won't be
// a normal pool that contains other symbols within it that we want to display. It will be
// something like a vtable for a class with multiple inheritance (for example, dCcD_Cyl in
// The Wind Waker). So just showing that vtable symbol plus an addend to represent the
// offset into it works fine in this case, no fake symbol to hold an address is necessary.
target = pool_reloc.target.clone();
addend = pool_reloc.addend;
};
Some(ObjReloc {
flags: RelocationFlags::Elf { r_type: elf::R_PPC_NONE },
address: cur_addr as u64,
target,
addend,
})
}
// Searches through all instructions in a function, determining which registers have the addresses
// of pooled data relocations in them, finding which instructions load data from those addresses,
// and constructing a mapping of the address of that instruction to a "fake pool relocation" that
// simulates what that instruction's relocation would look like if data hadn't been pooled.
// This method tries to follow the function's proper control flow. It keeps track of a queue of
// states it hasn't traversed yet, where each state holds an instruction address and a HashMap of
// which registers hold which pool relocations at that point.
// When a conditional or unconditional branch is encountered, the destination of the branch is added
// to the queue. Conditional branches will traverse both the path where the branch is taken and the
// one where it's not. Unconditional branches only follow the branch, ignoring any code immediately
// after the branch instruction.
// Limitations: This method cannot read jump tables. This is because the jump tables are located in
// the .data section, but ObjArch.process_code only has access to the .text section. In order to
// work around this limitation and avoid completely missing most code inside switch statements that
// use jump tables, we instead guess that any parts of a function we missed were switch cases, and
// traverse them as if the last `bctr` before that address had branched there. This should be fairly
// accurate in practice - in testing the only instructions it seems to miss are double branches that
// the compiler generates in error which can never be reached during normal execution anyway.
fn generate_fake_pool_reloc_for_addr_mapping(
func_address: u64,
code: &[u8],
relocations: &[ObjReloc],
) -> HashMap<u32, ObjReloc> {
let mut visited_ins_addrs = HashSet::new();
let mut pool_reloc_for_addr = HashMap::new();
let mut ins_iters_with_gpr_state =
vec![(InsIter::new(code, func_address as u32), HashMap::new())];
let mut gpr_state_at_bctr = BTreeMap::new();
while let Some((ins_iter, mut gpr_pool_relocs)) = ins_iters_with_gpr_state.pop() {
for (cur_addr, ins) in ins_iter {
if visited_ins_addrs.contains(&cur_addr) {
// Avoid getting stuck in an infinite loop when following looping branches.
break;
}
visited_ins_addrs.insert(cur_addr);
let simplified = ins.simplified();
// First handle traversing the function's control flow.
let mut branch_dest = None;
for arg in simplified.args_iter() {
if let Argument::BranchDest(dest) = arg {
let dest = cur_addr.wrapping_add_signed(dest.0);
branch_dest = Some(dest);
break;
}
}
if let Some(branch_dest) = branch_dest {
if branch_dest >= func_address as u32
&& (branch_dest - func_address as u32) < code.len() as u32
{
let dest_offset_into_func = branch_dest - func_address as u32;
let dest_code_slice = &code[dest_offset_into_func as usize..];
match ins.op {
Opcode::Bc => {
// Conditional branch.
// Add the branch destination to the queue to do later.
ins_iters_with_gpr_state.push((
InsIter::new(dest_code_slice, branch_dest),
gpr_pool_relocs.clone(),
));
// Then continue on with the current iterator.
}
Opcode::B => {
if simplified.mnemonic != "bl" {
// Unconditional branch.
// Add the branch destination to the queue.
ins_iters_with_gpr_state.push((
InsIter::new(dest_code_slice, branch_dest),
gpr_pool_relocs.clone(),
));
// Break out of the current iterator so we can do the newly added one.
break;
}
}
_ => unreachable!(),
}
}
}
if let Opcode::Bcctr = ins.op {
if simplified.mnemonic == "bctr" {
// Unconditional branch to count register.
// Likely a jump table.
gpr_state_at_bctr.insert(cur_addr, gpr_pool_relocs.clone());
}
}
// Then handle keeping track of which GPR contains which pool relocation.
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
if let Some(reloc) = reloc {
// This instruction has a real relocation, so it may be a pool load we want to keep
// track of.
let args = &simplified.args;
match (ins.op, args[0], args[1], args[2]) {
(
// `lis` + `addi`
Opcode::Addi,
Argument::GPR(addr_dst_gpr),
Argument::GPR(_addr_src_gpr),
Argument::Simm(_simm),
) => {
gpr_pool_relocs.insert(addr_dst_gpr.0, reloc.clone());
}
(
// `lis` + `ori`
Opcode::Ori,
Argument::GPR(addr_dst_gpr),
Argument::GPR(_addr_src_gpr),
Argument::Uimm(_uimm),
) => {
gpr_pool_relocs.insert(addr_dst_gpr.0, reloc.clone());
}
(Opcode::B, _, _, _) => {
if simplified.mnemonic == "bl" {
// When encountering a function call, clear any active pool relocations from
// the volatile registers (r0, r3-r12), but not the nonvolatile registers.
gpr_pool_relocs.remove(&0);
for gpr in 3..12 {
gpr_pool_relocs.remove(&gpr);
}
}
}
_ => {
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
}
}
} else if let Some((offset, addr_src_gpr, addr_dst_gpr)) =
get_offset_and_addr_gpr_for_possible_pool_reference(ins.op, &simplified)
{
// This instruction doesn't have a real relocation, so it may be a reference to one of
// the already-loaded pools.
if let Some(pool_reloc) = gpr_pool_relocs.get(&addr_src_gpr.0) {
if let Some(fake_pool_reloc) =
make_fake_pool_reloc(offset, cur_addr, pool_reloc)
{
pool_reloc_for_addr.insert(cur_addr, fake_pool_reloc);
}
if let Some(addr_dst_gpr) = addr_dst_gpr {
// If the address of the pool relocation got copied into another register, we
// need to keep track of it in that register too as future instructions may
// reference the symbol indirectly via this new register, instead of the
// register the symbol's address was originally loaded into.
// For example, the start of the function might `lis` + `addi` the start of the
// ...data pool into r25, and then later the start of a loop will `addi` r25
// with the offset within the .data section of an array variable into r21.
// Then the body of the loop will `lwzx` one of the array elements from r21.
let mut new_reloc = pool_reloc.clone();
new_reloc.addend += offset as i64;
gpr_pool_relocs.insert(addr_dst_gpr.0, new_reloc);
} else {
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
}
} else {
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
}
} else {
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
}
}
// Finally, if we're about to finish the outer loop and don't have any more control flow to
// follow, we check if there are any instruction addresses in this function that we missed.
// If so, and if there were any `bctr` instructions before those points in this function,
// then we try to traverse those missing spots as switch cases.
if ins_iters_with_gpr_state.is_empty() {
let unseen_addrs = (func_address as u32..func_address as u32 + code.len() as u32)
.step_by(4)
.filter(|addr| !visited_ins_addrs.contains(addr));
for unseen_addr in unseen_addrs {
let prev_bctr_gpr_state = gpr_state_at_bctr
.iter()
.filter(|(&addr, _)| addr < unseen_addr)
.min_by_key(|(&addr, _)| addr)
.map(|(_, gpr_state)| gpr_state);
if let Some(gpr_pool_relocs) = prev_bctr_gpr_state {
let dest_offset_into_func = unseen_addr - func_address as u32;
let dest_code_slice = &code[dest_offset_into_func as usize..];
ins_iters_with_gpr_state.push((
InsIter::new(dest_code_slice, unseen_addr),
gpr_pool_relocs.clone(),
));
break;
}
}
}
}
pool_reloc_for_addr
}

View File

@ -0,0 +1,649 @@
use alloc::{
boxed::Box,
collections::{BTreeMap, BTreeSet},
format,
string::{String, ToString},
vec::Vec,
};
use core::{
ffi::CStr,
ops::{Index, IndexMut},
};
use itertools::Itertools;
use powerpc::{Extensions, Simm};
use crate::{
arch::DataType,
obj::{FlowAnalysisResult, FlowAnalysisValue, Object, Relocation, Symbol},
util::{RawDouble, RawFloat},
};
fn is_store_instruction(op: powerpc::Opcode) -> bool {
use powerpc::Opcode;
matches!(
op,
Opcode::Stbux
| Opcode::Stbx
| Opcode::Stfdux
| Opcode::Stfdx
| Opcode::Stfiwx
| Opcode::Stfsux
| Opcode::Stfsx
| Opcode::Sthbrx
| Opcode::Sthux
| Opcode::Sthx
| Opcode::Stswi
| Opcode::Stswx
| Opcode::Stwbrx
| Opcode::Stwcx_
| Opcode::Stwux
| Opcode::Stwx
| Opcode::Stwu
| Opcode::Stb
| Opcode::Stbu
| Opcode::Sth
| Opcode::Sthu
| Opcode::Stmw
| Opcode::Stfs
| Opcode::Stfsu
| Opcode::Stfd
| Opcode::Stfdu
)
}
pub fn guess_data_type_from_load_store_inst_op(inst_op: powerpc::Opcode) -> Option<DataType> {
use powerpc::Opcode;
match inst_op {
Opcode::Lbz | Opcode::Lbzu | Opcode::Lbzux | Opcode::Lbzx => Some(DataType::Int8),
Opcode::Lhz | Opcode::Lhzu | Opcode::Lhzux | Opcode::Lhzx => Some(DataType::Int16),
Opcode::Lha | Opcode::Lhau | Opcode::Lhaux | Opcode::Lhax => Some(DataType::Int16),
Opcode::Lwz | Opcode::Lwzu | Opcode::Lwzux | Opcode::Lwzx => Some(DataType::Int32),
Opcode::Lfs | Opcode::Lfsu | Opcode::Lfsux | Opcode::Lfsx => Some(DataType::Float),
Opcode::Lfd | Opcode::Lfdu | Opcode::Lfdux | Opcode::Lfdx => Some(DataType::Double),
Opcode::Stb | Opcode::Stbu | Opcode::Stbux | Opcode::Stbx => Some(DataType::Int8),
Opcode::Sth | Opcode::Sthu | Opcode::Sthux | Opcode::Sthx => Some(DataType::Int16),
Opcode::Stw | Opcode::Stwu | Opcode::Stwux | Opcode::Stwx => Some(DataType::Int32),
Opcode::Stfs | Opcode::Stfsu | Opcode::Stfsux | Opcode::Stfsx => Some(DataType::Float),
Opcode::Stfd | Opcode::Stfdu | Opcode::Stfdux | Opcode::Stfdx => Some(DataType::Double),
_ => None,
}
}
#[derive(Default, PartialEq, Eq, Copy, Clone, Debug, PartialOrd, Ord)]
enum RegisterContent {
#[default]
Unknown,
Variable, // Multiple potential values
FloatConstant(RawFloat),
DoubleConstant(RawDouble),
IntConstant(i32),
InputRegister(u8),
Symbol(usize),
}
impl core::fmt::Display for RegisterContent {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
RegisterContent::Unknown => write!(f, "unknown"),
RegisterContent::Variable => write!(f, "variable"),
RegisterContent::IntConstant(i) =>
// -i is safe because it's at most a 16 bit constant in the i32
{
if *i >= 0 {
write!(f, "0x{i:x}")
} else {
write!(f, "-0x{:x}", -i)
}
}
RegisterContent::FloatConstant(RawFloat(fp)) => write!(f, "{fp:?}f"),
RegisterContent::DoubleConstant(RawDouble(fp)) => write!(f, "{fp:?}d"),
RegisterContent::InputRegister(p) => write!(f, "input{p}"),
RegisterContent::Symbol(_u) => write!(f, "relocation"),
}
}
}
#[derive(Clone, PartialEq, Eq, Ord, PartialOrd)]
struct RegisterState {
gpr: [RegisterContent; 32],
fpr: [RegisterContent; 32],
}
impl RegisterState {
fn new() -> Self {
RegisterState { gpr: [RegisterContent::Unknown; 32], fpr: [RegisterContent::Unknown; 32] }
}
// During a function call, these registers must be assumed trashed.
fn clear_volatile(&mut self) {
self[powerpc::GPR(0)] = RegisterContent::Unknown;
for i in 0..=13 {
self[powerpc::GPR(i)] = RegisterContent::Unknown;
}
for i in 0..=13 {
self[powerpc::FPR(i)] = RegisterContent::Unknown;
}
}
// Mark potential input values.
// Subsequent flow analysis will "realize" that they are not actually inputs if
// they get overwritten with another value before getting read.
fn set_potential_inputs(&mut self) {
for g_reg in 3..=13 {
self[powerpc::GPR(g_reg)] = RegisterContent::InputRegister(g_reg);
}
for f_reg in 1..=13 {
self[powerpc::FPR(f_reg)] = RegisterContent::InputRegister(f_reg);
}
}
// If the there is no value, we can take the new known value.
// If there's a known value different than the new value, the content
// must is variable.
// Returns whether the current value was updated.
fn unify_values(current: &mut RegisterContent, new: &RegisterContent) -> bool {
if *current == *new {
false
} else if *current == RegisterContent::Unknown {
*current = *new;
true
} else if *current == RegisterContent::Variable {
// Already variable
false
} else {
*current = RegisterContent::Variable;
true
}
}
// Unify currently known register contents in a give situation with new
// information about the register contents in that situation.
// Currently unknown register contents can be filled, but if there are
// conflicting contents, we go back to unknown.
fn unify(&mut self, other: &RegisterState) -> bool {
let mut updated = false;
for i in 0..32 {
updated |= Self::unify_values(&mut self.gpr[i], &other.gpr[i]);
updated |= Self::unify_values(&mut self.fpr[i], &other.fpr[i]);
}
updated
}
}
impl Index<powerpc::GPR> for RegisterState {
type Output = RegisterContent;
fn index(&self, gpr: powerpc::GPR) -> &Self::Output { &self.gpr[gpr.0 as usize] }
}
impl IndexMut<powerpc::GPR> for RegisterState {
fn index_mut(&mut self, gpr: powerpc::GPR) -> &mut Self::Output {
&mut self.gpr[gpr.0 as usize]
}
}
impl Index<powerpc::FPR> for RegisterState {
type Output = RegisterContent;
fn index(&self, fpr: powerpc::FPR) -> &Self::Output { &self.fpr[fpr.0 as usize] }
}
impl IndexMut<powerpc::FPR> for RegisterState {
fn index_mut(&mut self, fpr: powerpc::FPR) -> &mut Self::Output {
&mut self.fpr[fpr.0 as usize]
}
}
fn execute_instruction(
registers: &mut RegisterState,
op: &powerpc::Opcode,
args: &powerpc::Arguments,
) {
use powerpc::{Argument, GPR, Opcode};
match (op, args[0], args[1], args[2]) {
(Opcode::Or, Argument::GPR(a), Argument::GPR(b), Argument::GPR(c)) => {
// Move is implemented as or with self for ints
if b == c {
registers[a] = registers[b];
} else {
registers[a] = RegisterContent::Unknown;
}
}
(Opcode::Fmr, Argument::FPR(a), Argument::FPR(b), _) => {
registers[a] = registers[b];
}
(Opcode::Addi, Argument::GPR(a), Argument::GPR(GPR(0)), Argument::Simm(c)) => {
// Load immidiate implemented as addi with addend = r0
// Let Addi with other addends fall through to the case which
// overwrites the destination
registers[a] = RegisterContent::IntConstant(c.0 as i32);
}
(Opcode::Bcctr, _, _, _) => {
// Called a function pointer, may have erased volatile registers
registers.clear_volatile();
}
(Opcode::B, _, _, _) => {
if get_branch_offset(args) == 0 {
// Call to another function
registers.clear_volatile();
}
}
(
Opcode::Stbu | Opcode::Sthu | Opcode::Stwu | Opcode::Stfsu | Opcode::Stfdu,
_,
_,
Argument::GPR(rel),
) => {
// Storing with update, clear updated register (third arg)
registers[rel] = RegisterContent::Unknown;
}
(
Opcode::Stbux | Opcode::Sthux | Opcode::Stwux | Opcode::Stfsux | Opcode::Stfdux,
_,
Argument::GPR(rel),
_,
) => {
// Storing indexed with update, clear updated register (second arg)
registers[rel] = RegisterContent::Unknown;
}
(Opcode::Lmw, Argument::GPR(target), _, _) => {
// `lmw` overwrites all registers from rd to r31.
for reg in target.0..31 {
registers[GPR(reg)] = RegisterContent::Unknown;
}
}
(_, Argument::GPR(a), _, _) => {
// Store instructions don't modify the GPR
if !is_store_instruction(*op) {
// Other operations which write to GPR a
registers[a] = RegisterContent::Unknown;
}
}
(_, Argument::FPR(a), _, _) => {
// Store instructions don't modify the FPR
if !is_store_instruction(*op) {
// Other operations which write to FPR a
registers[a] = RegisterContent::Unknown;
}
}
(_, _, _, _) => {}
}
}
fn get_branch_offset(args: &powerpc::Arguments) -> i32 {
for arg in args.iter() {
match arg {
powerpc::Argument::BranchDest(dest) => return dest.0 / 4,
powerpc::Argument::None => break,
_ => {}
}
}
0
}
#[derive(Debug, Default)]
struct PPCFlowAnalysisResult {
argument_contents: BTreeMap<(u64, u8), FlowAnalysisValue>,
}
impl PPCFlowAnalysisResult {
fn set_argument_value_at_address(
&mut self,
address: u64,
argument: u8,
value: FlowAnalysisValue,
) {
self.argument_contents.insert((address, argument), value);
}
fn new() -> Self { PPCFlowAnalysisResult { argument_contents: Default::default() } }
}
impl FlowAnalysisResult for PPCFlowAnalysisResult {
fn get_argument_value_at_address(
&self,
address: u64,
argument: u8,
) -> Option<&FlowAnalysisValue> {
self.argument_contents.get(&(address, argument))
}
}
fn clamp_text_length(s: String, max: usize) -> String {
if s.len() <= max { s } else { format!("{}", s.chars().take(max - 3).collect::<String>()) }
}
fn get_register_content_from_reloc(
reloc: &Relocation,
obj: &Object,
op: powerpc::Opcode,
) -> RegisterContent {
if let Some(bytes) = obj.symbol_data(reloc.target_symbol) {
match guess_data_type_from_load_store_inst_op(op) {
Some(DataType::Float) => {
RegisterContent::FloatConstant(RawFloat(match obj.endianness {
object::Endianness::Little => {
f32::from_le_bytes(bytes.try_into().unwrap_or([0; 4]))
}
object::Endianness::Big => {
f32::from_be_bytes(bytes.try_into().unwrap_or([0; 4]))
}
}))
}
Some(DataType::Double) => {
RegisterContent::DoubleConstant(RawDouble(match obj.endianness {
object::Endianness::Little => {
f64::from_le_bytes(bytes.try_into().unwrap_or([0; 8]))
}
object::Endianness::Big => {
f64::from_be_bytes(bytes.try_into().unwrap_or([0; 8]))
}
}))
}
_ => RegisterContent::Symbol(reloc.target_symbol),
}
} else {
RegisterContent::Symbol(reloc.target_symbol)
}
}
// Executing op with args at cur_address, update current_state with symbols that
// come from relocations. That is, references to globals, floating point
// constants, string constants, etc.
fn fill_registers_from_relocation(
reloc: &Relocation,
current_state: &mut RegisterState,
obj: &Object,
op: powerpc::Opcode,
args: &powerpc::Arguments,
) {
// Only update the register state for loads. We may store to a reloc
// address but that doesn't update register contents.
if !is_store_instruction(op) {
match (op, args[0]) {
// Everything else is a load of some sort
(_, powerpc::Argument::GPR(gpr)) => {
current_state[gpr] = get_register_content_from_reloc(reloc, obj, op);
}
(_, powerpc::Argument::FPR(fpr)) => {
current_state[fpr] = get_register_content_from_reloc(reloc, obj, op);
}
_ => {}
}
}
}
// Special helper fragments generated by MWCC.
// See: https://github.com/encounter/decomp-toolkit/blob/main/src/analysis/pass.rs
const SLEDS: [&str; 6] = ["_savefpr_", "_restfpr_", "_savegpr_", "_restgpr_", "_savev", "_restv"];
fn is_sled_function(name: &str) -> bool { SLEDS.iter().any(|sled| name.starts_with(sled)) }
pub fn ppc_data_flow_analysis(
obj: &Object,
func_symbol: &Symbol,
code: &[u8],
relocations: &[Relocation],
extensions: Extensions,
) -> Box<dyn FlowAnalysisResult> {
use alloc::collections::VecDeque;
use powerpc::InsIter;
let instructions = InsIter::new(code, func_symbol.address as u32, extensions)
.map(|(_addr, ins)| (ins.op, ins.basic().args))
.collect_vec();
let func_address = func_symbol.address;
// Get initial register values from function parameters
let mut initial_register_state = RegisterState::new();
initial_register_state.set_potential_inputs();
let mut execution_queue = VecDeque::<(usize, RegisterState)>::new();
execution_queue.push_back((0, initial_register_state));
// Execute the instructions against abstract data
let mut failsafe_counter = 0;
let mut taken_branches = BTreeSet::<(usize, RegisterState)>::new();
let mut register_state_at = Vec::<RegisterState>::new();
let mut completed_first_pass = false;
register_state_at.resize_with(instructions.len(), RegisterState::new);
while let Some((mut index, mut current_state)) = execution_queue.pop_front() {
while let Some((op, args)) = instructions.get(index) {
// Record the state at this index
// If recording does not result in any changes to the known values
// we're done, because the subsequent values are a function of the
// current values so we'll get the same result as the last time
// we went down this path.
// Don't break out if we haven't even completed the first pass
// through the function though.
if !register_state_at[index].unify(&current_state) && completed_first_pass {
break;
}
// Get symbol used in this instruction
let cur_addr = (func_address as u32) + ((index * 4) as u32);
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
// Is this a branch to a compiler generated helper? These helpers
// do not trash registers like normal function calls, so we don't
// want to treat this as normal execution.
let symbol = reloc.and_then(|r| obj.symbols.get(r.target_symbol));
let is_sled_invocation = symbol.is_some_and(|x| is_sled_function(&x.name));
// Execute the instruction to update the state
// Since sled invocations are only used to save / restore registers
// as part of prelude / cleanup in a function call we don't have to
// do any execution for them.
if !is_sled_invocation {
execute_instruction(&mut current_state, op, args);
}
// Fill in register state coming from relocations at this line. This
// handles references to global variables, floating point constants,
// etc.
if let Some(reloc) = reloc {
fill_registers_from_relocation(reloc, &mut current_state, obj, *op, args);
}
// Add conditional branches to execution queue
// Only take a given (address, register state) combination once. If
// the known register state is different we have to take the branch
// again to stabilize the known values for backwards branches.
if op == &powerpc::Opcode::Bc {
let branch_state = (index, current_state.clone());
if !taken_branches.contains(&branch_state) {
let offset = get_branch_offset(args);
let target_index = ((index as i32) + offset) as usize;
execution_queue.push_back((target_index, current_state.clone()));
taken_branches.insert(branch_state);
// We should never hit this case, but avoid getting stuck in
// an infinite loop if we hit some kind of bad behavior.
failsafe_counter += 1;
if failsafe_counter > 256 {
//println!("Analysis of {} failed to stabilize", func_symbol.name);
return Box::new(PPCFlowAnalysisResult::new());
}
}
}
// Update index
if op == &powerpc::Opcode::B {
// Unconditional branch
let offset = get_branch_offset(args);
if offset > 0 {
// Jump table or branch to over else clause.
index += offset as usize;
} else if offset == 0 {
// Function call with relocation. We'll return to
// the next instruction.
index += 1;
} else {
// Unconditional branch (E.g.: loop { ... })
// Also some compilations of loops put the conditional at
// the end and B to it for the check of the first iteration.
let branch_state = (index, current_state.clone());
if taken_branches.contains(&branch_state) {
break;
}
taken_branches.insert(branch_state);
index = ((index as i32) + offset) as usize;
}
} else {
// Normal execution of next instruction
index += 1;
}
}
// Mark that we've completed at least one pass over the function, at
// this point we can break out if the code we're running doesn't change
// any register outcomes.
completed_first_pass = true;
}
// Store the relevant data flow values for simplified instructions
generate_flow_analysis_result(
obj,
func_address,
code,
register_state_at,
relocations,
extensions,
)
}
fn get_string_data(obj: &Object, symbol_index: usize, offset: Simm) -> Option<&str> {
if let Some(sym) = obj.symbols.get(symbol_index)
&& sym.name.starts_with("@stringBase")
&& offset.0 != 0
&& let Some(data) = obj.symbol_data(symbol_index)
{
let bytes = &data[offset.0 as usize..];
if let Ok(Ok(str)) = CStr::from_bytes_until_nul(bytes).map(|x| x.to_str()) {
return Some(str);
}
}
None
}
// Write the relevant part of the flow analysis out into the FlowAnalysisResult
// the rest of the application will use to query results of the flow analysis.
// Flow analysis will compute the known contents of every register at every
// line, but we only need to record the values of registers that are actually
// referenced at each line.
fn generate_flow_analysis_result(
obj: &Object,
base_address: u64,
code: &[u8],
register_state_at: Vec<RegisterState>,
relocations: &[Relocation],
extensions: Extensions,
) -> Box<PPCFlowAnalysisResult> {
use powerpc::{Argument, InsIter};
let mut analysis_result = PPCFlowAnalysisResult::new();
let default_register_state = RegisterState::new();
for (addr, ins) in InsIter::new(code, 0, extensions) {
let ins_address = base_address + (addr as u64);
let index = addr / 4;
let powerpc::ParsedIns { mnemonic: _, args } = ins.simplified();
// If we're already showing relocations on a line don't also show data flow
let reloc = relocations.iter().find(|r| (r.address & !3) == ins_address);
// Special case to show float and double constants on the line where
// they are being loaded.
// We need to do this before we break out on showing relocations in the
// subsequent if statement.
if let (powerpc::Opcode::Lfs | powerpc::Opcode::Lfd, Some(reloc)) = (ins.op, reloc) {
let content = get_register_content_from_reloc(reloc, obj, ins.op);
if matches!(
content,
RegisterContent::FloatConstant(_) | RegisterContent::DoubleConstant(_)
) {
analysis_result.set_argument_value_at_address(
ins_address,
1,
FlowAnalysisValue::Text(content.to_string()),
);
// Don't need to show any other data flow if we're showing that
continue;
}
}
// Special case to show string constants on the line where they are
// being indexed to. This will typically be "addi t, stringbase, offset"
let registers = register_state_at.get(index as usize).unwrap_or(&default_register_state);
if let (powerpc::Opcode::Addi, Argument::GPR(rel), Argument::Simm(offset)) =
(ins.op, args[1], args[2])
&& let RegisterContent::Symbol(sym_index) = registers[rel]
&& let Some(str) = get_string_data(obj, sym_index, offset)
{
// Show the string constant in the analysis result
let formatted = format!("\"{str}\"");
analysis_result.set_argument_value_at_address(
ins_address,
2,
FlowAnalysisValue::Text(clamp_text_length(formatted, 20)),
);
// Don't continue, we want to show the stringbase value as well
}
let is_store = is_store_instruction(ins.op);
for (arg_index, arg) in args.into_iter().enumerate() {
// Hacky shorthand for determining which arguments are sources,
// We only want to show data flow for source registers, not target
// registers. Technically there are some non-"st_" operations which
// read from their first argument but they're rare.
if (arg_index == 0) && !is_store {
continue;
}
let content = match arg {
Argument::GPR(gpr) => Some(registers[gpr]),
Argument::FPR(fpr) => Some(registers[fpr]),
_ => None,
};
let analysis_value = match content {
Some(RegisterContent::Symbol(s)) => {
if reloc.is_none() {
// Only symbols if there isn't already a relocation, because
// code other than the data flow analysis will be showing
// the symbol for a relocation on the line it is for. If we
// also showed it as data flow analysis value we would be
// showing redundant information.
obj.symbols.get(s).map(|sym| {
FlowAnalysisValue::Text(clamp_text_length(
sym.demangled_name.as_ref().unwrap_or(&sym.name).clone(),
20,
))
})
} else {
None
}
}
Some(RegisterContent::InputRegister(reg)) => {
let reg_name = match arg {
Argument::GPR(_) => format!("in_r{reg}"),
Argument::FPR(_) => format!("in_f{reg}"),
_ => panic!("Register content should only be in a register"),
};
Some(FlowAnalysisValue::Text(reg_name))
}
Some(RegisterContent::Unknown) | Some(RegisterContent::Variable) => None,
Some(value) => Some(FlowAnalysisValue::Text(value.to_string())),
None => None,
};
if let Some(analysis_value) = analysis_value {
analysis_result.set_argument_value_at_address(
ins_address,
arg_index as u8,
analysis_value,
);
}
}
}
Box::new(analysis_result)
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,810 @@
use alloc::{collections::BTreeMap, format, string::String, vec, vec::Vec};
use anyhow::Result;
use object::elf;
use crate::{
arch::{Arch, superh::disasm::sh2_disasm},
diff::{DiffObjConfig, display::InstructionPart},
obj::{InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef},
};
pub mod disasm;
#[derive(Debug)]
pub struct ArchSuperH {}
impl ArchSuperH {
pub fn new(_file: &object::File) -> Result<Self> { Ok(Self {}) }
}
struct DataInfo {
address: u64,
size: u32,
}
impl Arch for ArchSuperH {
fn scan_instructions_internal(
&self,
address: u64,
code: &[u8],
_section_index: usize,
_relocations: &[Relocation],
_diff_config: &DiffObjConfig,
) -> Result<Vec<InstructionRef>> {
let mut ops = Vec::<InstructionRef>::with_capacity(code.len() / 2);
let mut offset = address;
for chunk in code.chunks_exact(2) {
let opcode = u16::from_be_bytes(chunk.try_into().unwrap());
let mut parts: Vec<InstructionPart> = vec![];
let resolved: ResolvedInstructionRef = Default::default();
let mut branch_dest: Option<u64> = None;
sh2_disasm(
offset.try_into().unwrap(),
opcode,
true,
&mut parts,
&resolved,
&mut branch_dest,
);
let opcode_enum: u16 = match parts.first() {
Some(InstructionPart::Opcode(_, val)) => *val,
_ => 0,
};
ops.push(InstructionRef { address: offset, size: 2, opcode: opcode_enum, branch_dest });
offset += 2;
}
Ok(ops)
}
fn display_instruction(
&self,
resolved: ResolvedInstructionRef,
_diff_config: &DiffObjConfig,
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
let opcode = u16::from_be_bytes(resolved.code.try_into().unwrap());
let mut parts: Vec<InstructionPart> = vec![];
let mut branch_dest: Option<u64> = None;
sh2_disasm(0, opcode, true, &mut parts, &resolved, &mut branch_dest);
if let Some(symbol_data) =
resolved.section.data_range(resolved.symbol.address, resolved.symbol.size as usize)
{
// scan for data
// map of instruction offsets to data target offsets
let mut data_offsets = BTreeMap::<u64, DataInfo>::new();
let mut pos: u64 = 0;
for chunk in symbol_data.chunks_exact(2) {
let opcode = u16::from_be_bytes(chunk.try_into().unwrap());
// mov.w
if (opcode & 0xf000) == 0x9000 {
let target = (opcode as u64 & 0xff) * 2 + 4 + pos;
let data_info = DataInfo { address: target, size: 2 };
data_offsets.insert(pos, data_info);
}
// mov.l
else if (opcode & 0xf000) == 0xd000 {
let target = ((opcode as u64 & 0xff) * 4 + 4 + pos) & 0xfffffffc;
let data_info = DataInfo { address: target, size: 4 };
data_offsets.insert(pos, data_info);
}
pos += 2;
}
let pos = resolved.ins_ref.address - resolved.symbol.address;
// add the data info
if let Some(value) = data_offsets.get(&pos) {
if value.size == 2 && value.address as usize + 1 < symbol_data.len() {
let data = u16::from_be_bytes(
symbol_data[value.address as usize..value.address as usize + 2]
.try_into()
.unwrap(),
);
parts.push(InstructionPart::basic(" /* "));
parts.push(InstructionPart::basic("0x"));
parts.push(InstructionPart::basic(format!("{data:04X}")));
parts.push(InstructionPart::basic(" */"));
} else if value.size == 4 && value.address as usize + 3 < symbol_data.len() {
let data = u32::from_be_bytes(
symbol_data[value.address as usize..value.address as usize + 4]
.try_into()
.unwrap(),
);
parts.push(InstructionPart::basic(" /* "));
parts.push(InstructionPart::basic("0x"));
parts.push(InstructionPart::basic(format!("{data:08X}")));
parts.push(InstructionPart::basic(" */"));
}
}
}
for part in parts {
cb(part)?;
}
Ok(())
}
fn demangle(&self, name: &str) -> Option<String> {
cpp_demangle::Symbol::new(name)
.ok()
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
}
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
match flags {
RelocationFlags::Elf(r_type) => match r_type {
elf::R_SH_NONE => Some("R_SH_NONE"),
elf::R_SH_DIR32 => Some("R_SH_DIR32"),
elf::R_SH_REL32 => Some("R_SH_REL32"),
elf::R_SH_DIR8WPN => Some("R_SH_DIR8WPN"),
elf::R_SH_IND12W => Some("R_SH_IND12W"),
elf::R_SH_DIR8WPL => Some("R_SH_DIR8WPL"),
elf::R_SH_DIR8WPZ => Some("R_SH_DIR8WPZ"),
elf::R_SH_DIR8BP => Some("R_SH_DIR8BP"),
elf::R_SH_DIR8W => Some("R_SH_DIR8W"),
elf::R_SH_DIR8L => Some("R_SH_DIR8L"),
elf::R_SH_SWITCH16 => Some("R_SH_SWITCH16"),
elf::R_SH_SWITCH32 => Some("R_SH_SWITCH32"),
elf::R_SH_USES => Some("R_SH_USES"),
elf::R_SH_COUNT => Some("R_SH_COUNT"),
elf::R_SH_ALIGN => Some("R_SH_ALIGN"),
elf::R_SH_CODE => Some("R_SH_CODE"),
elf::R_SH_DATA => Some("R_SH_DATA"),
elf::R_SH_LABEL => Some("R_SH_LABEL"),
elf::R_SH_SWITCH8 => Some("R_SH_SWITCH8"),
elf::R_SH_GNU_VTINHERIT => Some("R_SH_GNU_VTINHERIT"),
elf::R_SH_GNU_VTENTRY => Some("R_SH_GNU_VTENTRY"),
elf::R_SH_TLS_GD_32 => Some("R_SH_TLS_GD_32"),
elf::R_SH_TLS_LD_32 => Some("R_SH_TLS_LD_32"),
elf::R_SH_TLS_LDO_32 => Some("R_SH_TLS_LDO_32"),
elf::R_SH_TLS_IE_32 => Some("R_SH_TLS_IE_32"),
elf::R_SH_TLS_LE_32 => Some("R_SH_TLS_LE_32"),
elf::R_SH_TLS_DTPMOD32 => Some("R_SH_TLS_DTPMOD32"),
elf::R_SH_TLS_DTPOFF32 => Some("R_SH_TLS_DTPOFF32"),
elf::R_SH_TLS_TPOFF32 => Some("R_SH_TLS_TPOFF32"),
elf::R_SH_GOT32 => Some("R_SH_GOT32"),
elf::R_SH_PLT32 => Some("R_SH_PLT32"),
elf::R_SH_COPY => Some("R_SH_COPY"),
elf::R_SH_GLOB_DAT => Some("R_SH_GLOB_DAT"),
elf::R_SH_JMP_SLOT => Some("R_SH_JMP_SLOT"),
elf::R_SH_RELATIVE => Some("R_SH_RELATIVE"),
elf::R_SH_GOTOFF => Some("R_SH_GOTOFF"),
elf::R_SH_GOTPC => Some("R_SH_GOTPC"),
_ => None,
},
_ => None,
}
}
fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
match flags {
RelocationFlags::Elf(elf::R_SH_DIR32) => 4,
RelocationFlags::Elf(_) => 1,
_ => 1,
}
}
}
#[cfg(test)]
mod test {
use std::fmt::{self, Display};
use super::*;
use crate::obj::{InstructionArg, Section, SectionData, Symbol};
impl Display for InstructionPart<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
InstructionPart::Basic(s) => f.write_str(s),
InstructionPart::Opcode(s, _o) => write!(f, "{s} "),
InstructionPart::Arg(arg) => write!(f, "{arg}"),
InstructionPart::Separator => f.write_str(", "),
}
}
}
impl Display for InstructionArg<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
InstructionArg::Value(v) => write!(f, "{v}"),
InstructionArg::BranchDest(v) => write!(f, "{v}"),
InstructionArg::Reloc => f.write_str("reloc"),
}
}
}
#[test]
fn test_sh2_display_instruction_basic_ops() {
let arch = ArchSuperH {};
let ops: [(u16, &str); 8] = [
(0x0008, "clrt "),
(0x0028, "clrmac "),
(0x0019, "div0u "),
(0x0009, "nop "),
(0x002b, "rte "),
(0x000b, "rts "),
(0x0018, "sett "),
(0x001b, "sleep "),
];
for (opcode, expected_str) in ops {
let code = opcode.to_be_bytes();
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1000, size: 2, opcode, branch_dest: None },
code: &code,
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
#[test]
fn test_sh2_display_instruction_f0ff_ops() {
let arch = ArchSuperH {};
let ops: [(u16, &str); 49] = [
(0x4015, "cmp/pl r0"),
(0x4115, "cmp/pl r1"),
(0x4215, "cmp/pl r2"),
(0x4315, "cmp/pl r3"),
(0x4011, "cmp/pz r0"),
(0x4010, "dt r0"),
(0x0029, "movt r0"),
(0x4004, "rotl r0"),
(0x4005, "rotr r0"),
(0x4024, "rotcl r0"),
(0x4025, "rotcr r0"),
(0x4020, "shal r0"),
(0x4021, "shar r0"),
(0x4000, "shll r0"),
(0x4001, "shlr r0"),
(0x4008, "shll2 r0"),
(0x4009, "shlr2 r0"),
(0x4018, "shll8 r0"),
(0x4019, "shlr8 r0"),
(0x4028, "shll16 r0"),
(0x4029, "shlr16 r0"),
(0x0002, "stc sr, r0"),
(0x0012, "stc gbr, r0"),
(0x0022, "stc vbr, r0"),
(0x000a, "sts mach, r0"),
(0x001a, "sts macl, r0"),
(0x402a, "lds r0, pr"),
(0x401b, "tas.b r0"),
(0x4003, "stc.l sr, @-r0"),
(0x4013, "stc.l gbr, @-r0"),
(0x4023, "stc.l vbr, @-r0"),
(0x4002, "sts.l mach, @-r0"),
(0x4012, "sts.l macl, @-r0"),
(0x4022, "sts.l pr, @-r0"),
(0x400e, "ldc r0, sr"),
(0x401e, "ldc r0, gbr"),
(0x402e, "ldc r0, vbr"),
(0x400a, "lds r0, mach"),
(0x401a, "lds r0, macl"),
(0x402b, "jmp @r0"),
(0x400b, "jsr @r0"),
(0x4007, "ldc.l @r0+, sr"),
(0x4017, "ldc.l @r0+, gbr"),
(0x4027, "ldc.l @r0+, vbr"),
(0x4006, "lds.l @r0+, mach"),
(0x4016, "lds.l @r0+, macl"),
(0x4026, "lds.l @r0+, pr"),
(0x0023, "braf r0"),
(0x0003, "bsrf r0"),
];
for (opcode, expected_str) in ops {
let code = opcode.to_be_bytes();
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1000, size: 2, opcode, branch_dest: None },
code: &code,
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
#[test]
fn test_sh2_display_instructions_f00f() {
let arch = ArchSuperH {};
let ops: [(u16, &str); 54] = [
(0x300c, "add r0, r0"),
(0x300e, "addc r0, r0"),
(0x300f, "addv r0, r0"),
(0x2009, "and r0, r0"),
(0x3000, "cmp/eq r0, r0"),
(0x3002, "cmp/hs r0, r0"),
(0x3003, "cmp/ge r0, r0"),
(0x3006, "cmp/hi r0, r0"),
(0x3007, "cmp/gt r0, r0"),
(0x200c, "cmp/str r0, r0"),
(0x3004, "div1 r0, r0"),
(0x2007, "div0s r0, r0"),
(0x300d, "dmuls.l r0, r0"),
(0x3005, "dmulu.l r0, r0"),
(0x600e, "exts.b r0, r0"),
(0x600f, "exts.w r0, r0"),
(0x600c, "extu.b r0, r0"),
(0x600d, "extu.w r0, r0"),
(0x6003, "mov r0, r0"),
(0x0007, "mul.l r0, r0"),
(0x200f, "muls r0, r0"),
(0x200e, "mulu r0, r0"),
(0x600b, "neg r0, r0"),
(0x600a, "negc r0, r0"),
(0x6007, "not r0, r0"),
(0x200b, "or r0, r0"),
(0x3008, "sub r0, r0"),
(0x300a, "subc r0, r0"),
(0x300b, "subv r0, r0"),
(0x6008, "swap.b r0, r0"),
(0x6009, "swap.w r0, r0"),
(0x2008, "tst r0, r0"),
(0x200a, "xor r0, r0"),
(0x200d, "xtrct r0, r0"),
(0x2000, "mov.b r0, @r0"),
(0x2001, "mov.w r0, @r0"),
(0x2002, "mov.l r0, @r0"),
(0x6000, "mov.b @r0, r0"),
(0x6001, "mov.w @r0, r0"),
(0x6002, "mov.l @r0, r0"),
(0x000f, "mac.l @r0+, @r0+"),
(0x400f, "mac.w @r0+, @r0+"),
(0x6004, "mov.b @r0+, r0"),
(0x6005, "mov.w @r0+, r0"),
(0x6006, "mov.l @r0+, r0"),
(0x2004, "mov.b r0, @-r0"),
(0x2005, "mov.w r0, @-r0"),
(0x2006, "mov.l r0, @-r0"),
(0x0004, "mov.b r0, @(r0, r0)"),
(0x0005, "mov.w r0, @(r0, r0)"),
(0x0006, "mov.l r0, @(r0, r0)"),
(0x000c, "mov.b @(r0, r0), r0"),
(0x000d, "mov.w @(r0, r0), r0"),
(0x000e, "mov.l @(r0, r0), r0"),
];
for (opcode, expected_str) in ops {
let code = opcode.to_be_bytes();
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1000, size: 2, opcode, branch_dest: None },
code: &code,
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
#[test]
fn test_sh2_display_instruction_mov_immediate_offset() {
let arch = ArchSuperH {};
let ops: [(u16, &str); 8] = [
(0x8000, "mov.b r0, @(0x0, r0)"),
(0x8011, "mov.b r0, @(0x1, r1)"),
(0x8102, "mov.w r0, @(0x4, r0)"),
(0x8113, "mov.w r0, @(0x6, r1)"),
(0x8404, "mov.b @(0x4, r0), r0"),
(0x8415, "mov.b @(0x5, r1), r0"),
(0x8506, "mov.w @(0xc, r0), r0"),
(0x8517, "mov.w @(0xe, r1), r0"),
];
for (opcode, expected_str) in ops {
let code = opcode.to_be_bytes();
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1000, size: 2, opcode, branch_dest: None },
code: &code,
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
#[test]
fn test_sh2_display_instruction_gbr_and_branches() {
let arch = ArchSuperH {};
let ops: &[(u16, u32, &str)] = &[
(0xc000, 0x0000, "mov.b r0, @(0x0, gbr)"),
(0xc07f, 0x0000, "mov.b r0, @(0x7f, gbr)"),
(0xc100, 0x0000, "mov.w r0, @(0x0, gbr)"),
(0xc17f, 0x0000, "mov.w r0, @(0xfe, gbr)"),
(0xc200, 0x0000, "mov.l r0, @(0x0, gbr)"),
(0xc27f, 0x0000, "mov.l r0, @(0x1fc, gbr)"),
(0xc400, 0x0000, "mov.b @(0x0, gbr), r0"),
(0xc47f, 0x0000, "mov.b @(0x7f, gbr), r0"),
(0xc500, 0x0000, "mov.w @(0x0, gbr), r0"),
(0xc57f, 0x0000, "mov.w @(0xfe, gbr), r0"),
(0xc600, 0x0000, "mov.l @(0x0, gbr), r0"),
(0xc67f, 0x0000, "mov.l @(0x1fc, gbr), r0"),
(0x8b20, 0x1000, "bf 0x44"),
(0x8b80, 0x1000, "bf 0xffffff04"),
(0x8f10, 0x2000, "bf.s 0x24"),
(0x8f90, 0x2000, "bf.s 0xffffff24"),
(0x8904, 0x3000, "bt 0xc"),
(0x8980, 0x3000, "bt 0xffffff04"),
(0x8d04, 0x4000, "bt.s 0xc"),
(0x8d80, 0x4000, "bt.s 0xffffff04"),
];
for &(opcode, addr, expected_str) in ops {
let code = opcode.to_be_bytes();
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef {
address: addr as u64,
size: 2,
opcode,
branch_dest: None,
},
code: &code,
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
#[test]
fn test_sh2_display_instruction_mov_l() {
let arch = ArchSuperH {};
let ops: &[(u16, u32, &str)] = &[
// mov.l rX, @(0xXXX, rY)
(0x1000, 0x0000, "mov.l r0, @(0x0, r0)"),
(0x1001, 0x0000, "mov.l r0, @(0x4, r0)"),
(0x100f, 0x0000, "mov.l r0, @(0x3c, r0)"),
(0x101f, 0x0000, "mov.l r1, @(0x3c, r0)"),
// mov.l @(0xXXX, rY), rX
(0x5000, 0x0000, "mov.l @(0x0, r0), r0"),
];
for &(opcode, addr, expected_str) in ops {
let code = opcode.to_be_bytes();
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef {
address: addr as u64,
size: 2,
opcode,
branch_dest: None,
},
code: &code,
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
#[test]
fn test_sh2_display_instruction_bra_bsr() {
let arch: ArchSuperH = ArchSuperH {};
let ops: &[(u16, u32, &str)] = &[
// bra
(0xa000, 0x0000, "bra 0x4"),
(0xa001, 0x0000, "bra 0x6"),
(0xa800, 0x0000, "bra 0xfffff004"),
(0xa801, 0x0000, "bra 0xfffff006"),
// bsr
(0xb000, 0x0000, "bsr 0x4"),
(0xb001, 0x0000, "bsr 0x6"),
(0xb800, 0x0000, "bsr 0xfffff004"),
(0xb801, 0x0000, "bsr 0xfffff006"),
];
for &(opcode, addr, expected_str) in ops {
let code = opcode.to_be_bytes();
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef {
address: addr as u64,
size: 2,
opcode,
branch_dest: None,
},
code: &code,
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
#[test]
fn test_sh2_display_instruction_operations() {
let arch = ArchSuperH {};
let ops: &[(u16, u32, &str)] = &[
(0xcdff, 0x0000, "and.b #0xff, @(r0, gbr)"),
(0xcfff, 0x0000, "or.b #0xff, @(r0, gbr)"),
(0xccff, 0x0000, "tst.b #0xff, @(r0, gbr)"),
(0xceff, 0x0000, "xor.b #0xff, @(r0, gbr)"),
(0xc9ff, 0x0000, "and #0xff, r0"),
(0x88ff, 0x0000, "cmp/eq #0xff, r0"),
(0xcbff, 0x0000, "or #0xff, r0"),
(0xc8ff, 0x0000, "tst #0xff, r0"),
(0xcaff, 0x0000, "xor #0xff, r0"),
(0xc3ff, 0x0000, "trapa #0xff"),
];
for &(opcode, addr, expected_str) in ops {
let code = opcode.to_be_bytes();
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef {
address: addr as u64,
size: 2,
opcode,
branch_dest: None,
},
code: &code,
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
#[test]
fn test_sh2_add_mov_unknown_instructions() {
let arch = ArchSuperH {};
let ops: &[(u16, u32, &str)] = &[
(0x70FF, 0x0000, "add #0xff, r0"),
(0xE0FF, 0x0000, "mov #0xff, r0"),
(0x0000, 0x0000, ".word 0x0000 /* unknown instruction */"),
];
for &(opcode, addr, expected_str) in ops {
let code = opcode.to_be_bytes();
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef {
address: addr as u64,
size: 2,
opcode,
branch_dest: None,
},
code: &code,
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
#[test]
fn test_sh2_mov_instructions_with_labels() {
let arch = ArchSuperH {};
let ops: &[(u16, u32, &str)] =
&[(0x9000, 0x0000, "mov.w @(0x4, pc), r0"), (0xd000, 0x0000, "mov.l @(0x4, pc), r0")];
for &(opcode, addr, expected_str) in ops {
let code = opcode.to_be_bytes();
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef {
address: addr as u64,
size: 2,
opcode,
branch_dest: None,
},
code: &code,
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
#[test]
fn test_func_0606_f378_mov_w_data_labeling() {
let arch = ArchSuperH {};
let ops: &[(u16, u32, &str)] = &[(0x9000, 0x0606F378, "mov.w @(0x4, pc), r0 /* 0x00B0 */")];
let mut code = Vec::new();
code.extend_from_slice(&0x9000_u16.to_be_bytes());
code.extend_from_slice(&0x0009_u16.to_be_bytes());
code.extend_from_slice(&0x00B0_u16.to_be_bytes());
for &(opcode, addr, expected_str) in ops {
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef {
address: addr as u64,
size: 2,
opcode,
branch_dest: None,
},
code: &opcode.to_be_bytes(),
symbol: &Symbol {
address: 0x0606F378, // func base address
size: code.len() as u64,
..Default::default()
},
section: &Section {
address: 0x0606F378,
size: code.len() as u64,
data: SectionData(code.clone()),
..Default::default()
},
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
#[test]
fn test_func_0606_f378_mov_l_data_labeling() {
let arch = ArchSuperH {};
let ops: &[(u16, u32, &str)] =
&[(0xd000, 0x0606F378, "mov.l @(0x4, pc), r0 /* 0x00B000B0 */")];
let mut code = Vec::new();
code.extend_from_slice(&0xd000_u16.to_be_bytes());
code.extend_from_slice(&0x0009_u16.to_be_bytes());
code.extend_from_slice(&0x00B0_u16.to_be_bytes());
code.extend_from_slice(&0x00B0_u16.to_be_bytes());
for &(opcode, addr, expected_str) in ops {
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef {
address: addr as u64,
size: 2,
opcode,
branch_dest: None,
},
code: &opcode.to_be_bytes(),
symbol: &Symbol {
address: 0x0606F378, // func base address
size: code.len() as u64,
..Default::default()
},
section: &Section {
address: 0x0606F378,
size: code.len() as u64,
data: SectionData(code.clone()),
..Default::default()
},
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
let joined_str: String = parts.iter().map(<_>::to_string).collect();
assert_eq!(joined_str, expected_str.to_string());
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,256 +0,0 @@
#![allow(clippy::needless_lifetimes)] // Generated serde code
use crate::{
diff::{
ObjDataDiff, ObjDataDiffKind, ObjDiff, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo,
ObjInsDiff, ObjInsDiffKind, ObjSectionDiff, ObjSymbolDiff,
},
obj,
obj::{
ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSectionKind, ObjSymbol,
ObjSymbolFlagSet, ObjSymbolFlags,
},
};
// Protobuf diff types
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.rs"));
include!(concat!(env!("OUT_DIR"), "/objdiff.diff.serde.rs"));
impl DiffResult {
pub fn new(left: Option<(&ObjInfo, &ObjDiff)>, right: Option<(&ObjInfo, &ObjDiff)>) -> Self {
Self {
left: left.map(|(obj, diff)| ObjectDiff::new(obj, diff)),
right: right.map(|(obj, diff)| ObjectDiff::new(obj, diff)),
}
}
}
impl ObjectDiff {
pub fn new(obj: &ObjInfo, diff: &ObjDiff) -> Self {
Self {
sections: diff
.sections
.iter()
.enumerate()
.map(|(i, d)| SectionDiff::new(obj, i, d))
.collect(),
}
}
}
impl SectionDiff {
pub fn new(obj: &ObjInfo, section_index: usize, section_diff: &ObjSectionDiff) -> Self {
let section = &obj.sections[section_index];
let symbols = section_diff.symbols.iter().map(|d| SymbolDiff::new(obj, d)).collect();
let data = section_diff.data_diff.iter().map(|d| DataDiff::new(obj, d)).collect();
Self {
name: section.name.to_string(),
kind: SectionKind::from(section.kind) as i32,
size: section.size,
address: section.address,
symbols,
data,
match_percent: section_diff.match_percent,
}
}
}
impl From<ObjSectionKind> for SectionKind {
fn from(value: ObjSectionKind) -> Self {
match value {
ObjSectionKind::Code => SectionKind::SectionText,
ObjSectionKind::Data => SectionKind::SectionData,
ObjSectionKind::Bss => SectionKind::SectionBss,
// TODO common
}
}
}
impl From<obj::SymbolRef> for SymbolRef {
fn from(value: obj::SymbolRef) -> Self {
Self {
section_index: if value.section_idx == obj::SECTION_COMMON {
None
} else {
Some(value.section_idx as u32)
},
symbol_index: value.symbol_idx as u32,
}
}
}
impl SymbolDiff {
pub fn new(object: &ObjInfo, symbol_diff: &ObjSymbolDiff) -> Self {
let (_section, symbol) = object.section_symbol(symbol_diff.symbol_ref);
let instructions = symbol_diff
.instructions
.iter()
.map(|ins_diff| InstructionDiff::new(object, ins_diff))
.collect();
Self {
symbol: Some(Symbol::new(symbol)),
instructions,
match_percent: symbol_diff.match_percent,
target: symbol_diff.target_symbol.map(SymbolRef::from),
}
}
}
impl DataDiff {
pub fn new(_object: &ObjInfo, data_diff: &ObjDataDiff) -> Self {
Self {
kind: DiffKind::from(data_diff.kind) as i32,
data: data_diff.data.clone(),
size: data_diff.len as u64,
}
}
}
impl Symbol {
pub fn new(value: &ObjSymbol) -> Self {
Self {
name: value.name.to_string(),
demangled_name: value.demangled_name.clone(),
address: value.address,
size: value.size,
flags: symbol_flags(value.flags),
}
}
}
fn symbol_flags(value: ObjSymbolFlagSet) -> u32 {
let mut flags = 0u32;
if value.0.contains(ObjSymbolFlags::Global) {
flags |= SymbolFlag::SymbolGlobal as u32;
}
if value.0.contains(ObjSymbolFlags::Local) {
flags |= SymbolFlag::SymbolLocal as u32;
}
if value.0.contains(ObjSymbolFlags::Weak) {
flags |= SymbolFlag::SymbolWeak as u32;
}
if value.0.contains(ObjSymbolFlags::Common) {
flags |= SymbolFlag::SymbolCommon as u32;
}
if value.0.contains(ObjSymbolFlags::Hidden) {
flags |= SymbolFlag::SymbolHidden as u32;
}
flags
}
impl Instruction {
pub fn new(object: &ObjInfo, instruction: &ObjIns) -> Self {
Self {
address: instruction.address,
size: instruction.size as u32,
opcode: instruction.op as u32,
mnemonic: instruction.mnemonic.to_string(),
formatted: instruction.formatted.clone(),
arguments: instruction.args.iter().map(Argument::new).collect(),
relocation: instruction.reloc.as_ref().map(|reloc| Relocation::new(object, reloc)),
branch_dest: instruction.branch_dest,
line_number: instruction.line,
original: instruction.orig.clone(),
}
}
}
impl Argument {
pub fn new(value: &ObjInsArg) -> Self {
Self {
value: Some(match value {
ObjInsArg::PlainText(s) => argument::Value::PlainText(s.to_string()),
ObjInsArg::Arg(v) => argument::Value::Argument(ArgumentValue::new(v)),
ObjInsArg::Reloc => argument::Value::Relocation(ArgumentRelocation {}),
ObjInsArg::BranchDest(dest) => argument::Value::BranchDest(*dest),
}),
}
}
}
impl ArgumentValue {
pub fn new(value: &ObjInsArgValue) -> Self {
Self {
value: Some(match value {
ObjInsArgValue::Signed(v) => argument_value::Value::Signed(*v),
ObjInsArgValue::Unsigned(v) => argument_value::Value::Unsigned(*v),
ObjInsArgValue::Opaque(v) => argument_value::Value::Opaque(v.to_string()),
}),
}
}
}
impl Relocation {
pub fn new(object: &ObjInfo, reloc: &ObjReloc) -> Self {
Self {
r#type: match reloc.flags {
object::RelocationFlags::Elf { r_type } => r_type,
object::RelocationFlags::MachO { r_type, .. } => r_type as u32,
object::RelocationFlags::Coff { typ } => typ as u32,
object::RelocationFlags::Xcoff { r_rtype, .. } => r_rtype as u32,
_ => unreachable!(),
},
type_name: object.arch.display_reloc(reloc.flags).into_owned(),
target: Some(RelocationTarget {
symbol: Some(Symbol::new(&reloc.target)),
addend: reloc.addend,
}),
}
}
}
impl InstructionDiff {
pub fn new(object: &ObjInfo, instruction_diff: &ObjInsDiff) -> Self {
Self {
instruction: instruction_diff.ins.as_ref().map(|ins| Instruction::new(object, ins)),
diff_kind: DiffKind::from(instruction_diff.kind) as i32,
branch_from: instruction_diff.branch_from.as_ref().map(InstructionBranchFrom::new),
branch_to: instruction_diff.branch_to.as_ref().map(InstructionBranchTo::new),
arg_diff: instruction_diff.arg_diff.iter().map(ArgumentDiff::new).collect(),
}
}
}
impl ArgumentDiff {
pub fn new(value: &Option<ObjInsArgDiff>) -> Self {
Self { diff_index: value.as_ref().map(|v| v.idx as u32) }
}
}
impl From<ObjInsDiffKind> for DiffKind {
fn from(value: ObjInsDiffKind) -> Self {
match value {
ObjInsDiffKind::None => DiffKind::DiffNone,
ObjInsDiffKind::OpMismatch => DiffKind::DiffOpMismatch,
ObjInsDiffKind::ArgMismatch => DiffKind::DiffArgMismatch,
ObjInsDiffKind::Replace => DiffKind::DiffReplace,
ObjInsDiffKind::Delete => DiffKind::DiffDelete,
ObjInsDiffKind::Insert => DiffKind::DiffInsert,
}
}
}
impl From<ObjDataDiffKind> for DiffKind {
fn from(value: ObjDataDiffKind) -> Self {
match value {
ObjDataDiffKind::None => DiffKind::DiffNone,
ObjDataDiffKind::Replace => DiffKind::DiffReplace,
ObjDataDiffKind::Delete => DiffKind::DiffDelete,
ObjDataDiffKind::Insert => DiffKind::DiffInsert,
}
}
}
impl InstructionBranchFrom {
pub fn new(value: &ObjInsBranchFrom) -> Self {
Self {
instruction_index: value.ins_idx.iter().map(|&x| x as u32).collect(),
branch_index: value.branch_idx as u32,
}
}
}
impl InstructionBranchTo {
pub fn new(value: &ObjInsBranchTo) -> Self {
Self { instruction_index: value.ins_idx as u32, branch_index: value.branch_idx as u32 }
}
}

View File

@ -1,5 +1 @@
#[cfg(feature = "any-arch")]
pub mod diff;
pub mod report; pub mod report;
#[cfg(feature = "wasm")]
pub mod wasm;

View File

@ -1,12 +1,18 @@
#![allow(clippy::needless_lifetimes)] // Generated serde code #![allow(clippy::needless_lifetimes)] // Generated serde code
use std::ops::AddAssign;
use anyhow::{bail, Result}; use alloc::{
string::{String, ToString},
vec,
vec::Vec,
};
use core::ops::AddAssign;
use anyhow::{Result, bail};
use prost::Message; use prost::Message;
use serde_json::error::Category;
// Protobuf report types // Protobuf report types
include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs")); include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs"));
#[cfg(feature = "serde")]
include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs")); include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs"));
pub const REPORT_VERSION: u32 = 2; pub const REPORT_VERSION: u32 = 2;
@ -15,23 +21,30 @@ impl Report {
/// Attempts to parse the report as binary protobuf or JSON. /// Attempts to parse the report as binary protobuf or JSON.
pub fn parse(data: &[u8]) -> Result<Self> { pub fn parse(data: &[u8]) -> Result<Self> {
if data.is_empty() { if data.is_empty() {
bail!(std::io::Error::from(std::io::ErrorKind::UnexpectedEof)); bail!("Empty data");
} }
let report = if data[0] == b'{' { let report = if data[0] == b'{' {
// Load as JSON // Load as JSON
Self::from_json(data)? #[cfg(feature = "serde")]
{
Self::from_json(data)?
}
#[cfg(not(feature = "serde"))]
bail!("JSON report parsing requires the `serde` feature")
} else { } else {
// Load as binary protobuf // Load as binary protobuf
Self::decode(data)? Self::decode(data).map_err(|e| anyhow::Error::msg(e.to_string()))?
}; };
Ok(report) Ok(report)
} }
#[cfg(feature = "serde")]
/// Attempts to parse the report as JSON, migrating from the legacy report format if necessary. /// Attempts to parse the report as JSON, migrating from the legacy report format if necessary.
fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> { fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> {
match serde_json::from_slice::<Self>(bytes) { match serde_json::from_slice::<Self>(bytes) {
Ok(report) => Ok(report), Ok(report) => Ok(report),
Err(e) => { Err(e) => {
use serde_json::error::Category;
match e.classify() { match e.classify() {
Category::Io | Category::Eof | Category::Syntax => Err(e), Category::Io | Category::Eof | Category::Syntax => Err(e),
Category::Data => { Category::Data => {
@ -304,7 +317,8 @@ impl FromIterator<Measures> for Measures {
} }
// Older JSON report types // Older JSON report types
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct LegacyReport { struct LegacyReport {
fuzzy_match_percent: f32, fuzzy_match_percent: f32,
total_code: u64, total_code: u64,
@ -341,7 +355,8 @@ impl From<LegacyReport> for Report {
} }
} }
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct LegacyReportUnit { struct LegacyReportUnit {
name: String, name: String,
fuzzy_match_percent: f32, fuzzy_match_percent: f32,
@ -351,11 +366,11 @@ struct LegacyReportUnit {
matched_data: u64, matched_data: u64,
total_functions: u32, total_functions: u32,
matched_functions: u32, matched_functions: u32,
#[serde(skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
complete: Option<bool>, complete: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
module_name: Option<String>, module_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
module_id: Option<u32>, module_id: Option<u32>,
sections: Vec<LegacyReportItem>, sections: Vec<LegacyReportItem>,
functions: Vec<LegacyReportItem>, functions: Vec<LegacyReportItem>,
@ -389,16 +404,20 @@ impl From<LegacyReportUnit> for ReportUnit {
} }
} }
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct LegacyReportItem { struct LegacyReportItem {
name: String, name: String,
#[serde(skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
demangled_name: Option<String>, demangled_name: Option<String>,
#[serde( #[cfg_attr(
default, feature = "serde",
skip_serializing_if = "Option::is_none", serde(
serialize_with = "serialize_hex", default,
deserialize_with = "deserialize_hex" skip_serializing_if = "Option::is_none",
serialize_with = "serialize_hex",
deserialize_with = "deserialize_hex"
)
)] )]
address: Option<u64>, address: Option<u64>,
size: u64, size: u64,
@ -415,19 +434,18 @@ impl From<LegacyReportItem> for ReportItem {
demangled_name: value.demangled_name, demangled_name: value.demangled_name,
virtual_address: value.address, virtual_address: value.address,
}), }),
address: None,
} }
} }
} }
#[cfg(feature = "serde")]
fn serialize_hex<S>(x: &Option<u64>, s: S) -> Result<S::Ok, S::Error> fn serialize_hex<S>(x: &Option<u64>, s: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer { where S: serde::Serializer {
if let Some(x) = x { if let Some(x) = x { s.serialize_str(&format!("{x:#x}")) } else { s.serialize_none() }
s.serialize_str(&format!("{:#x}", x))
} else {
s.serialize_none()
}
} }
#[cfg(feature = "serde")]
fn deserialize_hex<'de, D>(d: D) -> Result<Option<u64>, D::Error> fn deserialize_hex<'de, D>(d: D) -> Result<Option<u64>, D::Error>
where D: serde::Deserializer<'de> { where D: serde::Deserializer<'de> {
use serde::Deserialize; use serde::Deserialize;

View File

@ -1,81 +0,0 @@
use prost::Message;
use wasm_bindgen::prelude::*;
use crate::{bindings::diff::DiffResult, diff, obj};
fn parse_object(
data: Option<Box<[u8]>>,
config: &diff::DiffObjConfig,
) -> Result<Option<obj::ObjInfo>, JsError> {
data.as_ref().map(|data| obj::read::parse(data, config)).transpose().to_js()
}
fn parse_and_run_diff(
left: Option<Box<[u8]>>,
right: Option<Box<[u8]>>,
diff_config: diff::DiffObjConfig,
mapping_config: diff::MappingConfig,
) -> Result<DiffResult, JsError> {
let target = parse_object(left, &diff_config)?;
let base = parse_object(right, &diff_config)?;
run_diff(target.as_ref(), base.as_ref(), diff_config, mapping_config)
}
fn run_diff(
left: Option<&obj::ObjInfo>,
right: Option<&obj::ObjInfo>,
diff_config: diff::DiffObjConfig,
mapping_config: diff::MappingConfig,
) -> Result<DiffResult, JsError> {
log::debug!("Running diff with config: {:?}", diff_config);
let result = diff::diff_objs(&diff_config, &mapping_config, left, right, None).to_js()?;
let left = left.and_then(|o| result.left.as_ref().map(|d| (o, d)));
let right = right.and_then(|o| result.right.as_ref().map(|d| (o, d)));
Ok(DiffResult::new(left, right))
}
// #[wasm_bindgen]
// pub fn run_diff_json(
// left: Option<Box<[u8]>>,
// right: Option<Box<[u8]>>,
// config: diff::DiffObjConfig,
// ) -> Result<String, JsError> {
// let out = run_diff_opt_box(left, right, config)?;
// serde_json::to_string(&out).map_err(|e| JsError::new(&e.to_string()))
// }
#[wasm_bindgen]
pub fn run_diff_proto(
left: Option<Box<[u8]>>,
right: Option<Box<[u8]>>,
diff_config: diff::DiffObjConfig,
mapping_config: diff::MappingConfig,
) -> Result<Box<[u8]>, JsError> {
let out = parse_and_run_diff(left, right, diff_config, mapping_config)?;
Ok(out.encode_to_vec().into_boxed_slice())
}
#[wasm_bindgen(start)]
fn start() -> Result<(), JsError> {
console_error_panic_hook::set_once();
#[cfg(debug_assertions)]
console_log::init_with_level(log::Level::Debug).to_js()?;
#[cfg(not(debug_assertions))]
console_log::init_with_level(log::Level::Info).to_js()?;
Ok(())
}
#[inline]
fn to_js_error(e: impl std::fmt::Display) -> JsError { JsError::new(&e.to_string()) }
trait ToJsResult {
type Output;
fn to_js(self) -> Result<Self::Output, JsError>;
}
impl<T, E: std::fmt::Display> ToJsResult for Result<T, E> {
type Output = T;
fn to_js(self) -> Result<T, JsError> { self.map_err(to_js_error) }
}

View File

@ -1,9 +1,8 @@
pub mod watcher; pub mod watcher;
use std::{ use std::process::Command;
path::{Path, PathBuf},
process::Command, use typed_path::{Utf8PlatformPathBuf, Utf8UnixPath};
};
pub struct BuildStatus { pub struct BuildStatus {
pub success: bool, pub success: bool,
@ -25,14 +24,14 @@ impl Default for BuildStatus {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct BuildConfig { pub struct BuildConfig {
pub project_dir: Option<PathBuf>, pub project_dir: Option<Utf8PlatformPathBuf>,
pub custom_make: Option<String>, pub custom_make: Option<String>,
pub custom_args: Option<Vec<String>>, pub custom_args: Option<Vec<String>>,
#[allow(unused)] #[allow(unused)]
pub selected_wsl_distro: Option<String>, pub selected_wsl_distro: Option<String>,
} }
pub fn run_make(config: &BuildConfig, arg: &Path) -> BuildStatus { pub fn run_make(config: &BuildConfig, arg: &Utf8UnixPath) -> BuildStatus {
let Some(cwd) = &config.project_dir else { let Some(cwd) = &config.project_dir else {
return BuildStatus { return BuildStatus {
success: false, success: false,
@ -50,9 +49,9 @@ pub fn run_make(config: &BuildConfig, arg: &Path) -> BuildStatus {
}; };
#[cfg(windows)] #[cfg(windows)]
let mut command = { let mut command = {
use alloc::borrow::Cow;
use std::os::windows::process::CommandExt; use std::os::windows::process::CommandExt;
use path_slash::PathExt;
let mut command = if config.selected_wsl_distro.is_some() { let mut command = if config.selected_wsl_distro.is_some() {
Command::new("wsl") Command::new("wsl")
} else { } else {
@ -62,21 +61,25 @@ pub fn run_make(config: &BuildConfig, arg: &Path) -> BuildStatus {
// Strip distro root prefix \\wsl.localhost\{distro} // Strip distro root prefix \\wsl.localhost\{distro}
let wsl_path_prefix = format!("\\\\wsl.localhost\\{}", distro); let wsl_path_prefix = format!("\\\\wsl.localhost\\{}", distro);
let cwd = match cwd.strip_prefix(wsl_path_prefix) { let cwd = match cwd.strip_prefix(wsl_path_prefix) {
Ok(new_cwd) => format!("/{}", new_cwd.to_slash_lossy().as_ref()), // Convert to absolute Unix path
Err(_) => cwd.to_string_lossy().to_string(), Ok(new_cwd) => Cow::Owned(
Utf8UnixPath::new("/").join(new_cwd.with_unix_encoding()).into_string(),
),
// Otherwise, use the Windows path as is
Err(_) => Cow::Borrowed(cwd.as_str()),
}; };
command command
.arg("--cd") .arg("--cd")
.arg(cwd) .arg::<&str>(cwd.as_ref())
.arg("-d") .arg("-d")
.arg(distro) .arg(distro)
.arg("--") .arg("--")
.arg(make) .arg(make)
.args(make_args) .args(make_args)
.arg(arg.to_slash_lossy().as_ref()); .arg(arg.as_str());
} else { } else {
command.current_dir(cwd).args(make_args).arg(arg.to_slash_lossy().as_ref()); command.current_dir(cwd).args(make_args).arg(arg.as_str());
} }
command.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW); command.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW);
command command

View File

@ -2,8 +2,8 @@ use std::{
fs, fs,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{ sync::{
atomic::{AtomicBool, Ordering},
Arc, Arc,
atomic::{AtomicBool, Ordering},
}, },
task::Waker, task::Waker,
time::Duration, time::Duration,
@ -11,7 +11,7 @@ use std::{
use globset::GlobSet; use globset::GlobSet;
use notify::RecursiveMode; use notify::RecursiveMode;
use notify_debouncer_full::{new_debouncer_opt, DebounceEventResult}; use notify_debouncer_full::{DebounceEventResult, new_debouncer_opt};
pub type Watcher = notify_debouncer_full::Debouncer< pub type Watcher = notify_debouncer_full::Debouncer<
notify::RecommendedWatcher, notify::RecommendedWatcher,
@ -29,6 +29,7 @@ pub fn create_watcher(
modified: Arc<AtomicBool>, modified: Arc<AtomicBool>,
project_dir: &Path, project_dir: &Path,
patterns: GlobSet, patterns: GlobSet,
ignore_patterns: GlobSet,
waker: Waker, waker: Waker,
) -> notify::Result<Watcher> { ) -> notify::Result<Watcher> {
let base_dir = fs::canonicalize(project_dir)?; let base_dir = fs::canonicalize(project_dir)?;
@ -54,8 +55,8 @@ pub fn create_watcher(
let Ok(path) = path.strip_prefix(&base_dir_clone) else { let Ok(path) = path.strip_prefix(&base_dir_clone) else {
continue; continue;
}; };
if patterns.is_match(path) { if patterns.is_match(path) && !ignore_patterns.is_match(path) {
// log::info!("File modified: {}", path.display()); log::debug!("File modified: {}", path.display());
any_match = true; any_match = true;
} }
} }

View File

@ -1,37 +1,49 @@
use std::{ pub mod path;
use alloc::{
collections::BTreeMap, collections::BTreeMap,
fs, string::{String, ToString},
fs::File, vec::Vec,
io::{BufReader, BufWriter, Read},
path::{Path, PathBuf},
}; };
use anyhow::{anyhow, Context, Result}; use anyhow::{Context, Result, anyhow};
use filetime::FileTime;
use globset::{Glob, GlobSet, GlobSetBuilder}; use globset::{Glob, GlobSet, GlobSetBuilder};
use path::unix_path_serde_option;
use typed_path::Utf8UnixPathBuf;
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)] #[derive(Default, Clone)]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
pub struct ProjectConfig { pub struct ProjectConfig {
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub min_version: Option<String>, pub min_version: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub custom_make: Option<String>, pub custom_make: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub custom_args: Option<Vec<String>>, pub custom_args: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(
pub target_dir: Option<PathBuf>, feature = "serde",
#[serde(default, skip_serializing_if = "Option::is_none")] serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
pub base_dir: Option<PathBuf>, )]
#[serde(default, skip_serializing_if = "Option::is_none")] pub target_dir: Option<Utf8UnixPathBuf>,
#[cfg_attr(
feature = "serde",
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
)]
pub base_dir: Option<Utf8UnixPathBuf>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub build_base: Option<bool>, pub build_base: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub build_target: Option<bool>, pub build_target: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub watch_patterns: Option<Vec<String>>, pub watch_patterns: Option<Vec<String>>,
#[serde(default, alias = "objects", skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub ignore_patterns: Option<Vec<String>>,
#[cfg_attr(
feature = "serde",
serde(alias = "objects", skip_serializing_if = "Option::is_none")
)]
pub units: Option<Vec<ProjectObject>>, pub units: Option<Vec<ProjectObject>>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub progress_categories: Option<Vec<ProjectProgressCategory>>, pub progress_categories: Option<Vec<ProjectProgressCategory>>,
} }
@ -39,11 +51,6 @@ impl ProjectConfig {
#[inline] #[inline]
pub fn units(&self) -> &[ProjectObject] { self.units.as_deref().unwrap_or_default() } pub fn units(&self) -> &[ProjectObject] { self.units.as_deref().unwrap_or_default() }
#[inline]
pub fn units_mut(&mut self) -> &mut Vec<ProjectObject> {
self.units.get_or_insert_with(Vec::new)
}
#[inline] #[inline]
pub fn progress_categories(&self) -> &[ProjectProgressCategory] { pub fn progress_categories(&self) -> &[ProjectProgressCategory] {
self.progress_categories.as_deref().unwrap_or_default() self.progress_categories.as_deref().unwrap_or_default()
@ -61,60 +68,78 @@ impl ProjectConfig {
.map(|s| Glob::new(s)) .map(|s| Glob::new(s))
.collect::<Result<Vec<Glob>, globset::Error>>()? .collect::<Result<Vec<Glob>, globset::Error>>()?
} else { } else {
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect() default_watch_patterns()
})
}
pub fn build_ignore_patterns(&self) -> Result<Vec<Glob>, globset::Error> {
Ok(if let Some(ignore_patterns) = &self.ignore_patterns {
ignore_patterns
.iter()
.map(|s| Glob::new(s))
.collect::<Result<Vec<Glob>, globset::Error>>()?
} else {
default_ignore_patterns()
}) })
} }
} }
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)] #[derive(Default, Clone)]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
pub struct ProjectObject { pub struct ProjectObject {
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub name: Option<String>, pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(
pub path: Option<PathBuf>, feature = "serde",
#[serde(default, skip_serializing_if = "Option::is_none")] serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
pub target_path: Option<PathBuf>, )]
#[serde(default, skip_serializing_if = "Option::is_none")] pub path: Option<Utf8UnixPathBuf>,
pub base_path: Option<PathBuf>, #[cfg_attr(
#[serde(default, skip_serializing_if = "Option::is_none")] feature = "serde",
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
)]
pub target_path: Option<Utf8UnixPathBuf>,
#[cfg_attr(
feature = "serde",
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
)]
pub base_path: Option<Utf8UnixPathBuf>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
#[deprecated(note = "Use metadata.reverse_fn_order")] #[deprecated(note = "Use metadata.reverse_fn_order")]
pub reverse_fn_order: Option<bool>, pub reverse_fn_order: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
#[deprecated(note = "Use metadata.complete")] #[deprecated(note = "Use metadata.complete")]
pub complete: Option<bool>, pub complete: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub scratch: Option<ScratchConfig>, pub scratch: Option<ScratchConfig>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub metadata: Option<ProjectObjectMetadata>, pub metadata: Option<ProjectObjectMetadata>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub symbol_mappings: Option<SymbolMappings>, pub symbol_mappings: Option<BTreeMap<String, String>>,
} }
#[cfg_attr(feature = "wasm", tsify_next::declare)] #[derive(Default, Clone)]
pub type SymbolMappings = BTreeMap<String, String>; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))]
pub struct ProjectObjectMetadata { pub struct ProjectObjectMetadata {
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub complete: Option<bool>, pub complete: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub reverse_fn_order: Option<bool>, pub reverse_fn_order: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(
pub source_path: Option<String>, feature = "serde",
#[serde(default, skip_serializing_if = "Option::is_none")] serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
)]
pub source_path: Option<Utf8UnixPathBuf>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub progress_categories: Option<Vec<String>>, pub progress_categories: Option<Vec<String>>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub auto_generated: Option<bool>, pub auto_generated: Option<bool>,
} }
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)] #[derive(Default, Clone)]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
pub struct ProjectProgressCategory { pub struct ProjectProgressCategory {
#[serde(default)]
pub id: String, pub id: String,
#[serde(default)]
pub name: String, pub name: String,
} }
@ -123,33 +148,12 @@ impl ProjectObject {
if let Some(name) = &self.name { if let Some(name) = &self.name {
name name
} else if let Some(path) = &self.path { } else if let Some(path) = &self.path {
path.to_str().unwrap_or("[invalid path]") path.as_str()
} else { } else {
"[unknown]" "[unknown]"
} }
} }
pub fn resolve_paths(
&mut self,
project_dir: &Path,
target_obj_dir: Option<&Path>,
base_obj_dir: Option<&Path>,
) {
if let (Some(target_obj_dir), Some(path), None) =
(target_obj_dir, &self.path, &self.target_path)
{
self.target_path = Some(target_obj_dir.join(path));
} else if let Some(path) = &self.target_path {
self.target_path = Some(project_dir.join(path));
}
if let (Some(base_obj_dir), Some(path), None) = (base_obj_dir, &self.path, &self.base_path)
{
self.base_path = Some(base_obj_dir.join(path));
} else if let Some(path) = &self.base_path {
self.base_path = Some(project_dir.join(path));
}
}
pub fn complete(&self) -> Option<bool> { pub fn complete(&self) -> Option<bool> {
#[expect(deprecated)] #[expect(deprecated)]
self.metadata.as_ref().and_then(|m| m.complete).or(self.complete) self.metadata.as_ref().and_then(|m| m.complete).or(self.complete)
@ -164,25 +168,36 @@ impl ProjectObject {
self.metadata.as_ref().and_then(|m| m.auto_generated).unwrap_or(false) self.metadata.as_ref().and_then(|m| m.auto_generated).unwrap_or(false)
} }
pub fn source_path(&self) -> Option<&String> { pub fn source_path(&self) -> Option<&Utf8UnixPathBuf> {
self.metadata.as_ref().and_then(|m| m.source_path.as_ref()) self.metadata.as_ref().and_then(|m| m.source_path.as_ref())
} }
pub fn progress_categories(&self) -> &[String] {
self.metadata.as_ref().and_then(|m| m.progress_categories.as_deref()).unwrap_or_default()
}
pub fn auto_generated(&self) -> Option<bool> {
self.metadata.as_ref().and_then(|m| m.auto_generated)
}
} }
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] #[derive(Default, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify), tsify(from_wasm_abi))] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(default))]
pub struct ScratchConfig { pub struct ScratchConfig {
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub platform: Option<String>, pub platform: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub compiler: Option<String>, pub compiler: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub c_flags: Option<String>, pub c_flags: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(
pub ctx_path: Option<PathBuf>, feature = "serde",
#[serde(default, skip_serializing_if = "Option::is_none")] serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
)]
pub ctx_path: Option<Utf8UnixPathBuf>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub build_ctx: Option<bool>, pub build_ctx: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub preset_id: Option<u32>, pub preset_id: Option<u32>,
} }
@ -193,20 +208,30 @@ pub const DEFAULT_WATCH_PATTERNS: &[&str] = &[
"*.inc", "*.py", "*.yml", "*.txt", "*.json", "*.inc", "*.py", "*.yml", "*.txt", "*.json",
]; ];
pub const DEFAULT_IGNORE_PATTERNS: &[&str] = &["build/**/*"];
pub fn default_watch_patterns() -> Vec<Glob> { pub fn default_watch_patterns() -> Vec<Glob> {
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect() DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
} }
#[derive(Clone, Eq, PartialEq)] pub fn default_ignore_patterns() -> Vec<Glob> {
pub struct ProjectConfigInfo { DEFAULT_IGNORE_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
pub path: PathBuf,
pub timestamp: Option<FileTime>,
} }
pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> { #[cfg(feature = "std")]
#[derive(Clone, Eq, PartialEq)]
pub struct ProjectConfigInfo {
pub path: std::path::PathBuf,
pub timestamp: Option<filetime::FileTime>,
}
#[cfg(feature = "std")]
pub fn try_project_config(
dir: &std::path::Path,
) -> Option<(Result<ProjectConfig>, ProjectConfigInfo)> {
for filename in CONFIG_FILENAMES.iter() { for filename in CONFIG_FILENAMES.iter() {
let config_path = dir.join(filename); let config_path = dir.join(filename);
let Ok(file) = File::open(&config_path) else { let Ok(file) = std::fs::File::open(&config_path) else {
continue; continue;
}; };
let metadata = file.metadata(); let metadata = file.metadata();
@ -214,12 +239,9 @@ pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectC
if !metadata.is_file() { if !metadata.is_file() {
continue; continue;
} }
let ts = FileTime::from_last_modification_time(&metadata); let ts = filetime::FileTime::from_last_modification_time(&metadata);
let mut reader = BufReader::new(file); let mut reader = std::io::BufReader::new(file);
let mut result = match filename.contains("json") { let mut result = read_json_config(&mut reader);
true => read_json_config(&mut reader),
false => read_yml_config(&mut reader),
};
if let Ok(config) = &result { if let Ok(config) = &result {
// Validate min_version if present // Validate min_version if present
if let Err(e) = validate_min_version(config) { if let Err(e) = validate_min_version(config) {
@ -232,40 +254,42 @@ pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectC
None None
} }
#[cfg(feature = "std")]
pub fn save_project_config( pub fn save_project_config(
config: &ProjectConfig, config: &ProjectConfig,
info: &ProjectConfigInfo, info: &ProjectConfigInfo,
) -> Result<ProjectConfigInfo> { ) -> Result<ProjectConfigInfo> {
if let Some(last_ts) = info.timestamp { if let Some(last_ts) = info.timestamp {
// Check if the file has changed since we last read it // Check if the file has changed since we last read it
if let Ok(metadata) = fs::metadata(&info.path) { if let Ok(metadata) = std::fs::metadata(&info.path) {
let ts = FileTime::from_last_modification_time(&metadata); let ts = filetime::FileTime::from_last_modification_time(&metadata);
if ts != last_ts { if ts != last_ts {
return Err(anyhow!("Config file has changed since last read")); return Err(anyhow!("Config file has changed since last read"));
} }
} }
} }
let mut writer = let mut writer = std::io::BufWriter::new(
BufWriter::new(File::create(&info.path).context("Failed to create config file")?); std::fs::File::create(&info.path).context("Failed to create config file")?,
);
let ext = info.path.extension().and_then(|ext| ext.to_str()).unwrap_or("json"); let ext = info.path.extension().and_then(|ext| ext.to_str()).unwrap_or("json");
match ext { match ext {
"json" => serde_json::to_writer_pretty(&mut writer, config).context("Failed to write JSON"), "json" => serde_json::to_writer_pretty(&mut writer, config).context("Failed to write JSON"),
"yml" | "yaml" => {
serde_yaml::to_writer(&mut writer, config).context("Failed to write YAML")
}
_ => Err(anyhow!("Unknown config file extension: {ext}")), _ => Err(anyhow!("Unknown config file extension: {ext}")),
}?; }?;
let file = writer.into_inner().context("Failed to flush file")?; let file = writer.into_inner().context("Failed to flush file")?;
let metadata = file.metadata().context("Failed to get file metadata")?; let metadata = file.metadata().context("Failed to get file metadata")?;
let ts = FileTime::from_last_modification_time(&metadata); let ts = filetime::FileTime::from_last_modification_time(&metadata);
Ok(ProjectConfigInfo { path: info.path.clone(), timestamp: Some(ts) }) Ok(ProjectConfigInfo { path: info.path.clone(), timestamp: Some(ts) })
} }
fn validate_min_version(config: &ProjectConfig) -> Result<()> { fn validate_min_version(config: &ProjectConfig) -> Result<()> {
let Some(min_version) = &config.min_version else { return Ok(()) }; let Some(min_version) = &config.min_version else { return Ok(()) };
let version = semver::Version::parse(env!("CARGO_PKG_VERSION")) let version = semver::Version::parse(env!("CARGO_PKG_VERSION"))
.map_err(|e| anyhow::Error::msg(e.to_string()))
.context("Failed to parse package version")?; .context("Failed to parse package version")?;
let min_version = semver::Version::parse(min_version).context("Failed to parse min_version")?; let min_version = semver::Version::parse(min_version)
.map_err(|e| anyhow::Error::msg(e.to_string()))
.context("Failed to parse min_version")?;
if version >= min_version { if version >= min_version {
Ok(()) Ok(())
} else { } else {
@ -273,15 +297,12 @@ fn validate_min_version(config: &ProjectConfig) -> Result<()> {
} }
} }
fn read_yml_config<R: Read>(reader: &mut R) -> Result<ProjectConfig> { #[cfg(feature = "std")]
Ok(serde_yaml::from_reader(reader)?) fn read_json_config<R: std::io::Read>(reader: &mut R) -> Result<ProjectConfig> {
}
fn read_json_config<R: Read>(reader: &mut R) -> Result<ProjectConfig> {
Ok(serde_json::from_reader(reader)?) Ok(serde_json::from_reader(reader)?)
} }
pub fn build_globset(vec: &[Glob]) -> std::result::Result<GlobSet, globset::Error> { pub fn build_globset(vec: &[Glob]) -> Result<GlobSet, globset::Error> {
let mut builder = GlobSetBuilder::new(); let mut builder = GlobSetBuilder::new();
for glob in vec { for glob in vec {
builder.add(glob.clone()); builder.add(glob.clone());

View File

@ -0,0 +1,57 @@
// For argp::FromArgs
#[cfg(feature = "std")]
pub fn platform_path(value: &str) -> Result<typed_path::Utf8PlatformPathBuf, String> {
Ok(typed_path::Utf8PlatformPathBuf::from(value))
}
/// Checks if the path is valid UTF-8 and returns it as a [`Utf8PlatformPath`].
#[cfg(feature = "std")]
pub fn check_path(
path: &std::path::Path,
) -> Result<&typed_path::Utf8PlatformPath, core::str::Utf8Error> {
typed_path::Utf8PlatformPath::from_bytes_path(typed_path::PlatformPath::new(
path.as_os_str().as_encoded_bytes(),
))
}
/// Checks if the path is valid UTF-8 and returns it as a [`Utf8NativePathBuf`].
#[cfg(feature = "std")]
pub fn check_path_buf(
path: std::path::PathBuf,
) -> Result<typed_path::Utf8PlatformPathBuf, alloc::string::FromUtf8Error> {
typed_path::Utf8PlatformPathBuf::from_bytes_path_buf(typed_path::PlatformPathBuf::from(
path.into_os_string().into_encoded_bytes(),
))
}
#[cfg(feature = "serde")]
pub mod unix_path_serde_option {
use serde::{Deserialize, Deserializer, Serializer};
use typed_path::Utf8UnixPathBuf;
pub fn serialize<S>(path: &Option<Utf8UnixPathBuf>, s: S) -> Result<S::Ok, S::Error>
where S: Serializer {
if let Some(path) = path { s.serialize_some(path.as_str()) } else { s.serialize_none() }
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Utf8UnixPathBuf>, D::Error>
where D: Deserializer<'de> {
Ok(Option::<String>::deserialize(deserializer)?.map(Utf8UnixPathBuf::from))
}
}
#[cfg(all(feature = "serde", feature = "std"))]
pub mod platform_path_serde_option {
use serde::{Deserialize, Deserializer, Serializer};
use typed_path::Utf8PlatformPathBuf;
pub fn serialize<S>(path: &Option<Utf8PlatformPathBuf>, s: S) -> Result<S::Ok, S::Error>
where S: Serializer {
if let Some(path) = path { s.serialize_some(path.as_str()) } else { s.serialize_none() }
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Utf8PlatformPathBuf>, D::Error>
where D: Deserializer<'de> {
Ok(Option::<String>::deserialize(deserializer)?.map(Utf8PlatformPathBuf::from))
}
}

View File

@ -1,413 +1,503 @@
use std::{cmp::max, collections::BTreeMap}; use alloc::{
collections::{BTreeMap, btree_map},
use anyhow::{anyhow, Result}; string::{String, ToString},
use similar::{capture_diff_slices_deadline, Algorithm}; vec,
vec::Vec,
use super::FunctionRelocDiffs;
use crate::{
arch::ProcessCodeResult,
diff::{
DiffObjConfig, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind,
ObjSymbolDiff,
},
obj::{
ObjInfo, ObjIns, ObjInsArg, ObjReloc, ObjSection, ObjSymbol, ObjSymbolFlags, ObjSymbolKind,
SymbolRef,
},
}; };
pub fn process_code_symbol( use anyhow::{Context, Result, anyhow, ensure};
obj: &ObjInfo,
symbol_ref: SymbolRef, use super::{
config: &DiffObjConfig, DiffObjConfig, FunctionRelocDiffs, InstructionArgDiffIndex, InstructionBranchFrom,
) -> Result<ProcessCodeResult> { InstructionBranchTo, InstructionDiffKind, InstructionDiffRow, SymbolDiff,
let (section, symbol) = obj.section_symbol(symbol_ref); display::display_ins_data_literals,
let section = section.ok_or_else(|| anyhow!("Code symbol section not found"))?; };
let code = &section.data use crate::obj::{
[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize]; InstructionArg, InstructionArgValue, InstructionRef, Object, ResolvedInstructionRef,
let mut res = obj.arch.process_code( ResolvedRelocation, ResolvedSymbol, SymbolFlag, SymbolKind,
symbol.address, };
code,
section.orig_index, pub fn no_diff_code(
&section.relocations, obj: &Object,
&section.line_info, symbol_index: usize,
config, diff_config: &DiffObjConfig,
) -> Result<SymbolDiff> {
let symbol = &obj.symbols[symbol_index];
let section_index = symbol.section.ok_or_else(|| anyhow!("Missing section for symbol"))?;
let section = &obj.sections[section_index];
let data = section.data_range(symbol.address, symbol.size as usize).ok_or_else(|| {
anyhow!(
"Symbol data out of bounds: {:#x}..{:#x}",
symbol.address,
symbol.address + symbol.size
)
})?;
let ops = obj.arch.scan_instructions(
ResolvedSymbol { obj, symbol_index, symbol, section_index, section, data },
diff_config,
)?; )?;
let mut instruction_rows = Vec::<InstructionDiffRow>::new();
for inst in res.insts.iter_mut() { for i in &ops {
if let Some(reloc) = &mut inst.reloc { instruction_rows.push(InstructionDiffRow { ins_ref: Some(*i), ..Default::default() });
if reloc.target.size == 0 && reloc.target.name.is_empty() {
// Fake target symbol we added as a placeholder. We need to find the real one.
if let Some(real_target) =
find_symbol_matching_fake_symbol_in_sections(&reloc.target, &obj.sections)
{
reloc.addend = (reloc.target.address - real_target.address) as i64;
reloc.target = real_target;
}
}
}
} }
resolve_branches(&ops, &mut instruction_rows);
Ok(res) Ok(SymbolDiff { target_symbol: None, match_percent: None, diff_score: None, instruction_rows })
} }
pub fn no_diff_code(out: &ProcessCodeResult, symbol_ref: SymbolRef) -> Result<ObjSymbolDiff> { const PENALTY_IMM_DIFF: u64 = 1;
let mut diff = Vec::<ObjInsDiff>::new(); const PENALTY_REG_DIFF: u64 = 5;
for i in &out.insts { const PENALTY_REPLACE: u64 = 60;
diff.push(ObjInsDiff { const PENALTY_INSERT_DELETE: u64 = 100;
ins: Some(i.clone()),
kind: ObjInsDiffKind::None,
..Default::default()
});
}
resolve_branches(&mut diff);
Ok(ObjSymbolDiff { symbol_ref, target_symbol: None, instructions: diff, match_percent: None })
}
pub fn diff_code( pub fn diff_code(
left_obj: &ObjInfo, left_obj: &Object,
right_obj: &ObjInfo, right_obj: &Object,
left_out: &ProcessCodeResult, left_symbol_idx: usize,
right_out: &ProcessCodeResult, right_symbol_idx: usize,
left_symbol_ref: SymbolRef, diff_config: &DiffObjConfig,
right_symbol_ref: SymbolRef, ) -> Result<(SymbolDiff, SymbolDiff)> {
config: &DiffObjConfig, let left_symbol = &left_obj.symbols[left_symbol_idx];
) -> Result<(ObjSymbolDiff, ObjSymbolDiff)> { let right_symbol = &right_obj.symbols[right_symbol_idx];
let mut left_diff = Vec::<ObjInsDiff>::new(); let left_section = left_symbol
let mut right_diff = Vec::<ObjInsDiff>::new(); .section
diff_instructions(&mut left_diff, &mut right_diff, left_out, right_out)?; .and_then(|i| left_obj.sections.get(i))
.ok_or_else(|| anyhow!("Missing section for symbol"))?;
let right_section = right_symbol
.section
.and_then(|i| right_obj.sections.get(i))
.ok_or_else(|| anyhow!("Missing section for symbol"))?;
let left_data = left_section
.data_range(left_symbol.address, left_symbol.size as usize)
.ok_or_else(|| {
anyhow!(
"Symbol data out of bounds: {:#x}..{:#x}",
left_symbol.address,
left_symbol.address + left_symbol.size
)
})?;
let right_data = right_section
.data_range(right_symbol.address, right_symbol.size as usize)
.ok_or_else(|| {
anyhow!(
"Symbol data out of bounds: {:#x}..{:#x}",
right_symbol.address,
right_symbol.address + right_symbol.size
)
})?;
resolve_branches(&mut left_diff); let left_section_idx = left_symbol.section.unwrap();
resolve_branches(&mut right_diff); let right_section_idx = right_symbol.section.unwrap();
let left_ops = left_obj.arch.scan_instructions(
ResolvedSymbol {
obj: left_obj,
symbol_index: left_symbol_idx,
symbol: left_symbol,
section_index: left_section_idx,
section: left_section,
data: left_data,
},
diff_config,
)?;
let right_ops = right_obj.arch.scan_instructions(
ResolvedSymbol {
obj: right_obj,
symbol_index: right_symbol_idx,
symbol: right_symbol,
section_index: right_section_idx,
section: right_section,
data: right_data,
},
diff_config,
)?;
let (mut left_rows, mut right_rows) = diff_instructions(&left_ops, &right_ops)?;
resolve_branches(&left_ops, &mut left_rows);
resolve_branches(&right_ops, &mut right_rows);
let mut diff_state = InsDiffState::default(); let mut diff_state = InstructionDiffState::default();
for (left, right) in left_diff.iter_mut().zip(right_diff.iter_mut()) { for (left_row, right_row) in left_rows.iter_mut().zip(right_rows.iter_mut()) {
let result = compare_ins(config, left_obj, right_obj, left, right, &mut diff_state)?; let result = diff_instruction(
left.kind = result.kind; left_obj,
right.kind = result.kind; right_obj,
left.arg_diff = result.left_args_diff; left_symbol_idx,
right.arg_diff = result.right_args_diff; right_symbol_idx,
left_row.ins_ref,
right_row.ins_ref,
left_row,
right_row,
diff_config,
&mut diff_state,
)?;
left_row.kind = result.kind;
right_row.kind = result.kind;
left_row.arg_diff = result.left_args_diff;
right_row.arg_diff = result.right_args_diff;
} }
let total = left_out.insts.len().max(right_out.insts.len()); let max_score = left_ops.len() as u64 * PENALTY_INSERT_DELETE;
let percent = if diff_state.diff_count >= total { let diff_score = diff_state.diff_score.min(max_score);
0.0 let match_percent = if max_score == 0 {
100.0
} else { } else {
((total - diff_state.diff_count) as f32 / total as f32) * 100.0 ((1.0 - (diff_score as f64 / max_score as f64)) * 100.0) as f32
}; };
Ok(( Ok((
ObjSymbolDiff { SymbolDiff {
symbol_ref: left_symbol_ref, target_symbol: Some(right_symbol_idx),
target_symbol: Some(right_symbol_ref), match_percent: Some(match_percent),
instructions: left_diff, diff_score: Some((diff_score, max_score)),
match_percent: Some(percent), instruction_rows: left_rows,
}, },
ObjSymbolDiff { SymbolDiff {
symbol_ref: right_symbol_ref, target_symbol: Some(left_symbol_idx),
target_symbol: Some(left_symbol_ref), match_percent: Some(match_percent),
instructions: right_diff, diff_score: Some((diff_score, max_score)),
match_percent: Some(percent), instruction_rows: right_rows,
}, },
)) ))
} }
fn diff_instructions( fn diff_instructions(
left_diff: &mut Vec<ObjInsDiff>, left_insts: &[InstructionRef],
right_diff: &mut Vec<ObjInsDiff>, right_insts: &[InstructionRef],
left_code: &ProcessCodeResult, ) -> Result<(Vec<InstructionDiffRow>, Vec<InstructionDiffRow>)> {
right_code: &ProcessCodeResult, let left_ops = left_insts.iter().map(|i| i.opcode).collect::<Vec<_>>();
) -> Result<()> { let right_ops = right_insts.iter().map(|i| i.opcode).collect::<Vec<_>>();
let ops = let ops = similar::capture_diff_slices(similar::Algorithm::Patience, &left_ops, &right_ops);
capture_diff_slices_deadline(Algorithm::Patience, &left_code.ops, &right_code.ops, None);
if ops.is_empty() { if ops.is_empty() {
left_diff.extend( ensure!(left_insts.len() == right_insts.len());
left_code let left_diff = left_insts
.insts .iter()
.iter() .map(|i| InstructionDiffRow { ins_ref: Some(*i), ..Default::default() })
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }), .collect();
); let right_diff = right_insts
right_diff.extend( .iter()
right_code .map(|i| InstructionDiffRow { ins_ref: Some(*i), ..Default::default() })
.insts .collect();
.iter() return Ok((left_diff, right_diff));
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }),
);
return Ok(());
} }
let row_count = ops
.iter()
.map(|op| match *op {
similar::DiffOp::Equal { len, .. } => len,
similar::DiffOp::Delete { old_len, .. } => old_len,
similar::DiffOp::Insert { new_len, .. } => new_len,
similar::DiffOp::Replace { old_len, new_len, .. } => old_len.max(new_len),
})
.sum();
let mut left_diff = Vec::<InstructionDiffRow>::with_capacity(row_count);
let mut right_diff = Vec::<InstructionDiffRow>::with_capacity(row_count);
for op in ops { for op in ops {
let (_tag, left_range, right_range) = op.as_tag_tuple(); let (_tag, left_range, right_range) = op.as_tag_tuple();
let len = max(left_range.len(), right_range.len()); let len = left_range.len().max(right_range.len());
left_diff.extend( left_diff.extend(
left_code.insts[left_range.clone()] left_range
.iter() .clone()
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }), .map(|i| InstructionDiffRow { ins_ref: Some(left_insts[i]), ..Default::default() }),
); );
right_diff.extend( right_diff.extend(
right_code.insts[right_range.clone()] right_range.clone().map(|i| InstructionDiffRow {
.iter() ins_ref: Some(right_insts[i]),
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }), ..Default::default()
}),
); );
if left_range.len() < len { if left_range.len() < len {
left_diff.extend((left_range.len()..len).map(|_| ObjInsDiff::default())); left_diff.extend((left_range.len()..len).map(|_| InstructionDiffRow::default()));
} }
if right_range.len() < len { if right_range.len() < len {
right_diff.extend((right_range.len()..len).map(|_| ObjInsDiff::default())); right_diff.extend((right_range.len()..len).map(|_| InstructionDiffRow::default()));
} }
} }
Ok((left_diff, right_diff))
Ok(())
} }
fn resolve_branches(vec: &mut [ObjInsDiff]) { fn arg_to_string(arg: &InstructionArg, reloc: Option<ResolvedRelocation>) -> String {
let mut branch_idx = 0usize; match arg {
InstructionArg::Value(arg) => arg.to_string(),
InstructionArg::Reloc => {
reloc.as_ref().map_or_else(|| "<unknown>".to_string(), |r| r.symbol.name.clone())
}
InstructionArg::BranchDest(arg) => arg.to_string(),
}
}
fn resolve_branches(ops: &[InstructionRef], rows: &mut [InstructionDiffRow]) {
let mut branch_idx = 0u32;
// Map addresses to indices // Map addresses to indices
let mut addr_map = BTreeMap::<u64, usize>::new(); let mut addr_map = BTreeMap::<u64, u32>::new();
for (i, ins_diff) in vec.iter().enumerate() { for (i, ins_diff) in rows.iter().enumerate() {
if let Some(ins) = &ins_diff.ins { if let Some(ins) = ins_diff.ins_ref {
addr_map.insert(ins.address, i); addr_map.insert(ins.address, i as u32);
} }
} }
// Generate branches // Generate branches
let mut branches = BTreeMap::<usize, ObjInsBranchFrom>::new(); let mut branches = BTreeMap::<u32, InstructionBranchFrom>::new();
for (i, ins_diff) in vec.iter_mut().enumerate() { for ((i, ins_diff), ins) in
if let Some(ins) = &ins_diff.ins { rows.iter_mut().enumerate().filter(|(_, row)| row.ins_ref.is_some()).zip(ops)
if let Some(ins_idx) = ins.branch_dest.and_then(|a| addr_map.get(&a)) { {
if let Some(branch) = branches.get_mut(ins_idx) { if let Some(ins_idx) = ins.branch_dest.and_then(|a| addr_map.get(&a).copied()) {
ins_diff.branch_to = match branches.entry(ins_idx) {
Some(ObjInsBranchTo { ins_idx: *ins_idx, branch_idx: branch.branch_idx }); btree_map::Entry::Vacant(e) => {
branch.ins_idx.push(i); ins_diff.branch_to = Some(InstructionBranchTo { ins_idx, branch_idx });
} else { e.insert(InstructionBranchFrom { ins_idx: vec![i as u32], branch_idx });
ins_diff.branch_to = Some(ObjInsBranchTo { ins_idx: *ins_idx, branch_idx });
branches.insert(*ins_idx, ObjInsBranchFrom { ins_idx: vec![i], branch_idx });
branch_idx += 1; branch_idx += 1;
} }
btree_map::Entry::Occupied(e) => {
let branch = e.into_mut();
ins_diff.branch_to =
Some(InstructionBranchTo { ins_idx, branch_idx: branch.branch_idx });
branch.ins_idx.push(i as u32);
}
} }
} }
} }
// Store branch from // Store branch from
for (i, branch) in branches { for (i, branch) in branches {
vec[i].branch_from = Some(branch); rows[i as usize].branch_from = Some(branch);
} }
} }
fn address_eq(left: &ObjReloc, right: &ObjReloc) -> bool { pub(crate) fn address_eq(left: ResolvedRelocation, right: ResolvedRelocation) -> bool {
left.target.address as i64 + left.addend == right.target.address as i64 + right.addend if right.symbol.size == 0 && left.symbol.size != 0 {
// The base relocation is against a pool but the target relocation isn't.
// This can happen in rare cases where the compiler will generate a pool+addend relocation
// in the base's data, but the one detected in the target is direct with no addend.
// Just check that the final address is the same so these count as a match.
left.symbol.address as i64 + left.relocation.addend
== right.symbol.address as i64 + right.relocation.addend
} else {
// But otherwise, if the compiler isn't using a pool, we're more strict and check that the
// target symbol address and relocation addend both match exactly.
left.symbol.address == right.symbol.address
&& left.relocation.addend == right.relocation.addend
}
} }
pub fn section_name_eq( pub(crate) fn section_name_eq(
left_obj: &ObjInfo, left_obj: &Object,
right_obj: &ObjInfo, right_obj: &Object,
left_orig_section_index: usize, left_section_index: usize,
right_orig_section_index: usize, right_section_index: usize,
) -> bool { ) -> bool {
let Some(left_section) = left_obj.sections.get(left_section_index).is_some_and(|left_section| {
left_obj.sections.iter().find(|s| s.orig_index == left_orig_section_index) right_obj
else { .sections
return false; .get(right_section_index)
}; .is_some_and(|right_section| left_section.name == right_section.name)
let Some(right_section) = })
right_obj.sections.iter().find(|s| s.orig_index == right_orig_section_index)
else {
return false;
};
left_section.name == right_section.name
} }
fn reloc_eq( fn reloc_eq(
config: &DiffObjConfig, left_obj: &Object,
left_obj: &ObjInfo, right_obj: &Object,
right_obj: &ObjInfo, left_ins: ResolvedInstructionRef,
left_ins: Option<&ObjIns>, right_ins: ResolvedInstructionRef,
right_ins: Option<&ObjIns>, diff_config: &DiffObjConfig,
) -> bool { ) -> bool {
let (Some(left_ins), Some(right_ins)) = (left_ins, right_ins) else { let relax_reloc_diffs = diff_config.function_reloc_diffs == FunctionRelocDiffs::None;
return false; let (left_reloc, right_reloc) = match (left_ins.relocation, right_ins.relocation) {
(Some(left_reloc), Some(right_reloc)) => (left_reloc, right_reloc),
// If relocations are relaxed, match if left is missing a reloc
(None, Some(_)) => return relax_reloc_diffs,
(None, None) => return true,
_ => return false,
}; };
let (Some(left), Some(right)) = (&left_ins.reloc, &right_ins.reloc) else { if left_reloc.relocation.flags != right_reloc.relocation.flags {
return false;
};
if left.flags != right.flags {
return false; return false;
} }
if config.function_reloc_diffs == FunctionRelocDiffs::None { if relax_reloc_diffs {
return true; return true;
} }
let symbol_name_matches = left.target.name == right.target.name; let symbol_name_addend_matches = left_reloc.symbol.name == right_reloc.symbol.name
match (&left.target.orig_section_index, &right.target.orig_section_index) { && left_reloc.relocation.addend == right_reloc.relocation.addend;
match (&left_reloc.symbol.section, &right_reloc.symbol.section) {
(Some(sl), Some(sr)) => { (Some(sl), Some(sr)) => {
// Match if section and name or address match // Match if section and name or address match
section_name_eq(left_obj, right_obj, *sl, *sr) section_name_eq(left_obj, right_obj, *sl, *sr)
&& (config.function_reloc_diffs == FunctionRelocDiffs::DataValue && (diff_config.function_reloc_diffs == FunctionRelocDiffs::DataValue
|| symbol_name_matches || symbol_name_addend_matches
|| address_eq(left, right)) || address_eq(left_reloc, right_reloc))
&& (config.function_reloc_diffs == FunctionRelocDiffs::NameAddress && (diff_config.function_reloc_diffs == FunctionRelocDiffs::NameAddress
|| left.target.kind != ObjSymbolKind::Object || left_reloc.symbol.kind != SymbolKind::Object
|| left_obj.arch.display_ins_data(left_ins) || right_reloc.symbol.size == 0 // Likely a pool symbol like ...data, don't treat this as a diff
== left_obj.arch.display_ins_data(right_ins)) || display_ins_data_literals(left_obj, left_ins)
== display_ins_data_literals(right_obj, right_ins))
} }
(Some(_), None) => false,
(None, Some(_)) => { (None, Some(_)) => {
// Match if possibly stripped weak symbol // Match if possibly stripped weak symbol
symbol_name_matches && right.target.flags.0.contains(ObjSymbolFlags::Weak) symbol_name_addend_matches && right_reloc.symbol.flags.contains(SymbolFlag::Weak)
} }
(None, None) => symbol_name_matches, (Some(_), None) | (None, None) => symbol_name_addend_matches,
} }
} }
fn arg_eq( fn arg_eq(
config: &DiffObjConfig, left_obj: &Object,
left_obj: &ObjInfo, right_obj: &Object,
right_obj: &ObjInfo, left_row: &InstructionDiffRow,
left: &ObjInsArg, right_row: &InstructionDiffRow,
right: &ObjInsArg, left_arg: &InstructionArg,
left_diff: &ObjInsDiff, right_arg: &InstructionArg,
right_diff: &ObjInsDiff, left_ins: ResolvedInstructionRef,
right_ins: ResolvedInstructionRef,
diff_config: &DiffObjConfig,
) -> bool { ) -> bool {
match left { match left_arg {
ObjInsArg::PlainText(l) => match right { InstructionArg::Value(l) => match right_arg {
ObjInsArg::PlainText(r) => l == r, InstructionArg::Value(r) => l.loose_eq(r),
_ => false,
},
ObjInsArg::Arg(l) => match right {
ObjInsArg::Arg(r) => l.loose_eq(r),
// If relocations are relaxed, match if left is a constant and right is a reloc // If relocations are relaxed, match if left is a constant and right is a reloc
// Useful for instances where the target object is created without relocations // Useful for instances where the target object is created without relocations
ObjInsArg::Reloc => config.function_reloc_diffs == FunctionRelocDiffs::None, InstructionArg::Reloc => diff_config.function_reloc_diffs == FunctionRelocDiffs::None,
_ => false, _ => false,
}, },
ObjInsArg::Reloc => { InstructionArg::Reloc => {
matches!(right, ObjInsArg::Reloc) matches!(right_arg, InstructionArg::Reloc)
&& reloc_eq( && reloc_eq(left_obj, right_obj, left_ins, right_ins, diff_config)
config,
left_obj,
right_obj,
left_diff.ins.as_ref(),
right_diff.ins.as_ref(),
)
} }
ObjInsArg::BranchDest(_) => match right { InstructionArg::BranchDest(_) => match right_arg {
// Compare dest instruction idx after diffing // Compare dest instruction idx after diffing
ObjInsArg::BranchDest(_) => { InstructionArg::BranchDest(_) => {
left_diff.branch_to.as_ref().map(|b| b.ins_idx) left_row.branch_to.as_ref().map(|b| b.ins_idx)
== right_diff.branch_to.as_ref().map(|b| b.ins_idx) == right_row.branch_to.as_ref().map(|b| b.ins_idx)
} }
// If relocations are relaxed, match if left is a constant and right is a reloc // If relocations are relaxed, match if left is a constant and right is a reloc
// Useful for instances where the target object is created without relocations // Useful for instances where the target object is created without relocations
ObjInsArg::Reloc => config.function_reloc_diffs == FunctionRelocDiffs::None, InstructionArg::Reloc => diff_config.function_reloc_diffs == FunctionRelocDiffs::None,
_ => false, _ => false,
}, },
} }
} }
#[derive(Default)] #[derive(Default)]
struct InsDiffState { struct InstructionDiffState {
diff_count: usize, diff_score: u64,
left_arg_idx: usize, left_arg_idx: u32,
right_arg_idx: usize, right_arg_idx: u32,
left_args_idx: BTreeMap<String, usize>, left_args_idx: BTreeMap<String, u32>,
right_args_idx: BTreeMap<String, usize>, right_args_idx: BTreeMap<String, u32>,
} }
#[derive(Default)] #[derive(Default)]
struct InsDiffResult { struct InstructionDiffResult {
kind: ObjInsDiffKind, kind: InstructionDiffKind,
left_args_diff: Vec<Option<ObjInsArgDiff>>, left_args_diff: Vec<InstructionArgDiffIndex>,
right_args_diff: Vec<Option<ObjInsArgDiff>>, right_args_diff: Vec<InstructionArgDiffIndex>,
} }
fn compare_ins( impl InstructionDiffResult {
config: &DiffObjConfig, #[inline]
left_obj: &ObjInfo, const fn new(kind: InstructionDiffKind) -> Self {
right_obj: &ObjInfo, Self { kind, left_args_diff: Vec::new(), right_args_diff: Vec::new() }
left: &ObjInsDiff, }
right: &ObjInsDiff, }
state: &mut InsDiffState,
) -> Result<InsDiffResult> { fn diff_instruction(
let mut result = InsDiffResult::default(); left_obj: &Object,
if let (Some(left_ins), Some(right_ins)) = (&left.ins, &right.ins) { right_obj: &Object,
// Count only non-PlainText args left_symbol_idx: usize,
let left_args_count = left_ins.iter_args().count(); right_symbol_idx: usize,
let right_args_count = right_ins.iter_args().count(); l: Option<InstructionRef>,
if left_args_count != right_args_count || left_ins.op != right_ins.op { r: Option<InstructionRef>,
// Totally different op left_row: &InstructionDiffRow,
result.kind = ObjInsDiffKind::Replace; right_row: &InstructionDiffRow,
state.diff_count += 1; diff_config: &DiffObjConfig,
return Ok(result); state: &mut InstructionDiffState,
) -> Result<InstructionDiffResult> {
let (l, r) = match (l, r) {
(Some(l), Some(r)) => (l, r),
(Some(_), None) => {
state.diff_score += PENALTY_INSERT_DELETE;
return Ok(InstructionDiffResult::new(InstructionDiffKind::Delete));
} }
(None, Some(_)) => {
state.diff_score += PENALTY_INSERT_DELETE;
return Ok(InstructionDiffResult::new(InstructionDiffKind::Insert));
}
(None, None) => return Ok(InstructionDiffResult::new(InstructionDiffKind::None)),
};
// If opcodes don't match, replace
if l.opcode != r.opcode {
state.diff_score += PENALTY_REPLACE;
return Ok(InstructionDiffResult::new(InstructionDiffKind::Replace));
}
let left_resolved = left_obj
.resolve_instruction_ref(left_symbol_idx, l)
.context("Failed to resolve left instruction")?;
let right_resolved = right_obj
.resolve_instruction_ref(right_symbol_idx, r)
.context("Failed to resolve right instruction")?;
if left_resolved.code != right_resolved.code
|| !reloc_eq(left_obj, right_obj, left_resolved, right_resolved, diff_config)
{
// If either the raw code bytes or relocations don't match, process instructions and compare args
let left_ins = left_obj.arch.process_instruction(left_resolved, diff_config)?;
let right_ins = right_obj.arch.process_instruction(right_resolved, diff_config)?;
if left_ins.args.len() != right_ins.args.len() {
state.diff_score += PENALTY_REPLACE;
return Ok(InstructionDiffResult::new(InstructionDiffKind::Replace));
}
let mut result = InstructionDiffResult::new(InstructionDiffKind::None);
if left_ins.mnemonic != right_ins.mnemonic { if left_ins.mnemonic != right_ins.mnemonic {
// Same op but different mnemonic, still cmp args state.diff_score += PENALTY_REG_DIFF;
result.kind = ObjInsDiffKind::OpMismatch; result.kind = InstructionDiffKind::OpMismatch;
state.diff_count += 1;
} }
for (a, b) in left_ins.iter_args().zip(right_ins.iter_args()) { for (a, b) in left_ins.args.iter().zip(right_ins.args.iter()) {
if arg_eq(config, left_obj, right_obj, a, b, left, right) { if arg_eq(
result.left_args_diff.push(None); left_obj,
result.right_args_diff.push(None); right_obj,
left_row,
right_row,
a,
b,
left_resolved,
right_resolved,
diff_config,
) {
result.left_args_diff.push(InstructionArgDiffIndex::NONE);
result.right_args_diff.push(InstructionArgDiffIndex::NONE);
} else { } else {
if result.kind == ObjInsDiffKind::None { state.diff_score += if let InstructionArg::Value(
result.kind = ObjInsDiffKind::ArgMismatch; InstructionArgValue::Signed(_) | InstructionArgValue::Unsigned(_),
state.diff_count += 1; ) = a
{
PENALTY_IMM_DIFF
} else {
PENALTY_REG_DIFF
};
if result.kind == InstructionDiffKind::None {
result.kind = InstructionDiffKind::ArgMismatch;
} }
let a_str = match a { let a_str = arg_to_string(a, left_resolved.relocation);
ObjInsArg::PlainText(arg) => arg.to_string(), let a_diff = match state.left_args_idx.entry(a_str) {
ObjInsArg::Arg(arg) => arg.to_string(), btree_map::Entry::Vacant(e) => {
ObjInsArg::Reloc => left_ins let idx = state.left_arg_idx;
.reloc state.left_arg_idx = idx + 1;
.as_ref() e.insert(idx);
.map_or_else(|| "<unknown>".to_string(), |r| r.target.name.clone()), idx
ObjInsArg::BranchDest(arg) => arg.to_string(), }
btree_map::Entry::Occupied(e) => *e.get(),
}; };
let a_diff = if let Some(idx) = state.left_args_idx.get(&a_str) { let b_str = arg_to_string(b, right_resolved.relocation);
ObjInsArgDiff { idx: *idx } let b_diff = match state.right_args_idx.entry(b_str) {
} else { btree_map::Entry::Vacant(e) => {
let idx = state.left_arg_idx; let idx = state.right_arg_idx;
state.left_args_idx.insert(a_str, idx); state.right_arg_idx = idx + 1;
state.left_arg_idx += 1; e.insert(idx);
ObjInsArgDiff { idx } idx
}
btree_map::Entry::Occupied(e) => *e.get(),
}; };
let b_str = match b { result.left_args_diff.push(InstructionArgDiffIndex::new(a_diff));
ObjInsArg::PlainText(arg) => arg.to_string(), result.right_args_diff.push(InstructionArgDiffIndex::new(b_diff));
ObjInsArg::Arg(arg) => arg.to_string(),
ObjInsArg::Reloc => right_ins
.reloc
.as_ref()
.map_or_else(|| "<unknown>".to_string(), |r| r.target.name.clone()),
ObjInsArg::BranchDest(arg) => arg.to_string(),
};
let b_diff = if let Some(idx) = state.right_args_idx.get(&b_str) {
ObjInsArgDiff { idx: *idx }
} else {
let idx = state.right_arg_idx;
state.right_args_idx.insert(b_str, idx);
state.right_arg_idx += 1;
ObjInsArgDiff { idx }
};
result.left_args_diff.push(Some(a_diff));
result.right_args_diff.push(Some(b_diff));
} }
} }
} else if left.ins.is_some() { return Ok(result);
result.kind = ObjInsDiffKind::Delete;
state.diff_count += 1;
} else {
result.kind = ObjInsDiffKind::Insert;
state.diff_count += 1;
} }
Ok(result)
}
fn find_symbol_matching_fake_symbol_in_sections( Ok(InstructionDiffResult::new(InstructionDiffKind::None))
fake_symbol: &ObjSymbol,
sections: &[ObjSection],
) -> Option<ObjSymbol> {
let orig_section_index = fake_symbol.orig_section_index?;
let section = sections.iter().find(|s| s.orig_index == orig_section_index)?;
let real_symbol = section
.symbols
.iter()
.find(|s| s.size > 0 && (s.address..s.address + s.size).contains(&fake_symbol.address))?;
Some(real_symbol.clone())
} }

View File

@ -1,138 +1,124 @@
use std::{ use alloc::{vec, vec::Vec};
cmp::{max, min, Ordering}, use core::{cmp::Ordering, ops::Range};
ops::Range,
};
use anyhow::{anyhow, Result}; use anyhow::{Result, anyhow};
use similar::{capture_diff_slices_deadline, get_diff_ratio, Algorithm}; use similar::{Algorithm, capture_diff_slices, get_diff_ratio};
use super::code::section_name_eq; use super::{
use crate::{ DataDiff, DataDiffKind, DataRelocationDiff, ObjectDiff, SectionDiff, SymbolDiff,
diff::{ObjDataDiff, ObjDataDiffKind, ObjSectionDiff, ObjSymbolDiff}, code::{address_eq, section_name_eq},
obj::{ObjInfo, ObjReloc, ObjSection, ObjSymbolFlags, SymbolRef},
}; };
use crate::obj::{Object, Relocation, ResolvedRelocation, Symbol, SymbolFlag, SymbolKind};
pub fn diff_bss_symbol( pub fn diff_bss_symbol(
left_obj: &ObjInfo, left_obj: &Object,
right_obj: &ObjInfo, right_obj: &Object,
left_symbol_ref: SymbolRef, left_symbol_ref: usize,
right_symbol_ref: SymbolRef, right_symbol_ref: usize,
) -> Result<(ObjSymbolDiff, ObjSymbolDiff)> { ) -> Result<(SymbolDiff, SymbolDiff)> {
let (_, left_symbol) = left_obj.section_symbol(left_symbol_ref); let left_symbol = &left_obj.symbols[left_symbol_ref];
let (_, right_symbol) = right_obj.section_symbol(right_symbol_ref); let right_symbol = &right_obj.symbols[right_symbol_ref];
let percent = if left_symbol.size == right_symbol.size { 100.0 } else { 50.0 }; let percent = if left_symbol.size == right_symbol.size { 100.0 } else { 50.0 };
Ok(( Ok((
ObjSymbolDiff { SymbolDiff {
symbol_ref: left_symbol_ref,
target_symbol: Some(right_symbol_ref), target_symbol: Some(right_symbol_ref),
instructions: vec![],
match_percent: Some(percent), match_percent: Some(percent),
diff_score: None,
instruction_rows: vec![],
}, },
ObjSymbolDiff { SymbolDiff {
symbol_ref: right_symbol_ref,
target_symbol: Some(left_symbol_ref), target_symbol: Some(left_symbol_ref),
instructions: vec![],
match_percent: Some(percent), match_percent: Some(percent),
diff_score: None,
instruction_rows: vec![],
}, },
)) ))
} }
pub fn no_diff_symbol(_obj: &ObjInfo, symbol_ref: SymbolRef) -> ObjSymbolDiff { fn reloc_eq(
ObjSymbolDiff { symbol_ref, target_symbol: None, instructions: vec![], match_percent: None } left_obj: &Object,
} right_obj: &Object,
left: ResolvedRelocation,
fn address_eq(left: &ObjReloc, right: &ObjReloc) -> bool { right: ResolvedRelocation,
if right.target.size == 0 && left.target.size != 0 { ) -> bool {
// The base relocation is against a pool but the target relocation isn't. if left.relocation.flags != right.relocation.flags {
// This can happen in rare cases where the compiler will generate a pool+addend relocation
// in the base, but the one detected in the target is direct with no addend.
// Just check that the final address is the same so these count as a match.
left.target.address as i64 + left.addend == right.target.address as i64 + right.addend
} else {
// But otherwise, if the compiler isn't using a pool, we're more strict and check that the
// target symbol address and relocation addend both match exactly.
left.target.address == right.target.address && left.addend == right.addend
}
}
fn reloc_eq(left_obj: &ObjInfo, right_obj: &ObjInfo, left: &ObjReloc, right: &ObjReloc) -> bool {
if left.flags != right.flags {
return false; return false;
} }
let symbol_name_matches = left.target.name == right.target.name; let symbol_name_addend_matches =
match (&left.target.orig_section_index, &right.target.orig_section_index) { left.symbol.name == right.symbol.name && left.relocation.addend == right.relocation.addend;
match (left.symbol.section, right.symbol.section) {
(Some(sl), Some(sr)) => { (Some(sl), Some(sr)) => {
// Match if section and name+addend or address match // Match if section and name+addend or address match
section_name_eq(left_obj, right_obj, *sl, *sr) section_name_eq(left_obj, right_obj, sl, sr)
&& ((symbol_name_matches && left.addend == right.addend) || address_eq(left, right)) && (symbol_name_addend_matches || address_eq(left, right))
} }
(Some(_), None) => false,
(None, Some(_)) => { (None, Some(_)) => {
// Match if possibly stripped weak symbol // Match if possibly stripped weak symbol
(symbol_name_matches && left.addend == right.addend) symbol_name_addend_matches && right.symbol.flags.contains(SymbolFlag::Weak)
&& right.target.flags.0.contains(ObjSymbolFlags::Weak)
} }
(None, None) => symbol_name_matches, (Some(_), None) | (None, None) => symbol_name_addend_matches,
} }
} }
#[inline]
pub fn resolve_relocation<'obj>(
symbols: &'obj [Symbol],
reloc: &'obj Relocation,
) -> ResolvedRelocation<'obj> {
let symbol = &symbols[reloc.target_symbol];
ResolvedRelocation { relocation: reloc, symbol }
}
/// Compares relocations contained with a certain data range. /// Compares relocations contained with a certain data range.
/// The ObjDataDiffKind for each diff will either be `None`` (if the relocation matches), fn diff_data_relocs_for_range<'left, 'right>(
/// or `Replace` (if a relocation was changed, added, or removed). left_obj: &'left Object,
/// `Insert` and `Delete` are not used when a relocation is added or removed to avoid confusing diffs right_obj: &'right Object,
/// where it looks like the bytes themselves were changed but actually only the relocations changed. left_section_idx: usize,
fn diff_data_relocs_for_range( right_section_idx: usize,
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left: &ObjSection,
right: &ObjSection,
left_range: Range<usize>, left_range: Range<usize>,
right_range: Range<usize>, right_range: Range<usize>,
) -> Vec<(ObjDataDiffKind, Option<ObjReloc>, Option<ObjReloc>)> { ) -> Vec<(DataDiffKind, Option<ResolvedRelocation<'left>>, Option<ResolvedRelocation<'right>>)> {
let left_section = &left_obj.sections[left_section_idx];
let right_section = &right_obj.sections[right_section_idx];
let mut diffs = Vec::new(); let mut diffs = Vec::new();
for left_reloc in left.relocations.iter() { for left_reloc in left_section.relocations.iter() {
if !left_range.contains(&(left_reloc.address as usize)) { if !left_range.contains(&(left_reloc.address as usize)) {
continue; continue;
} }
let left_offset = left_reloc.address as usize - left_range.start; let left_offset = left_reloc.address as usize - left_range.start;
let Some(right_reloc) = right.relocations.iter().find(|r| { let left_reloc = resolve_relocation(&left_obj.symbols, left_reloc);
let Some(right_reloc) = right_section.relocations.iter().find(|r| {
if !right_range.contains(&(r.address as usize)) { if !right_range.contains(&(r.address as usize)) {
return false; return false;
} }
let right_offset = r.address as usize - right_range.start; let right_offset = r.address as usize - right_range.start;
right_offset == left_offset right_offset == left_offset
}) else { }) else {
diffs.push((ObjDataDiffKind::Replace, Some(left_reloc.clone()), None)); diffs.push((DataDiffKind::Delete, Some(left_reloc), None));
continue; continue;
}; };
let right_reloc = resolve_relocation(&right_obj.symbols, right_reloc);
if reloc_eq(left_obj, right_obj, left_reloc, right_reloc) { if reloc_eq(left_obj, right_obj, left_reloc, right_reloc) {
diffs.push(( diffs.push((DataDiffKind::None, Some(left_reloc), Some(right_reloc)));
ObjDataDiffKind::None,
Some(left_reloc.clone()),
Some(right_reloc.clone()),
));
} else { } else {
diffs.push(( diffs.push((DataDiffKind::Replace, Some(left_reloc), Some(right_reloc)));
ObjDataDiffKind::Replace,
Some(left_reloc.clone()),
Some(right_reloc.clone()),
));
} }
} }
for right_reloc in right.relocations.iter() { for right_reloc in right_section.relocations.iter() {
if !right_range.contains(&(right_reloc.address as usize)) { if !right_range.contains(&(right_reloc.address as usize)) {
continue; continue;
} }
let right_offset = right_reloc.address as usize - right_range.start; let right_offset = right_reloc.address as usize - right_range.start;
let Some(_) = left.relocations.iter().find(|r| { let right_reloc = resolve_relocation(&right_obj.symbols, right_reloc);
let Some(_) = left_section.relocations.iter().find(|r| {
if !left_range.contains(&(r.address as usize)) { if !left_range.contains(&(r.address as usize)) {
return false; return false;
} }
let left_offset = r.address as usize - left_range.start; let left_offset = r.address as usize - left_range.start;
left_offset == right_offset left_offset == right_offset
}) else { }) else {
diffs.push((ObjDataDiffKind::Replace, None, Some(right_reloc.clone()))); diffs.push((DataDiffKind::Insert, None, Some(right_reloc)));
continue; continue;
}; };
// No need to check the cases for relocations being deleted or matching again. // No need to check the cases for relocations being deleted or matching again.
@ -141,171 +127,113 @@ fn diff_data_relocs_for_range(
diffs diffs
} }
pub fn no_diff_data_section(obj: &Object, section_idx: usize) -> Result<SectionDiff> {
let section = &obj.sections[section_idx];
let len = section.data.len();
let data = &section.data[0..len];
let data_diff =
vec![DataDiff { data: data.to_vec(), kind: DataDiffKind::None, len, ..Default::default() }];
let mut reloc_diffs = Vec::new();
for reloc in section.relocations.iter() {
let reloc_len = obj.arch.data_reloc_size(reloc.flags);
let range = reloc.address as usize..reloc.address as usize + reloc_len;
reloc_diffs.push(DataRelocationDiff {
reloc: reloc.clone(),
kind: DataDiffKind::None,
range,
});
}
Ok(SectionDiff { match_percent: Some(0.0), data_diff, reloc_diff: reloc_diffs })
}
/// Compare the data sections of two object files. /// Compare the data sections of two object files.
pub fn diff_data_section( pub fn diff_data_section(
left_obj: &ObjInfo, left_obj: &Object,
right_obj: &ObjInfo, right_obj: &Object,
left: &ObjSection, left_diff: &ObjectDiff,
right: &ObjSection, right_diff: &ObjectDiff,
left_section_diff: &ObjSectionDiff, left_section_idx: usize,
right_section_diff: &ObjSectionDiff, right_section_idx: usize,
) -> Result<(ObjSectionDiff, ObjSectionDiff)> { ) -> Result<(SectionDiff, SectionDiff)> {
let left_max = let left_section = &left_obj.sections[left_section_idx];
left.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0).min(left.size); let right_section = &right_obj.sections[right_section_idx];
let right_max = let left_max = symbols_matching_section(&left_obj.symbols, left_section_idx)
right.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0).min(right.size); .filter_map(|(_, s)| s.address.checked_sub(left_section.address).map(|a| a + s.size))
let left_data = &left.data[..left_max as usize]; .max()
let right_data = &right.data[..right_max as usize]; .unwrap_or(0)
let ops = capture_diff_slices_deadline(Algorithm::Patience, left_data, right_data, None); .min(left_section.size);
let right_max = symbols_matching_section(&right_obj.symbols, right_section_idx)
.filter_map(|(_, s)| s.address.checked_sub(right_section.address).map(|a| a + s.size))
.max()
.unwrap_or(0)
.min(right_section.size);
let left_data = &left_section.data[..left_max as usize];
let right_data = &right_section.data[..right_max as usize];
let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data);
let match_percent = get_diff_ratio(&ops, left_data.len(), right_data.len()) * 100.0; let match_percent = get_diff_ratio(&ops, left_data.len(), right_data.len()) * 100.0;
let mut left_diff = Vec::<ObjDataDiff>::new(); let mut left_data_diff = Vec::<DataDiff>::new();
let mut right_diff = Vec::<ObjDataDiff>::new(); let mut right_data_diff = Vec::<DataDiff>::new();
for op in ops { for op in ops {
let (tag, left_range, right_range) = op.as_tag_tuple(); let (tag, left_range, right_range) = op.as_tag_tuple();
let left_len = left_range.len(); let left_len = left_range.len();
let right_len = right_range.len(); let right_len = right_range.len();
let mut len = max(left_len, right_len); let mut len = left_len.max(right_len);
let kind = match tag { let kind = match tag {
similar::DiffTag::Equal => ObjDataDiffKind::None, similar::DiffTag::Equal => DataDiffKind::None,
similar::DiffTag::Delete => ObjDataDiffKind::Delete, similar::DiffTag::Delete => DataDiffKind::Delete,
similar::DiffTag::Insert => ObjDataDiffKind::Insert, similar::DiffTag::Insert => DataDiffKind::Insert,
similar::DiffTag::Replace => { similar::DiffTag::Replace => {
// Ensure replacements are equal length // Ensure replacements are equal length
len = min(left_len, right_len); len = left_len.min(right_len);
ObjDataDiffKind::Replace DataDiffKind::Replace
} }
}; };
if kind == ObjDataDiffKind::None { let left_data = &left_section.data[left_range];
let mut found_any_relocs = false; let right_data = &right_section.data[right_range];
let mut left_curr_addr = left_range.start; left_data_diff.push(DataDiff {
let mut right_curr_addr = right_range.start; data: left_data[..len.min(left_data.len())].to_vec(),
for (diff_kind, left_reloc, right_reloc) in diff_data_relocs_for_range(
left_obj,
right_obj,
left,
right,
left_range.clone(),
right_range.clone(),
) {
found_any_relocs = true;
if let Some(left_reloc) = left_reloc {
let left_reloc_addr = left_reloc.address as usize;
if left_reloc_addr > left_curr_addr {
let len = left_reloc_addr - left_curr_addr;
let left_data = &left.data[left_curr_addr..left_reloc_addr];
left_diff.push(ObjDataDiff {
data: left_data[..min(len, left_data.len())].to_vec(),
kind: ObjDataDiffKind::None,
len,
..Default::default()
});
}
let reloc_diff_len = left_obj.arch.get_reloc_byte_size(left_reloc.flags);
let left_data = &left.data[left_reloc_addr..left_reloc_addr + reloc_diff_len];
left_diff.push(ObjDataDiff {
data: left_data[..min(reloc_diff_len, left_data.len())].to_vec(),
kind: diff_kind,
len: reloc_diff_len,
reloc: Some(left_reloc.clone()),
..Default::default()
});
left_curr_addr = left_reloc_addr + reloc_diff_len;
}
if let Some(right_reloc) = right_reloc {
let right_reloc_addr = right_reloc.address as usize;
if right_reloc_addr > right_curr_addr {
let len = right_reloc_addr - right_curr_addr;
let right_data = &right.data[right_curr_addr..right_reloc_addr];
right_diff.push(ObjDataDiff {
data: right_data[..min(len, right_data.len())].to_vec(),
kind: ObjDataDiffKind::None,
len,
..Default::default()
});
}
let reloc_diff_len = right_obj.arch.get_reloc_byte_size(right_reloc.flags);
let right_data =
&right.data[right_reloc_addr..right_reloc_addr + reloc_diff_len];
right_diff.push(ObjDataDiff {
data: right_data[..min(reloc_diff_len, right_data.len())].to_vec(),
kind: diff_kind,
len: reloc_diff_len,
reloc: Some(right_reloc.clone()),
..Default::default()
});
right_curr_addr = right_reloc_addr + reloc_diff_len;
}
}
if found_any_relocs {
if left_curr_addr < left_range.end - 1 {
let len = left_range.end - left_curr_addr;
let left_data = &left.data[left_curr_addr..left_range.end];
left_diff.push(ObjDataDiff {
data: left_data[..min(len, left_data.len())].to_vec(),
kind: ObjDataDiffKind::None,
len,
..Default::default()
});
}
if right_curr_addr < right_range.end - 1 {
let len = right_range.end - right_curr_addr;
let right_data = &right.data[right_curr_addr..right_range.end];
right_diff.push(ObjDataDiff {
data: right_data[..min(len, right_data.len())].to_vec(),
kind: ObjDataDiffKind::None,
len,
..Default::default()
});
}
continue;
}
}
let left_data = &left.data[left_range];
let right_data = &right.data[right_range];
left_diff.push(ObjDataDiff {
data: left_data[..min(len, left_data.len())].to_vec(),
kind, kind,
len, len,
..Default::default() ..Default::default()
}); });
right_diff.push(ObjDataDiff { right_data_diff.push(DataDiff {
data: right_data[..min(len, right_data.len())].to_vec(), data: right_data[..len.min(right_data.len())].to_vec(),
kind, kind,
len, len,
..Default::default() ..Default::default()
}); });
if kind == ObjDataDiffKind::Replace { if kind == DataDiffKind::Replace {
match left_len.cmp(&right_len) { match left_len.cmp(&right_len) {
Ordering::Less => { Ordering::Less => {
let len = right_len - left_len; let len = right_len - left_len;
left_diff.push(ObjDataDiff { left_data_diff.push(DataDiff {
data: vec![], data: vec![],
kind: ObjDataDiffKind::Insert, kind: DataDiffKind::Insert,
len, len,
..Default::default() ..Default::default()
}); });
right_diff.push(ObjDataDiff { right_data_diff.push(DataDiff {
data: right_data[left_len..right_len].to_vec(), data: right_data[left_len..right_len].to_vec(),
kind: ObjDataDiffKind::Insert, kind: DataDiffKind::Insert,
len, len,
..Default::default() ..Default::default()
}); });
} }
Ordering::Greater => { Ordering::Greater => {
let len = left_len - right_len; let len = left_len - right_len;
left_diff.push(ObjDataDiff { left_data_diff.push(DataDiff {
data: left_data[right_len..left_len].to_vec(), data: left_data[right_len..left_len].to_vec(),
kind: ObjDataDiffKind::Delete, kind: DataDiffKind::Delete,
len, len,
..Default::default() ..Default::default()
}); });
right_diff.push(ObjDataDiff { right_data_diff.push(DataDiff {
data: vec![], data: vec![],
kind: ObjDataDiffKind::Delete, kind: DataDiffKind::Delete,
len, len,
..Default::default() ..Default::default()
}); });
@ -315,12 +243,51 @@ pub fn diff_data_section(
} }
} }
let (mut left_section_diff, mut right_section_diff) = let mut left_reloc_diffs = Vec::new();
diff_generic_section(left, right, left_section_diff, right_section_diff)?; let mut right_reloc_diffs = Vec::new();
let all_left_relocs_match = for (diff_kind, left_reloc, right_reloc) in diff_data_relocs_for_range(
left_diff.iter().all(|d| d.kind == ObjDataDiffKind::None || d.reloc.is_none()); left_obj,
left_section_diff.data_diff = left_diff; right_obj,
right_section_diff.data_diff = right_diff; left_section_idx,
right_section_idx,
0..left_max as usize,
0..right_max as usize,
) {
if let Some(left_reloc) = left_reloc {
let len = left_obj.arch.data_reloc_size(left_reloc.relocation.flags);
let range = left_reloc.relocation.address as usize
..left_reloc.relocation.address as usize + len;
left_reloc_diffs.push(DataRelocationDiff {
reloc: left_reloc.relocation.clone(),
kind: diff_kind,
range,
});
}
if let Some(right_reloc) = right_reloc {
let len = right_obj.arch.data_reloc_size(right_reloc.relocation.flags);
let range = right_reloc.relocation.address as usize
..right_reloc.relocation.address as usize + len;
right_reloc_diffs.push(DataRelocationDiff {
reloc: right_reloc.relocation.clone(),
kind: diff_kind,
range,
});
}
}
let (mut left_section_diff, mut right_section_diff) = diff_generic_section(
left_obj,
right_obj,
left_diff,
right_diff,
left_section_idx,
right_section_idx,
)?;
let all_left_relocs_match = left_reloc_diffs.iter().all(|d| d.kind == DataDiffKind::None);
left_section_diff.data_diff = left_data_diff;
right_section_diff.data_diff = right_data_diff;
left_section_diff.reloc_diff = left_reloc_diffs;
right_section_diff.reloc_diff = right_reloc_diffs;
if all_left_relocs_match { if all_left_relocs_match {
// Use the highest match percent between two options: // Use the highest match percent between two options:
// - Left symbols matching right symbols by name // - Left symbols matching right symbols by name
@ -328,41 +295,69 @@ pub fn diff_data_section(
// We only do this when all relocations on the left side match. // We only do this when all relocations on the left side match.
if left_section_diff.match_percent.unwrap_or(-1.0) < match_percent { if left_section_diff.match_percent.unwrap_or(-1.0) < match_percent {
left_section_diff.match_percent = Some(match_percent); left_section_diff.match_percent = Some(match_percent);
right_section_diff.match_percent = Some(match_percent);
} }
} }
Ok((left_section_diff, right_section_diff)) Ok((left_section_diff, right_section_diff))
} }
pub fn diff_data_symbol( pub fn diff_data_symbol(
left_obj: &ObjInfo, left_obj: &Object,
right_obj: &ObjInfo, right_obj: &Object,
left_symbol_ref: SymbolRef, left_symbol_idx: usize,
right_symbol_ref: SymbolRef, right_symbol_idx: usize,
) -> Result<(ObjSymbolDiff, ObjSymbolDiff)> { ) -> Result<(SymbolDiff, SymbolDiff)> {
let (left_section, left_symbol) = left_obj.section_symbol(left_symbol_ref); let left_symbol = &left_obj.symbols[left_symbol_idx];
let (right_section, right_symbol) = right_obj.section_symbol(right_symbol_ref); let right_symbol = &right_obj.symbols[right_symbol_idx];
let left_section = left_section.ok_or_else(|| anyhow!("Data symbol section not found"))?; let left_section_idx =
let right_section = right_section.ok_or_else(|| anyhow!("Data symbol section not found"))?; left_symbol.section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
let right_section_idx =
right_symbol.section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
let left_range = left_symbol.section_address as usize let left_section = &left_obj.sections[left_section_idx];
..(left_symbol.section_address + left_symbol.size) as usize; let right_section = &right_obj.sections[right_section_idx];
let right_range = right_symbol.section_address as usize
..(right_symbol.section_address + right_symbol.size) as usize; let left_start = left_symbol
.address
.checked_sub(left_section.address)
.ok_or_else(|| anyhow!("Symbol address out of section bounds"))?;
let right_start = right_symbol
.address
.checked_sub(right_section.address)
.ok_or_else(|| anyhow!("Symbol address out of section bounds"))?;
let left_end = left_start + left_symbol.size;
if left_end > left_section.size {
return Err(anyhow!(
"Symbol {} size out of section bounds ({} > {})",
left_symbol.name,
left_end,
left_section.size
));
}
let right_end = right_start + right_symbol.size;
if right_end > right_section.size {
return Err(anyhow!(
"Symbol {} size out of section bounds ({} > {})",
right_symbol.name,
right_end,
right_section.size
));
}
let left_range = left_start as usize..left_end as usize;
let right_range = right_start as usize..right_end as usize;
let left_data = &left_section.data[left_range.clone()]; let left_data = &left_section.data[left_range.clone()];
let right_data = &right_section.data[right_range.clone()]; let right_data = &right_section.data[right_range.clone()];
let reloc_diffs = diff_data_relocs_for_range( let reloc_diffs = diff_data_relocs_for_range(
left_obj, left_obj,
right_obj, right_obj,
left_section, left_section_idx,
right_section, right_section_idx,
left_range, left_range,
right_range, right_range,
); );
let ops = capture_diff_slices_deadline(Algorithm::Patience, left_data, right_data, None); let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data);
let bytes_match_ratio = get_diff_ratio(&ops, left_data.len(), right_data.len()); let bytes_match_ratio = get_diff_ratio(&ops, left_data.len(), right_data.len());
let mut match_ratio = bytes_match_ratio; let mut match_ratio = bytes_match_ratio;
@ -372,11 +367,13 @@ pub fn diff_data_symbol(
for (diff_kind, left_reloc, right_reloc) in reloc_diffs { for (diff_kind, left_reloc, right_reloc) in reloc_diffs {
let reloc_diff_len = match (left_reloc, right_reloc) { let reloc_diff_len = match (left_reloc, right_reloc) {
(None, None) => unreachable!(), (None, None) => unreachable!(),
(None, Some(right_reloc)) => right_obj.arch.get_reloc_byte_size(right_reloc.flags), (None, Some(right_reloc)) => {
(Some(left_reloc), _) => left_obj.arch.get_reloc_byte_size(left_reloc.flags), right_obj.arch.data_reloc_size(right_reloc.relocation.flags)
}
(Some(left_reloc), _) => left_obj.arch.data_reloc_size(left_reloc.relocation.flags),
}; };
total_reloc_bytes += reloc_diff_len; total_reloc_bytes += reloc_diff_len;
if diff_kind == ObjDataDiffKind::None { if diff_kind == DataDiffKind::None {
matching_reloc_bytes += reloc_diff_len; matching_reloc_bytes += reloc_diff_len;
} }
} }
@ -396,17 +393,17 @@ pub fn diff_data_symbol(
let match_percent = match_ratio * 100.0; let match_percent = match_ratio * 100.0;
Ok(( Ok((
ObjSymbolDiff { SymbolDiff {
symbol_ref: left_symbol_ref, target_symbol: Some(right_symbol_idx),
target_symbol: Some(right_symbol_ref),
instructions: vec![],
match_percent: Some(match_percent), match_percent: Some(match_percent),
diff_score: None,
instruction_rows: vec![],
}, },
ObjSymbolDiff { SymbolDiff {
symbol_ref: right_symbol_ref, target_symbol: Some(left_symbol_idx),
target_symbol: Some(left_symbol_ref),
instructions: vec![],
match_percent: Some(match_percent), match_percent: Some(match_percent),
diff_score: None,
instruction_rows: vec![],
}, },
)) ))
} }
@ -414,49 +411,85 @@ pub fn diff_data_symbol(
/// Compares a section of two object files. /// Compares a section of two object files.
/// This essentially adds up the match percentage of each symbol in the section. /// This essentially adds up the match percentage of each symbol in the section.
pub fn diff_generic_section( pub fn diff_generic_section(
left: &ObjSection, left_obj: &Object,
_right: &ObjSection, _right_obj: &Object,
left_diff: &ObjSectionDiff, left_diff: &ObjectDiff,
_right_diff: &ObjSectionDiff, _right_diff: &ObjectDiff,
) -> Result<(ObjSectionDiff, ObjSectionDiff)> { left_section_idx: usize,
let match_percent = if left_diff.symbols.iter().all(|d| d.match_percent == Some(100.0)) { _right_section_idx: usize,
) -> Result<(SectionDiff, SectionDiff)> {
let match_percent = if symbols_matching_section(&left_obj.symbols, left_section_idx)
.map(|(i, _)| &left_diff.symbols[i])
.all(|d| d.match_percent == Some(100.0))
{
100.0 // Avoid fp precision issues 100.0 // Avoid fp precision issues
} else { } else {
left.symbols let (matched, total) = symbols_matching_section(&left_obj.symbols, left_section_idx)
.iter() .map(|(i, s)| (s, &left_diff.symbols[i]))
.zip(left_diff.symbols.iter()) .fold((0.0, 0.0), |(matched, total), (s, d)| {
.map(|(s, d)| d.match_percent.unwrap_or(0.0) * s.size as f32) (matched + d.match_percent.unwrap_or(0.0) * s.size as f32, total + s.size as f32)
.sum::<f32>() });
/ left.size as f32 if total == 0.0 { 100.0 } else { matched / total }
}; };
Ok(( Ok((
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) }, SectionDiff { match_percent: Some(match_percent), data_diff: vec![], reloc_diff: vec![] },
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) }, SectionDiff { match_percent: None, data_diff: vec![], reloc_diff: vec![] },
)) ))
} }
pub fn no_diff_bss_section() -> Result<SectionDiff> {
Ok(SectionDiff { match_percent: Some(0.0), data_diff: vec![], reloc_diff: vec![] })
}
/// Compare the addresses and sizes of each symbol in the BSS sections. /// Compare the addresses and sizes of each symbol in the BSS sections.
pub fn diff_bss_section( pub fn diff_bss_section(
left: &ObjSection, left_obj: &Object,
right: &ObjSection, right_obj: &Object,
left_diff: &ObjSectionDiff, left_diff: &ObjectDiff,
right_diff: &ObjSectionDiff, right_diff: &ObjectDiff,
) -> Result<(ObjSectionDiff, ObjSectionDiff)> { left_section_idx: usize,
let left_sizes = left.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>(); right_section_idx: usize,
let right_sizes = right.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>(); ) -> Result<(SectionDiff, SectionDiff)> {
let ops = capture_diff_slices_deadline(Algorithm::Patience, &left_sizes, &right_sizes, None); let left_section = &left_obj.sections[left_section_idx];
let left_sizes = symbols_matching_section(&left_obj.symbols, left_section_idx)
.filter_map(|(_, s)| s.address.checked_sub(left_section.address).map(|a| (a, s.size)))
.collect::<Vec<_>>();
let right_section = &right_obj.sections[right_section_idx];
let right_sizes = symbols_matching_section(&right_obj.symbols, right_section_idx)
.filter_map(|(_, s)| s.address.checked_sub(right_section.address).map(|a| (a, s.size)))
.collect::<Vec<_>>();
let ops = capture_diff_slices(Algorithm::Patience, &left_sizes, &right_sizes);
let mut match_percent = get_diff_ratio(&ops, left_sizes.len(), right_sizes.len()) * 100.0; let mut match_percent = get_diff_ratio(&ops, left_sizes.len(), right_sizes.len()) * 100.0;
// Use the highest match percent between two options: // Use the highest match percent between two options:
// - Left symbols matching right symbols by name // - Left symbols matching right symbols by name
// - Diff of the addresses and sizes of each symbol // - Diff of the addresses and sizes of each symbol
let (generic_diff, _) = diff_generic_section(left, right, left_diff, right_diff)?; let (generic_diff, _) = diff_generic_section(
left_obj,
right_obj,
left_diff,
right_diff,
left_section_idx,
right_section_idx,
)?;
if generic_diff.match_percent.unwrap_or(-1.0) > match_percent { if generic_diff.match_percent.unwrap_or(-1.0) > match_percent {
match_percent = generic_diff.match_percent.unwrap(); match_percent = generic_diff.match_percent.unwrap();
} }
Ok(( Ok((
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) }, SectionDiff { match_percent: Some(match_percent), data_diff: vec![], reloc_diff: vec![] },
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) }, SectionDiff { match_percent: None, data_diff: vec![], reloc_diff: vec![] },
)) ))
} }
fn symbols_matching_section(
symbols: &[Symbol],
section_idx: usize,
) -> impl Iterator<Item = (usize, &Symbol)> + '_ {
symbols.iter().enumerate().filter(move |(_, s)| {
s.section == Some(section_idx)
&& s.kind != SymbolKind::Section
&& s.size > 0
&& !s.flags.contains(SymbolFlag::Ignored)
})
}

View File

@ -1,16 +1,29 @@
use std::cmp::Ordering; use alloc::{
borrow::Cow,
collections::BTreeSet,
format,
string::{String, ToString},
vec::Vec,
};
use core::cmp::Ordering;
use anyhow::Result;
use itertools::Itertools;
use regex::Regex;
use crate::{ use crate::{
diff::{ObjInsArgDiff, ObjInsDiff}, diff::{DiffObjConfig, InstructionDiffKind, InstructionDiffRow, ObjectDiff, SymbolDiff},
obj::{ObjInsArg, ObjInsArgValue, ObjReloc, ObjSymbol}, obj::{
FlowAnalysisValue, InstructionArg, InstructionArgValue, Object, ParsedInstruction,
ResolvedInstructionRef, ResolvedRelocation, SectionFlag, SectionKind, Symbol, SymbolFlag,
SymbolKind,
},
}; };
#[derive(Debug, Copy, Clone)] #[derive(Debug, Clone)]
pub enum DiffText<'a> { pub enum DiffText<'a> {
/// Basic text /// Basic text
Basic(&'a str), Basic(&'a str),
/// Colored text
BasicColor(&'a str, usize),
/// Line number /// Line number
Line(u32), Line(u32),
/// Instruction address /// Instruction address
@ -18,91 +31,294 @@ pub enum DiffText<'a> {
/// Instruction mnemonic /// Instruction mnemonic
Opcode(&'a str, u16), Opcode(&'a str, u16),
/// Instruction argument /// Instruction argument
Argument(&'a ObjInsArgValue, Option<&'a ObjInsArgDiff>), Argument(InstructionArgValue<'a>),
/// Branch destination /// Branch destination
BranchDest(u64, Option<&'a ObjInsArgDiff>), BranchDest(u64),
/// Symbol name /// Symbol name
Symbol(&'a ObjSymbol, Option<&'a ObjInsArgDiff>), Symbol(&'a Symbol),
/// Relocation addend
Addend(i64),
/// Number of spaces /// Number of spaces
Spacing(usize), Spacing(u8),
/// End of line /// End of line
Eol, Eol,
} }
#[derive(Debug, Default, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
pub enum DiffTextColor {
#[default]
Normal, // Grey
Dim, // Dark grey
Bright, // White
DataFlow, // Light blue
Replace, // Blue
Delete, // Red
Insert, // Green
Rotating(u8),
}
#[derive(Debug, Clone)]
pub struct DiffTextSegment<'a> {
pub text: DiffText<'a>,
pub color: DiffTextColor,
pub pad_to: u8,
}
impl<'a> DiffTextSegment<'a> {
#[inline(always)]
pub fn basic(text: &'a str, color: DiffTextColor) -> Self {
Self { text: DiffText::Basic(text), color, pad_to: 0 }
}
#[inline(always)]
pub fn spacing(spaces: u8) -> Self {
Self { text: DiffText::Spacing(spaces), color: DiffTextColor::Normal, pad_to: 0 }
}
}
const EOL_SEGMENT: DiffTextSegment<'static> =
DiffTextSegment { text: DiffText::Eol, color: DiffTextColor::Normal, pad_to: 0 };
#[derive(Debug, Default, Clone)]
pub enum HighlightKind { pub enum HighlightKind {
#[default] #[default]
None, None,
Opcode(u16), Opcode(u16),
Arg(ObjInsArgValue), Argument(InstructionArgValue<'static>),
Symbol(String), Symbol(String),
Address(u64), Address(u64),
} }
pub fn display_diff<E>( #[derive(Debug, Clone, PartialEq, Eq)]
ins_diff: &ObjInsDiff, pub enum InstructionPart<'a> {
base_addr: u64, Basic(Cow<'a, str>),
mut cb: impl FnMut(DiffText) -> Result<(), E>, Opcode(Cow<'a, str>, u16),
) -> Result<(), E> { Arg(InstructionArg<'a>),
let Some(ins) = &ins_diff.ins else { Separator,
cb(DiffText::Eol)?; }
impl<'a> InstructionPart<'a> {
#[inline(always)]
pub fn basic<T>(s: T) -> Self
where T: Into<Cow<'a, str>> {
InstructionPart::Basic(s.into())
}
#[inline(always)]
pub fn opcode<T>(s: T, o: u16) -> Self
where T: Into<Cow<'a, str>> {
InstructionPart::Opcode(s.into(), o)
}
#[inline(always)]
pub fn opaque<T>(s: T) -> Self
where T: Into<Cow<'a, str>> {
InstructionPart::Arg(InstructionArg::Value(InstructionArgValue::Opaque(s.into())))
}
#[inline(always)]
pub fn signed<T>(v: T) -> InstructionPart<'static>
where T: Into<i64> {
InstructionPart::Arg(InstructionArg::Value(InstructionArgValue::Signed(v.into())))
}
#[inline(always)]
pub fn unsigned<T>(v: T) -> InstructionPart<'static>
where T: Into<u64> {
InstructionPart::Arg(InstructionArg::Value(InstructionArgValue::Unsigned(v.into())))
}
#[inline(always)]
pub fn branch_dest<T>(v: T) -> InstructionPart<'static>
where T: Into<u64> {
InstructionPart::Arg(InstructionArg::BranchDest(v.into()))
}
#[inline(always)]
pub fn reloc() -> InstructionPart<'static> { InstructionPart::Arg(InstructionArg::Reloc) }
#[inline(always)]
pub fn separator() -> InstructionPart<'static> { InstructionPart::Separator }
pub fn into_static(self) -> InstructionPart<'static> {
match self {
InstructionPart::Basic(s) => InstructionPart::Basic(Cow::Owned(s.into_owned())),
InstructionPart::Opcode(s, o) => InstructionPart::Opcode(Cow::Owned(s.into_owned()), o),
InstructionPart::Arg(a) => InstructionPart::Arg(a.into_static()),
InstructionPart::Separator => InstructionPart::Separator,
}
}
}
pub fn display_row(
obj: &Object,
symbol_index: usize,
ins_row: &InstructionDiffRow,
diff_config: &DiffObjConfig,
mut cb: impl FnMut(DiffTextSegment) -> Result<()>,
) -> Result<()> {
let Some(ins_ref) = ins_row.ins_ref else {
cb(EOL_SEGMENT)?;
return Ok(()); return Ok(());
}; };
if let Some(line) = ins.line { let Some(resolved) = obj.resolve_instruction_ref(symbol_index, ins_ref) else {
cb(DiffText::Line(line))?; cb(DiffTextSegment::basic("<invalid>", DiffTextColor::Delete))?;
cb(EOL_SEGMENT)?;
return Ok(());
};
let base_color = match ins_row.kind {
InstructionDiffKind::Replace => DiffTextColor::Replace,
InstructionDiffKind::Delete => DiffTextColor::Delete,
InstructionDiffKind::Insert => DiffTextColor::Insert,
_ => DiffTextColor::Normal,
};
if let Some(line) = resolved.section.line_info.range(..=ins_ref.address).last().map(|(_, &b)| b)
{
cb(DiffTextSegment { text: DiffText::Line(line), color: DiffTextColor::Dim, pad_to: 5 })?;
} }
cb(DiffText::Address(ins.address - base_addr))?; cb(DiffTextSegment {
if let Some(branch) = &ins_diff.branch_from { text: DiffText::Address(ins_ref.address.saturating_sub(resolved.symbol.address)),
cb(DiffText::BasicColor(" ~> ", branch.branch_idx))?; color: base_color,
pad_to: 5,
})?;
if let Some(branch) = &ins_row.branch_from {
cb(DiffTextSegment::basic(" ~> ", DiffTextColor::Rotating(branch.branch_idx as u8)))?;
} else { } else {
cb(DiffText::Spacing(4))?; cb(DiffTextSegment::spacing(4))?;
} }
cb(DiffText::Opcode(&ins.mnemonic, ins.op))?; let mut arg_idx = 0;
let mut arg_diff_idx = 0; // non-PlainText index let mut displayed_relocation = false;
for (i, arg) in ins.args.iter().enumerate() { let analysis_result = if diff_config.show_data_flow {
if i == 0 { obj.get_flow_analysis_result(resolved.symbol)
cb(DiffText::Spacing(1))?; } else {
None
};
obj.arch.display_instruction(resolved, diff_config, &mut |part| match part {
InstructionPart::Basic(text) => {
if text.chars().all(|c| c == ' ') {
cb(DiffTextSegment::spacing(text.len() as u8))
} else {
cb(DiffTextSegment::basic(&text, base_color))
}
} }
let diff = ins_diff.arg_diff.get(arg_diff_idx).and_then(|o| o.as_ref()); InstructionPart::Opcode(mnemonic, opcode) => cb(DiffTextSegment {
match arg { text: DiffText::Opcode(mnemonic.as_ref(), opcode),
ObjInsArg::PlainText(s) => { color: match ins_row.kind {
cb(DiffText::Basic(s))?; InstructionDiffKind::OpMismatch => DiffTextColor::Replace,
_ => base_color,
},
pad_to: 10,
}),
InstructionPart::Arg(arg) => {
let diff_index = ins_row.arg_diff.get(arg_idx).copied().unwrap_or_default();
arg_idx += 1;
if arg == InstructionArg::Reloc {
displayed_relocation = true;
} }
ObjInsArg::Arg(v) => { let data_flow_value =
cb(DiffText::Argument(v, diff))?; analysis_result.and_then(|result|
arg_diff_idx += 1; result.get_argument_value_at_address(
} ins_ref.address, (arg_idx - 1) as u8));
ObjInsArg::Reloc => { match (arg, data_flow_value, resolved.ins_ref.branch_dest) {
display_reloc_name(ins.reloc.as_ref().unwrap(), &mut cb, diff)?; // If we have a flow analysis result, always use that over anything else.
arg_diff_idx += 1; (InstructionArg::Value(_) | InstructionArg::Reloc, Some(FlowAnalysisValue::Text(text)), _) => {
} cb(DiffTextSegment {
ObjInsArg::BranchDest(dest) => { text: DiffText::Argument(InstructionArgValue::Opaque(Cow::Borrowed(text))),
if let Some(dest) = dest.checked_sub(base_addr) { color: DiffTextColor::DataFlow,
cb(DiffText::BranchDest(dest, diff))?; pad_to: 0,
} else { })
cb(DiffText::Basic("<unknown>"))?; },
(InstructionArg::Value(value), None, _) => {
let color = diff_index
.get()
.map_or(base_color, |i| DiffTextColor::Rotating(i as u8));
cb(DiffTextSegment {
text: DiffText::Argument(value),
color,
pad_to: 0,
})
},
(InstructionArg::Reloc, _, None) => {
let resolved = resolved.relocation.unwrap();
let color = diff_index
.get()
.map_or(DiffTextColor::Bright, |i| DiffTextColor::Rotating(i as u8));
cb(DiffTextSegment {
text: DiffText::Symbol(resolved.symbol),
color,
pad_to: 0,
})?;
if resolved.relocation.addend != 0 {
cb(DiffTextSegment {
text: DiffText::Addend(resolved.relocation.addend),
color,
pad_to: 0,
})?;
}
Ok(())
}
(InstructionArg::BranchDest(dest), _, _) |
// If the relocation was resolved to a branch destination, emit that instead.
(InstructionArg::Reloc, _, Some(dest)) => {
if let Some(addr) = dest.checked_sub(resolved.symbol.address) {
cb(DiffTextSegment {
text: DiffText::BranchDest(addr),
color: diff_index
.get()
.map_or(base_color, |i| DiffTextColor::Rotating(i as u8)),
pad_to: 0,
})
} else {
cb(DiffTextSegment {
text: DiffText::Argument(InstructionArgValue::Opaque(Cow::Borrowed(
"<invalid>",
))),
color: diff_index
.get()
.map_or(base_color, |i| DiffTextColor::Rotating(i as u8)),
pad_to: 0,
})
}
} }
arg_diff_idx += 1;
} }
} }
InstructionPart::Separator => {
cb(DiffTextSegment::basic(diff_config.separator(), base_color))
}
})?;
// Fallback for relocation that wasn't displayed
if resolved.relocation.is_some() && !displayed_relocation {
cb(DiffTextSegment::basic(" <", base_color))?;
let resolved = resolved.relocation.unwrap();
let diff_index = ins_row.arg_diff.get(arg_idx).copied().unwrap_or_default();
let color =
diff_index.get().map_or(DiffTextColor::Bright, |i| DiffTextColor::Rotating(i as u8));
cb(DiffTextSegment { text: DiffText::Symbol(resolved.symbol), color, pad_to: 0 })?;
if resolved.relocation.addend != 0 {
cb(DiffTextSegment {
text: DiffText::Addend(resolved.relocation.addend),
color,
pad_to: 0,
})?;
}
cb(DiffTextSegment::basic(">", base_color))?;
} }
if let Some(branch) = &ins_diff.branch_to { if let Some(branch) = &ins_row.branch_to {
cb(DiffText::BasicColor(" ~>", branch.branch_idx))?; cb(DiffTextSegment::basic(" ~>", DiffTextColor::Rotating(branch.branch_idx as u8)))?;
} }
cb(DiffText::Eol)?; cb(EOL_SEGMENT)?;
Ok(()) Ok(())
} }
fn display_reloc_name<E>( impl PartialEq<HighlightKind> for HighlightKind {
reloc: &ObjReloc, fn eq(&self, other: &HighlightKind) -> bool {
mut cb: impl FnMut(DiffText) -> Result<(), E>, match (self, other) {
diff: Option<&ObjInsArgDiff>, (HighlightKind::Opcode(a), HighlightKind::Opcode(b)) => a == b,
) -> Result<(), E> { (HighlightKind::Argument(a), HighlightKind::Argument(b)) => a.loose_eq(b),
cb(DiffText::Symbol(&reloc.target, diff))?; (HighlightKind::Symbol(a), HighlightKind::Symbol(b)) => a == b,
match reloc.addend.cmp(&0i64) { (HighlightKind::Address(a), HighlightKind::Address(b)) => a == b,
Ordering::Greater => cb(DiffText::Basic(&format!("+{:#x}", reloc.addend))), _ => false,
Ordering::Less => cb(DiffText::Basic(&format!("-{:#x}", -reloc.addend))), }
_ => Ok(()),
} }
} }
@ -110,11 +326,9 @@ impl PartialEq<DiffText<'_>> for HighlightKind {
fn eq(&self, other: &DiffText) -> bool { fn eq(&self, other: &DiffText) -> bool {
match (self, other) { match (self, other) {
(HighlightKind::Opcode(a), DiffText::Opcode(_, b)) => a == b, (HighlightKind::Opcode(a), DiffText::Opcode(_, b)) => a == b,
(HighlightKind::Arg(a), DiffText::Argument(b, _)) => a.loose_eq(b), (HighlightKind::Argument(a), DiffText::Argument(b)) => a.loose_eq(b),
(HighlightKind::Symbol(a), DiffText::Symbol(b, _)) => a == &b.name, (HighlightKind::Symbol(a), DiffText::Symbol(b)) => a == &b.name,
(HighlightKind::Address(a), DiffText::Address(b) | DiffText::BranchDest(b, _)) => { (HighlightKind::Address(a), DiffText::Address(b) | DiffText::BranchDest(b)) => a == b,
a == b
}
_ => false, _ => false,
} }
} }
@ -124,14 +338,479 @@ impl PartialEq<HighlightKind> for DiffText<'_> {
fn eq(&self, other: &HighlightKind) -> bool { other.eq(self) } fn eq(&self, other: &HighlightKind) -> bool { other.eq(self) }
} }
impl From<DiffText<'_>> for HighlightKind { impl From<&DiffText<'_>> for HighlightKind {
fn from(value: DiffText<'_>) -> Self { fn from(value: &DiffText<'_>) -> Self {
match value { match value {
DiffText::Opcode(_, op) => HighlightKind::Opcode(op), DiffText::Opcode(_, op) => HighlightKind::Opcode(*op),
DiffText::Argument(arg, _) => HighlightKind::Arg(arg.clone()), DiffText::Argument(arg) => HighlightKind::Argument(arg.to_static()),
DiffText::Symbol(sym, _) => HighlightKind::Symbol(sym.name.to_string()), DiffText::Symbol(sym) => HighlightKind::Symbol(sym.name.to_string()),
DiffText::Address(addr) | DiffText::BranchDest(addr, _) => HighlightKind::Address(addr), DiffText::Address(addr) | DiffText::BranchDest(addr) => HighlightKind::Address(*addr),
_ => HighlightKind::None, _ => HighlightKind::None,
} }
} }
} }
pub enum ContextItem {
Copy { value: String, label: Option<String> },
Navigate { label: String, symbol_index: usize, kind: SymbolNavigationKind },
Separator,
}
#[derive(Debug, Clone, Default, Eq, PartialEq)]
pub enum SymbolNavigationKind {
#[default]
Normal,
Extab,
}
#[derive(Debug, Clone, Default, Eq, PartialEq)]
pub enum HoverItemColor {
#[default]
Normal, // Gray
Emphasized, // White
Special, // Blue
Delete, // Red
Insert, // Green
}
pub enum HoverItem {
Text { label: String, value: String, color: HoverItemColor },
Separator,
}
pub fn symbol_context(obj: &Object, symbol_index: usize) -> Vec<ContextItem> {
let symbol = &obj.symbols[symbol_index];
let mut out = Vec::new();
out.push(ContextItem::Copy { value: symbol.name.clone(), label: None });
if let Some(name) = &symbol.demangled_name {
out.push(ContextItem::Copy { value: name.clone(), label: None });
}
if symbol.section.is_some()
&& let Some(address) = symbol.virtual_address
{
out.push(ContextItem::Copy {
value: format!("{address:x}"),
label: Some("virtual address".to_string()),
});
}
out.append(&mut obj.arch.symbol_context(obj, symbol_index));
out
}
pub fn symbol_hover(
obj: &Object,
symbol_index: usize,
addend: i64,
override_color: Option<HoverItemColor>,
) -> Vec<HoverItem> {
let symbol = &obj.symbols[symbol_index];
let addend_str = match addend.cmp(&0i64) {
Ordering::Greater => format!("+{addend:x}"),
Ordering::Less => format!("-{:x}", -addend),
_ => String::new(),
};
let mut out = Vec::new();
out.push(HoverItem::Text {
label: "Name".into(),
value: format!("{}{}", symbol.name, addend_str),
color: override_color.clone().unwrap_or_default(),
});
if let Some(demangled_name) = &symbol.demangled_name {
out.push(HoverItem::Text {
label: "Demangled".into(),
value: demangled_name.into(),
color: override_color.clone().unwrap_or_default(),
});
}
if let Some(section) = symbol.section {
out.push(HoverItem::Text {
label: "Section".into(),
value: obj.sections[section].name.clone(),
color: override_color.clone().unwrap_or_default(),
});
out.push(HoverItem::Text {
label: "Address".into(),
value: format!("{:x}{}", symbol.address, addend_str),
color: override_color.clone().unwrap_or_default(),
});
if symbol.flags.contains(SymbolFlag::SizeInferred) {
out.push(HoverItem::Text {
label: "Size".into(),
value: format!("{:x} (inferred)", symbol.size),
color: override_color.clone().unwrap_or_default(),
});
} else {
out.push(HoverItem::Text {
label: "Size".into(),
value: format!("{:x}", symbol.size),
color: override_color.clone().unwrap_or_default(),
});
}
if let Some(align) = symbol.align {
out.push(HoverItem::Text {
label: "Alignment".into(),
value: align.get().to_string(),
color: override_color.clone().unwrap_or_default(),
});
}
if let Some(address) = symbol.virtual_address {
out.push(HoverItem::Text {
label: "Virtual address".into(),
value: format!("{address:x}"),
color: override_color.clone().unwrap_or(HoverItemColor::Special),
});
}
} else {
out.push(HoverItem::Text {
label: Default::default(),
value: "Extern".into(),
color: HoverItemColor::Emphasized,
});
}
out.append(&mut obj.arch.symbol_hover(obj, symbol_index));
out
}
pub fn relocation_context(
obj: &Object,
reloc: ResolvedRelocation,
ins: Option<ResolvedInstructionRef>,
) -> Vec<ContextItem> {
let mut out = Vec::new();
out.append(&mut symbol_context(obj, reloc.relocation.target_symbol));
if let Some(ins) = ins {
let literals = display_ins_data_literals(obj, ins);
if !literals.is_empty() {
out.push(ContextItem::Separator);
for (literal, label_override) in literals {
out.push(ContextItem::Copy { value: literal, label: label_override });
}
}
}
out
}
pub fn relocation_hover(
obj: &Object,
reloc: ResolvedRelocation,
override_color: Option<HoverItemColor>,
) -> Vec<HoverItem> {
let mut out = Vec::new();
if let Some(name) = obj.arch.reloc_name(reloc.relocation.flags) {
out.push(HoverItem::Text {
label: "Relocation".into(),
value: name.to_string(),
color: override_color.clone().unwrap_or_default(),
});
} else {
out.push(HoverItem::Text {
label: "Relocation".into(),
value: format!("<{:?}>", reloc.relocation.flags),
color: override_color.clone().unwrap_or_default(),
});
}
out.append(&mut symbol_hover(
obj,
reloc.relocation.target_symbol,
reloc.relocation.addend,
override_color,
));
out
}
pub fn instruction_context(
obj: &Object,
resolved: ResolvedInstructionRef,
ins: &ParsedInstruction,
) -> Vec<ContextItem> {
let mut out = Vec::new();
let mut hex_string = String::new();
for byte in resolved.code {
hex_string.push_str(&format!("{byte:02x}"));
}
out.push(ContextItem::Copy { value: hex_string, label: Some("instruction bytes".to_string()) });
out.append(&mut obj.arch.instruction_context(obj, resolved));
if let Some(virtual_address) = resolved.symbol.virtual_address {
let offset = resolved.ins_ref.address - resolved.symbol.address;
out.push(ContextItem::Copy {
value: format!("{:x}", virtual_address + offset),
label: Some("virtual address".to_string()),
});
}
for arg in &ins.args {
if let InstructionArg::Value(arg) = arg {
out.push(ContextItem::Copy { value: arg.to_string(), label: None });
match arg {
InstructionArgValue::Signed(v) => {
out.push(ContextItem::Copy { value: v.to_string(), label: None });
}
InstructionArgValue::Unsigned(v) => {
out.push(ContextItem::Copy { value: v.to_string(), label: None });
}
_ => {}
}
}
}
if let Some(reloc) = resolved.relocation {
out.push(ContextItem::Separator);
out.append(&mut relocation_context(obj, reloc, Some(resolved)));
}
out
}
pub fn instruction_hover(
obj: &Object,
resolved: ResolvedInstructionRef,
ins: &ParsedInstruction,
) -> Vec<HoverItem> {
let mut out = Vec::new();
out.push(HoverItem::Text {
label: Default::default(),
value: format!("{:02x?}", resolved.code),
color: HoverItemColor::Normal,
});
out.append(&mut obj.arch.instruction_hover(obj, resolved));
if let Some(virtual_address) = resolved.symbol.virtual_address {
let offset = resolved.ins_ref.address - resolved.symbol.address;
out.push(HoverItem::Text {
label: "Virtual address".into(),
value: format!("{:x}", virtual_address + offset),
color: HoverItemColor::Special,
});
}
for arg in &ins.args {
if let InstructionArg::Value(arg) = arg {
match arg {
InstructionArgValue::Signed(v) => {
out.push(HoverItem::Text {
label: Default::default(),
value: format!("{arg} == {v}"),
color: HoverItemColor::Normal,
});
}
InstructionArgValue::Unsigned(v) => {
out.push(HoverItem::Text {
label: Default::default(),
value: format!("{arg} == {v}"),
color: HoverItemColor::Normal,
});
}
_ => {}
}
}
}
if let Some(reloc) = resolved.relocation {
out.push(HoverItem::Separator);
out.append(&mut relocation_hover(obj, reloc, None));
let bytes = obj.symbol_data(reloc.relocation.target_symbol).unwrap_or(&[]);
if let Some(ty) = obj.arch.guess_data_type(resolved, bytes) {
let literals = display_ins_data_literals(obj, resolved);
if !literals.is_empty() {
out.push(HoverItem::Separator);
for (literal, label_override) in literals {
out.push(HoverItem::Text {
label: label_override.unwrap_or_else(|| ty.to_string()),
value: literal,
color: HoverItemColor::Normal,
});
}
}
}
}
out
}
#[derive(Debug, Copy, Clone)]
pub enum SymbolFilter<'a> {
None,
Search(&'a Regex),
Mapping(usize, Option<&'a Regex>),
}
fn symbol_matches_filter(
symbol: &Symbol,
diff: &SymbolDiff,
filter: SymbolFilter<'_>,
show_hidden_symbols: bool,
) -> bool {
// Ignore absolute symbols
if symbol.section.is_none() && !symbol.flags.contains(SymbolFlag::Common) {
return false;
}
if !show_hidden_symbols
&& (symbol.size == 0
|| symbol.flags.contains(SymbolFlag::Hidden)
|| symbol.flags.contains(SymbolFlag::Ignored))
{
return false;
}
match filter {
SymbolFilter::None => true,
SymbolFilter::Search(regex) => {
regex.is_match(&symbol.name)
|| symbol.demangled_name.as_deref().is_some_and(|s| regex.is_match(s))
}
SymbolFilter::Mapping(symbol_ref, regex) => {
diff.target_symbol == Some(symbol_ref)
&& regex.is_none_or(|r| {
r.is_match(&symbol.name)
|| symbol.demangled_name.as_deref().is_some_and(|s| r.is_match(s))
})
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct SectionDisplaySymbol {
pub symbol: usize,
pub is_mapping_symbol: bool,
}
#[derive(Debug, Clone)]
pub struct SectionDisplay {
pub id: String,
pub name: String,
pub size: u64,
pub match_percent: Option<f32>,
pub symbols: Vec<SectionDisplaySymbol>,
}
pub fn display_sections(
obj: &Object,
diff: &ObjectDiff,
filter: SymbolFilter<'_>,
show_hidden_symbols: bool,
show_mapped_symbols: bool,
reverse_fn_order: bool,
) -> Vec<SectionDisplay> {
let mut mapping = BTreeSet::new();
let is_mapping_symbol = if let SymbolFilter::Mapping(_, _) = filter {
for mapping_diff in &diff.mapping_symbols {
let symbol = &obj.symbols[mapping_diff.symbol_index];
if !symbol_matches_filter(
symbol,
&mapping_diff.symbol_diff,
filter,
show_hidden_symbols,
) {
continue;
}
if !show_mapped_symbols {
let symbol_diff = &diff.symbols[mapping_diff.symbol_index];
if symbol_diff.target_symbol.is_some() {
continue;
}
}
mapping.insert((symbol.section, mapping_diff.symbol_index));
}
true
} else {
for (symbol_idx, (symbol, symbol_diff)) in obj.symbols.iter().zip(&diff.symbols).enumerate()
{
if !symbol_matches_filter(symbol, symbol_diff, filter, show_hidden_symbols) {
continue;
}
mapping.insert((symbol.section, symbol_idx));
}
false
};
let num_sections = mapping.iter().map(|(section_idx, _)| *section_idx).dedup().count();
let mut sections = Vec::with_capacity(num_sections);
for (section_idx, group) in &mapping.iter().chunk_by(|(section_idx, _)| *section_idx) {
let mut symbols = group
.map(|&(_, symbol)| SectionDisplaySymbol { symbol, is_mapping_symbol })
.collect::<Vec<_>>();
if let Some(section_idx) = section_idx {
let section = &obj.sections[section_idx];
if section.kind == SectionKind::Unknown {
// Skip unknown and hidden sections
continue;
}
let section_diff = &diff.sections[section_idx];
let reverse_fn_order = section.kind == SectionKind::Code && reverse_fn_order;
symbols.sort_by(|a, b| {
let a = &obj.symbols[a.symbol];
let b = &obj.symbols[b.symbol];
section_symbol_sort(a, b)
.then_with(|| {
if reverse_fn_order {
b.address.cmp(&a.address)
} else {
a.address.cmp(&b.address)
}
})
.then_with(|| a.size.cmp(&b.size))
});
sections.push(SectionDisplay {
id: section.id.clone(),
name: if section.flags.contains(SectionFlag::Combined) {
format!("{} [combined]", section.name)
} else {
section.name.clone()
},
size: section.size,
match_percent: section_diff.match_percent,
symbols,
});
} else {
// Don't sort, preserve order of absolute symbols
sections.push(SectionDisplay {
id: ".comm".to_string(),
name: ".comm".to_string(),
size: 0,
match_percent: None,
symbols,
});
}
}
sections.sort_by(|a, b| a.name.cmp(&b.name));
sections
}
fn section_symbol_sort(a: &Symbol, b: &Symbol) -> Ordering {
if a.kind == SymbolKind::Section {
if b.kind != SymbolKind::Section {
return Ordering::Less;
}
} else if b.kind == SymbolKind::Section {
return Ordering::Greater;
}
Ordering::Equal
}
pub fn display_ins_data_labels(obj: &Object, resolved: ResolvedInstructionRef) -> Vec<String> {
let Some(reloc) = resolved.relocation else {
return Vec::new();
};
if reloc.relocation.addend < 0 || reloc.relocation.addend as u64 >= reloc.symbol.size {
return Vec::new();
}
let Some(data) = obj.symbol_data(reloc.relocation.target_symbol) else {
return Vec::new();
};
let bytes = &data[reloc.relocation.addend as usize..];
obj.arch
.guess_data_type(resolved, bytes)
.map(|ty| ty.display_labels(obj.endianness, bytes))
.unwrap_or_default()
}
pub fn display_ins_data_literals(
obj: &Object,
resolved: ResolvedInstructionRef,
) -> Vec<(String, Option<String>)> {
let Some(reloc) = resolved.relocation else {
return Vec::new();
};
if reloc.relocation.addend < 0 || reloc.relocation.addend as u64 >= reloc.symbol.size {
return Vec::new();
}
let Some(data) = obj.symbol_data(reloc.relocation.target_symbol) else {
return Vec::new();
};
let bytes = &data[reloc.relocation.addend as usize..];
obj.arch
.guess_data_type(resolved, bytes)
.map(|ty| ty.display_literals(obj.endianness, bytes))
.unwrap_or_default()
}

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ use self_update::{
update::{Release, ReleaseUpdate}, update::{Release, ReleaseUpdate},
}; };
use crate::jobs::{start_job, update_status, Job, JobContext, JobResult, JobState}; use crate::jobs::{Job, JobContext, JobResult, JobState, start_job, update_status};
pub struct CheckUpdateConfig { pub struct CheckUpdateConfig {
pub build_updater: fn() -> Result<Box<dyn ReleaseUpdate>>, pub build_updater: fn() -> Result<Box<dyn ReleaseUpdate>>,

View File

@ -1,16 +1,17 @@
use std::{fs, path::PathBuf, sync::mpsc::Receiver, task::Waker}; use std::{fs, sync::mpsc::Receiver, task::Waker};
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{Context, Result, anyhow, bail};
use typed_path::{Utf8PlatformPathBuf, Utf8UnixPathBuf};
use crate::{ use crate::{
build::{run_make, BuildConfig, BuildStatus}, build::{BuildConfig, BuildStatus, run_make},
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState}, jobs::{Job, JobContext, JobResult, JobState, start_job, update_status},
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CreateScratchConfig { pub struct CreateScratchConfig {
pub build_config: BuildConfig, pub build_config: BuildConfig,
pub context_path: Option<PathBuf>, pub context_path: Option<Utf8UnixPathBuf>,
pub build_context: bool, pub build_context: bool,
// Scratch fields // Scratch fields
@ -18,7 +19,7 @@ pub struct CreateScratchConfig {
pub platform: String, pub platform: String,
pub compiler_flags: String, pub compiler_flags: String,
pub function_name: String, pub function_name: String,
pub target_obj: PathBuf, pub target_obj: Utf8PlatformPathBuf,
pub preset_id: Option<u32>, pub preset_id: Option<u32>,
} }
@ -47,26 +48,25 @@ fn run_create_scratch(
if let Some(context_path) = &config.context_path { if let Some(context_path) = &config.context_path {
if config.build_context { if config.build_context {
update_status(status, "Building context".to_string(), 0, 2, &cancel)?; update_status(status, "Building context".to_string(), 0, 2, &cancel)?;
match run_make(&config.build_config, context_path) { match run_make(&config.build_config, context_path.as_ref()) {
BuildStatus { success: true, .. } => {} BuildStatus { success: true, .. } => {}
BuildStatus { success: false, stdout, stderr, .. } => { BuildStatus { success: false, stdout, stderr, .. } => {
bail!("Failed to build context:\n{stdout}\n{stderr}") bail!("Failed to build context:\n{stdout}\n{stderr}")
} }
} }
} }
let context_path = project_dir.join(context_path); let context_path = project_dir.join(context_path.with_platform_encoding());
context = Some( context = Some(
fs::read_to_string(&context_path) fs::read_to_string(&context_path)
.map_err(|e| anyhow!("Failed to read {}: {}", context_path.display(), e))?, .map_err(|e| anyhow!("Failed to read {}: {}", context_path, e))?,
); );
} }
update_status(status, "Creating scratch".to_string(), 1, 2, &cancel)?; update_status(status, "Creating scratch".to_string(), 1, 2, &cancel)?;
let diff_flags = [format!("--disassemble={}", config.function_name)]; let diff_flags = [format!("--disassemble={}", config.function_name)];
let diff_flags = serde_json::to_string(&diff_flags)?; let diff_flags = serde_json::to_string(&diff_flags)?;
let obj_path = project_dir.join(&config.target_obj); let file = reqwest::blocking::multipart::Part::file(&config.target_obj)
let file = reqwest::blocking::multipart::Part::file(&obj_path) .with_context(|| format!("Failed to open {}", config.target_obj))?;
.with_context(|| format!("Failed to open {}", obj_path.display()))?;
let mut form = reqwest::blocking::multipart::Form::new() let mut form = reqwest::blocking::multipart::Form::new()
.text("compiler", config.compiler.clone()) .text("compiler", config.compiler.clone())
.text("platform", config.platform.clone()) .text("platform", config.platform.clone())

View File

@ -1,8 +1,8 @@
use std::{ use std::{
sync::{ sync::{
Arc, RwLock,
atomic::{AtomicUsize, Ordering}, atomic::{AtomicUsize, Ordering},
mpsc::{Receiver, Sender, TryRecvError}, mpsc::{Receiver, Sender, TryRecvError},
Arc, RwLock,
}, },
task::Waker, task::Waker,
thread::JoinHandle, thread::JoinHandle,

View File

@ -1,21 +1,22 @@
use std::{path::PathBuf, sync::mpsc::Receiver, task::Waker}; use std::{sync::mpsc::Receiver, task::Waker};
use anyhow::{anyhow, Error, Result}; use anyhow::{Error, Result, bail};
use time::OffsetDateTime; use time::OffsetDateTime;
use typed_path::Utf8PlatformPathBuf;
use crate::{ use crate::{
build::{run_make, BuildConfig, BuildStatus}, build::{BuildConfig, BuildStatus, run_make},
diff::{diff_objs, DiffObjConfig, MappingConfig, ObjDiff}, diff::{DiffObjConfig, MappingConfig, ObjectDiff, diff_objs},
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState}, jobs::{Job, JobContext, JobResult, JobState, start_job, update_status},
obj::{read, ObjInfo}, obj::{Object, read},
}; };
pub struct ObjDiffConfig { pub struct ObjDiffConfig {
pub build_config: BuildConfig, pub build_config: BuildConfig,
pub build_base: bool, pub build_base: bool,
pub build_target: bool, pub build_target: bool,
pub target_path: Option<PathBuf>, pub target_path: Option<Utf8PlatformPathBuf>,
pub base_path: Option<PathBuf>, pub base_path: Option<Utf8PlatformPathBuf>,
pub diff_obj_config: DiffObjConfig, pub diff_obj_config: DiffObjConfig,
pub mapping_config: MappingConfig, pub mapping_config: MappingConfig,
} }
@ -23,8 +24,8 @@ pub struct ObjDiffConfig {
pub struct ObjDiffResult { pub struct ObjDiffResult {
pub first_status: BuildStatus, pub first_status: BuildStatus,
pub second_status: BuildStatus, pub second_status: BuildStatus,
pub first_obj: Option<(ObjInfo, ObjDiff)>, pub first_obj: Option<(Object, ObjectDiff)>,
pub second_obj: Option<(ObjInfo, ObjDiff)>, pub second_obj: Option<(Object, ObjectDiff)>,
pub time: OffsetDateTime, pub time: OffsetDateTime,
} }
@ -42,22 +43,20 @@ fn run_build(
.as_ref() .as_ref()
.ok_or_else(|| Error::msg("Missing project dir"))?; .ok_or_else(|| Error::msg("Missing project dir"))?;
if let Some(target_path) = &config.target_path { if let Some(target_path) = &config.target_path {
target_path_rel = Some(target_path.strip_prefix(project_dir).map_err(|_| { target_path_rel = match target_path.strip_prefix(project_dir) {
anyhow!( Ok(p) => Some(p.with_unix_encoding()),
"Target path '{}' doesn't begin with '{}'", Err(_) => {
target_path.display(), bail!("Target path '{}' doesn't begin with '{}'", target_path, project_dir);
project_dir.display() }
) };
})?);
} }
if let Some(base_path) = &config.base_path { if let Some(base_path) = &config.base_path {
base_path_rel = Some(base_path.strip_prefix(project_dir).map_err(|_| { base_path_rel = match base_path.strip_prefix(project_dir) {
anyhow!( Ok(p) => Some(p.with_unix_encoding()),
"Base path '{}' doesn't begin with '{}'", Err(_) => {
base_path.display(), bail!("Base path '{}' doesn't begin with '{}'", base_path, project_dir);
project_dir.display() }
) };
})?);
}; };
} }
@ -80,13 +79,13 @@ fn run_build(
Some(target_path_rel) if config.build_target => { Some(target_path_rel) if config.build_target => {
update_status( update_status(
context, context,
format!("Building target {}", target_path_rel.display()), format!("Building target {target_path_rel}"),
step_idx, step_idx,
total, total,
&cancel, &cancel,
)?; )?;
step_idx += 1; step_idx += 1;
run_make(&config.build_config, target_path_rel) run_make(&config.build_config, target_path_rel.as_ref())
} }
_ => BuildStatus::default(), _ => BuildStatus::default(),
}; };
@ -95,13 +94,13 @@ fn run_build(
Some(base_path_rel) if config.build_base => { Some(base_path_rel) if config.build_base => {
update_status( update_status(
context, context,
format!("Building base {}", base_path_rel.display()), format!("Building base {base_path_rel}"),
step_idx, step_idx,
total, total,
&cancel, &cancel,
)?; )?;
step_idx += 1; step_idx += 1;
run_make(&config.build_config, base_path_rel) run_make(&config.build_config, base_path_rel.as_ref())
} }
_ => BuildStatus::default(), _ => BuildStatus::default(),
}; };
@ -112,19 +111,19 @@ fn run_build(
Some(target_path) if first_status.success => { Some(target_path) if first_status.success => {
update_status( update_status(
context, context,
format!("Loading target {}", target_path.display()), format!("Loading target {target_path}"),
step_idx, step_idx,
total, total,
&cancel, &cancel,
)?; )?;
step_idx += 1; step_idx += 1;
match read::read(target_path, &config.diff_obj_config) { match read::read(target_path.as_ref(), &config.diff_obj_config) {
Ok(obj) => Some(obj), Ok(obj) => Some(obj),
Err(e) => { Err(e) => {
first_status = BuildStatus { first_status = BuildStatus {
success: false, success: false,
stdout: format!("Loading object '{}'", target_path.display()), stdout: format!("Loading object '{target_path}'"),
stderr: format!("{:#}", e), stderr: format!("{e:#}"),
..Default::default() ..Default::default()
}; };
None None
@ -140,21 +139,15 @@ fn run_build(
let second_obj = match &config.base_path { let second_obj = match &config.base_path {
Some(base_path) if second_status.success => { Some(base_path) if second_status.success => {
update_status( update_status(context, format!("Loading base {base_path}"), step_idx, total, &cancel)?;
context,
format!("Loading base {}", base_path.display()),
step_idx,
total,
&cancel,
)?;
step_idx += 1; step_idx += 1;
match read::read(base_path, &config.diff_obj_config) { match read::read(base_path.as_ref(), &config.diff_obj_config) {
Ok(obj) => Some(obj), Ok(obj) => Some(obj),
Err(e) => { Err(e) => {
second_status = BuildStatus { second_status = BuildStatus {
success: false, success: false,
stdout: format!("Loading object '{}'", base_path.display()), stdout: format!("Loading object '{base_path}'"),
stderr: format!("{:#}", e), stderr: format!("{e:#}"),
..Default::default() ..Default::default()
}; };
None None
@ -171,11 +164,11 @@ fn run_build(
update_status(context, "Performing diff".to_string(), step_idx, total, &cancel)?; update_status(context, "Performing diff".to_string(), step_idx, total, &cancel)?;
step_idx += 1; step_idx += 1;
let result = diff_objs( let result = diff_objs(
&config.diff_obj_config,
&config.mapping_config,
first_obj.as_ref(), first_obj.as_ref(),
second_obj.as_ref(), second_obj.as_ref(),
None, None,
&config.diff_obj_config,
&config.mapping_config,
)?; )?;
update_status(context, "Complete".to_string(), step_idx, total, &cancel)?; update_status(context, "Complete".to_string(), step_idx, total, &cancel)?;

View File

@ -10,7 +10,7 @@ use anyhow::{Context, Result};
pub use self_update; // Re-export self_update crate pub use self_update; // Re-export self_update crate
use self_update::update::ReleaseUpdate; use self_update::update::ReleaseUpdate;
use crate::jobs::{start_job, update_status, Job, JobContext, JobResult, JobState}; use crate::jobs::{Job, JobContext, JobResult, JobState, start_job, update_status};
pub struct UpdateConfig { pub struct UpdateConfig {
pub build_updater: fn() -> Result<Box<dyn ReleaseUpdate>>, pub build_updater: fn() -> Result<Box<dyn ReleaseUpdate>>,

View File

@ -1,3 +1,7 @@
#![allow(clippy::too_many_arguments)]
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
#[cfg(feature = "any-arch")] #[cfg(feature = "any-arch")]
pub mod arch; pub mod arch;
#[cfg(feature = "bindings")] #[cfg(feature = "bindings")]

View File

@ -0,0 +1,109 @@
use alloc::{borrow::Cow, vec::Vec};
use anyhow::{Context, Result};
use object::{Object as _, ObjectSection as _};
use typed_arena::Arena;
use crate::obj::{Section, SectionKind};
/// Parse line information from DWARF 2+ sections.
pub(crate) fn parse_line_info_dwarf2(
obj_file: &object::File,
sections: &mut [Section],
) -> Result<()> {
let arena_data = Arena::new();
let arena_relocations = Arena::new();
let endian = match obj_file.endianness() {
object::Endianness::Little => gimli::RunTimeEndian::Little,
object::Endianness::Big => gimli::RunTimeEndian::Big,
};
let dwarf = gimli::Dwarf::load(|id: gimli::SectionId| -> Result<_> {
load_file_section(id, obj_file, endian, &arena_data, &arena_relocations)
})
.context("loading DWARF sections")?;
let mut iter = dwarf.units();
if let Some(header) = iter.next().map_err(|e| gimli_error(e, "iterating over DWARF units"))? {
let unit = dwarf.unit(header).map_err(|e| gimli_error(e, "loading DWARF unit"))?;
if let Some(program) = unit.line_program.clone() {
let mut text_sections = sections.iter_mut().filter(|s| s.kind == SectionKind::Code);
let mut lines = text_sections.next().map(|section| &mut section.line_info);
let mut rows = program.rows();
while let Some((_header, row)) =
rows.next_row().map_err(|e| gimli_error(e, "loading program row"))?
{
if let (Some(line), Some(lines)) = (row.line(), &mut lines) {
lines.insert(row.address(), line.get() as u32);
}
if row.end_sequence() {
// The next row is the start of a new sequence, which means we must
// advance to the next .text section.
lines = text_sections.next().map(|section| &mut section.line_info);
}
}
}
}
if iter.next().map_err(|e| gimli_error(e, "checking for next unit"))?.is_some() {
log::warn!("Multiple units found in DWARF data, only processing the first");
}
Ok(())
}
#[derive(Debug, Default)]
struct RelocationMap(object::read::RelocationMap);
impl RelocationMap {
fn add(&mut self, file: &object::File, section: &object::Section) {
for (offset, relocation) in section.relocations() {
if let Err(e) = self.0.add(file, offset, relocation) {
log::error!(
"Relocation error for section {} at offset 0x{:08x}: {}",
section.name().unwrap(),
offset,
e
);
}
}
}
}
impl gimli::read::Relocate for &'_ RelocationMap {
fn relocate_address(&self, offset: usize, value: u64) -> gimli::Result<u64> {
Ok(self.0.relocate(offset as u64, value))
}
fn relocate_offset(&self, offset: usize, value: usize) -> gimli::Result<usize> {
<usize as gimli::ReaderOffset>::from_u64(self.0.relocate(offset as u64, value as u64))
}
}
type Relocate<'a, R> = gimli::RelocateReader<R, &'a RelocationMap>;
fn load_file_section<'input, 'arena, Endian: gimli::Endianity>(
id: gimli::SectionId,
file: &object::File<'input>,
endian: Endian,
arena_data: &'arena Arena<Cow<'input, [u8]>>,
arena_relocations: &'arena Arena<RelocationMap>,
) -> Result<Relocate<'arena, gimli::EndianSlice<'arena, Endian>>> {
let mut relocations = RelocationMap::default();
let data = match file.section_by_name(id.name()) {
Some(ref section) => {
relocations.add(file, section);
section.uncompressed_data()?
}
// Use a non-zero capacity so that `ReaderOffsetId`s are unique.
None => Cow::Owned(Vec::with_capacity(1)),
};
let data_ref = arena_data.alloc(data);
let section = gimli::EndianSlice::new(data_ref, endian);
let relocations = arena_relocations.alloc(relocations);
Ok(Relocate::new(section, relocations))
}
#[inline]
fn gimli_error(e: gimli::Error, context: &str) -> anyhow::Error {
anyhow::anyhow!("gimli error {context}: {e:?}")
}

View File

@ -1,23 +1,42 @@
#[cfg(feature = "dwarf")]
mod dwarf2;
pub mod read; pub mod read;
pub mod split_meta; pub mod split_meta;
use std::{borrow::Cow, collections::BTreeMap, fmt, path::PathBuf}; use alloc::{
borrow::Cow,
boxed::Box,
collections::BTreeMap,
string::{String, ToString},
vec,
vec::Vec,
};
use core::{
fmt,
num::{NonZeroU32, NonZeroU64},
};
use filetime::FileTime; use flagset::{FlagSet, flags};
use flagset::{flags, FlagSet};
use object::RelocationFlags;
use split_meta::SplitMeta;
use crate::{arch::ObjArch, util::ReallySigned}; use crate::{
arch::{Arch, ArchDummy},
obj::split_meta::SplitMeta,
util::ReallySigned,
};
#[derive(Debug, Eq, PartialEq, Copy, Clone)] #[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
pub enum ObjSectionKind { pub enum SectionKind {
#[default]
Unknown = -1,
Code, Code,
Data, Data,
Bss, Bss,
Common,
} }
flags! { flags! {
pub enum ObjSymbolFlags: u8 { #[derive(Hash)]
pub enum SymbolFlag: u8 {
Global, Global,
Local, Local,
Weak, Weak,
@ -26,105 +45,199 @@ flags! {
/// Has extra data associated with the symbol /// Has extra data associated with the symbol
/// (e.g. exception table entry) /// (e.g. exception table entry)
HasExtra, HasExtra,
/// Symbol size was missing and was inferred
SizeInferred,
/// Symbol should be ignored by any diffing
Ignored,
} }
} }
#[derive(Debug, Copy, Clone, Default)]
pub struct ObjSymbolFlagSet(pub FlagSet<ObjSymbolFlags>);
#[derive(Debug, Clone)] pub type SymbolFlagSet = FlagSet<SymbolFlag>;
pub struct ObjSection {
flags! {
#[derive(Hash)]
pub enum SectionFlag: u8 {
/// Section combined from multiple input sections
Combined,
}
}
pub type SectionFlagSet = FlagSet<SectionFlag>;
#[derive(Debug, Clone, Default)]
pub struct Section {
/// Unique section ID
pub id: String,
pub name: String, pub name: String,
pub kind: ObjSectionKind,
pub address: u64, pub address: u64,
pub size: u64, pub size: u64,
pub data: Vec<u8>, pub kind: SectionKind,
pub orig_index: usize, pub data: SectionData,
pub symbols: Vec<ObjSymbol>, pub flags: SectionFlagSet,
pub relocations: Vec<ObjReloc>, pub align: Option<NonZeroU64>,
pub virtual_address: Option<u64>, pub relocations: Vec<Relocation>,
/// Line number info (.line or .debug_line section) /// Line number info (.line or .debug_line section)
pub line_info: BTreeMap<u64, u32>, pub line_info: BTreeMap<u64, u32>,
/// Original virtual address (from .note.split section)
pub virtual_address: Option<u64>,
}
#[derive(Clone, Default)]
#[repr(transparent)]
pub struct SectionData(pub Vec<u8>);
impl core::ops::Deref for SectionData {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl fmt::Debug for SectionData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("SectionData").field(&self.0.len()).finish()
}
}
impl Section {
pub fn data_range(&self, address: u64, size: usize) -> Option<&[u8]> {
let offset = address.checked_sub(self.address)?;
self.data.get(offset as usize..offset as usize + size)
}
// The alignment to use when "Combine data/text sections" is enabled.
pub fn combined_alignment(&self) -> u64 {
const MIN_ALIGNMENT: u64 = 4;
self.align.map(|align| align.get().max(MIN_ALIGNMENT)).unwrap_or(MIN_ALIGNMENT)
}
pub fn relocation_at<'obj>(
&'obj self,
obj: &'obj Object,
ins_ref: InstructionRef,
) -> Option<ResolvedRelocation<'obj>> {
match self.relocations.binary_search_by_key(&ins_ref.address, |r| r.address) {
Ok(mut i) => {
// Find the first relocation at the address
while i
.checked_sub(1)
.and_then(|n| self.relocations.get(n))
.is_some_and(|r| r.address == ins_ref.address)
{
i -= 1;
}
self.relocations.get(i)
}
Err(i) => self
.relocations
.get(i)
.filter(|r| r.address < ins_ref.address + ins_ref.size as u64),
}
.and_then(|relocation| {
let symbol = obj.symbols.get(relocation.target_symbol)?;
Some(ResolvedRelocation { relocation, symbol })
})
}
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum ObjInsArgValue { pub enum InstructionArgValue<'a> {
Signed(i64), Signed(i64),
Unsigned(u64), Unsigned(u64),
Opaque(Cow<'static, str>), Opaque(Cow<'a, str>),
} }
impl ObjInsArgValue { impl InstructionArgValue<'_> {
pub fn loose_eq(&self, other: &ObjInsArgValue) -> bool { pub fn loose_eq(&self, other: &InstructionArgValue) -> bool {
match (self, other) { match (self, other) {
(ObjInsArgValue::Signed(a), ObjInsArgValue::Signed(b)) => a == b, (InstructionArgValue::Signed(a), InstructionArgValue::Signed(b)) => a == b,
(ObjInsArgValue::Unsigned(a), ObjInsArgValue::Unsigned(b)) => a == b, (InstructionArgValue::Unsigned(a), InstructionArgValue::Unsigned(b)) => a == b,
(ObjInsArgValue::Signed(a), ObjInsArgValue::Unsigned(b)) (InstructionArgValue::Signed(a), InstructionArgValue::Unsigned(b))
| (ObjInsArgValue::Unsigned(b), ObjInsArgValue::Signed(a)) => *a as u64 == *b, | (InstructionArgValue::Unsigned(b), InstructionArgValue::Signed(a)) => *a as u64 == *b,
(ObjInsArgValue::Opaque(a), ObjInsArgValue::Opaque(b)) => a == b, (InstructionArgValue::Opaque(a), InstructionArgValue::Opaque(b)) => a == b,
_ => false, _ => false,
} }
} }
pub fn to_static(&self) -> InstructionArgValue<'static> {
match self {
InstructionArgValue::Signed(v) => InstructionArgValue::Signed(*v),
InstructionArgValue::Unsigned(v) => InstructionArgValue::Unsigned(*v),
InstructionArgValue::Opaque(v) => InstructionArgValue::Opaque(v.to_string().into()),
}
}
pub fn into_static(self) -> InstructionArgValue<'static> {
match self {
InstructionArgValue::Signed(v) => InstructionArgValue::Signed(v),
InstructionArgValue::Unsigned(v) => InstructionArgValue::Unsigned(v),
InstructionArgValue::Opaque(v) => {
InstructionArgValue::Opaque(Cow::Owned(v.into_owned()))
}
}
}
} }
impl fmt::Display for ObjInsArgValue { impl fmt::Display for InstructionArgValue<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
ObjInsArgValue::Signed(v) => write!(f, "{:#x}", ReallySigned(*v)), InstructionArgValue::Signed(v) => write!(f, "{:#x}", ReallySigned(*v)),
ObjInsArgValue::Unsigned(v) => write!(f, "{:#x}", v), InstructionArgValue::Unsigned(v) => write!(f, "{v:#x}"),
ObjInsArgValue::Opaque(v) => write!(f, "{}", v), InstructionArgValue::Opaque(v) => write!(f, "{v}"),
} }
} }
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum ObjInsArg { pub enum InstructionArg<'a> {
PlainText(Cow<'static, str>), Value(InstructionArgValue<'a>),
Arg(ObjInsArgValue),
Reloc, Reloc,
BranchDest(u64), BranchDest(u64),
} }
impl ObjInsArg { impl InstructionArg<'_> {
#[inline] pub fn loose_eq(&self, other: &InstructionArg) -> bool {
pub fn is_plain_text(&self) -> bool { matches!(self, ObjInsArg::PlainText(_)) }
pub fn loose_eq(&self, other: &ObjInsArg) -> bool {
match (self, other) { match (self, other) {
(ObjInsArg::Arg(a), ObjInsArg::Arg(b)) => a.loose_eq(b), (InstructionArg::Value(a), InstructionArg::Value(b)) => a.loose_eq(b),
(ObjInsArg::Reloc, ObjInsArg::Reloc) => true, (InstructionArg::Reloc, InstructionArg::Reloc) => true,
(ObjInsArg::BranchDest(a), ObjInsArg::BranchDest(b)) => a == b, (InstructionArg::BranchDest(a), InstructionArg::BranchDest(b)) => a == b,
_ => false, _ => false,
} }
} }
pub fn to_static(&self) -> InstructionArg<'static> {
match self {
InstructionArg::Value(v) => InstructionArg::Value(v.to_static()),
InstructionArg::Reloc => InstructionArg::Reloc,
InstructionArg::BranchDest(v) => InstructionArg::BranchDest(*v),
}
}
pub fn into_static(self) -> InstructionArg<'static> {
match self {
InstructionArg::Value(v) => InstructionArg::Value(v.into_static()),
InstructionArg::Reloc => InstructionArg::Reloc,
InstructionArg::BranchDest(v) => InstructionArg::BranchDest(v),
}
}
} }
#[derive(Debug, Clone)] #[derive(Copy, Clone, Debug, Default)]
pub struct ObjIns { pub struct InstructionRef {
pub address: u64, pub address: u64,
pub size: u8, pub size: u8,
pub op: u16, pub opcode: u16,
pub mnemonic: Cow<'static, str>,
pub args: Vec<ObjInsArg>,
pub reloc: Option<ObjReloc>,
pub branch_dest: Option<u64>, pub branch_dest: Option<u64>,
/// Line number
pub line: Option<u32>,
/// Formatted instruction
pub formatted: String,
/// Original (unsimplified) instruction
pub orig: Option<String>,
} }
impl ObjIns { #[derive(Debug, Clone)]
/// Iterate over non-PlainText arguments. pub struct ParsedInstruction {
#[inline] pub ins_ref: InstructionRef,
pub fn iter_args(&self) -> impl DoubleEndedIterator<Item = &ObjInsArg> { pub mnemonic: Cow<'static, str>,
self.args.iter().filter(|a| !a.is_plain_text()) pub args: Vec<InstructionArg<'static>>,
}
} }
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
pub enum ObjSymbolKind { pub enum SymbolKind {
#[default] #[default]
Unknown, Unknown,
Function, Function,
@ -132,59 +245,195 @@ pub enum ObjSymbolKind {
Section, Section,
} }
#[derive(Debug, Clone)] #[derive(Debug)]
pub struct ObjSymbol { pub enum FlowAnalysisValue {
Text(String),
}
pub trait FlowAnalysisResult: core::fmt::Debug + Send {
fn get_argument_value_at_address(
&self,
address: u64,
argument: u8,
) -> Option<&FlowAnalysisValue>;
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
pub struct Symbol {
pub name: String, pub name: String,
pub demangled_name: Option<String>, pub demangled_name: Option<String>,
pub address: u64, pub address: u64,
pub section_address: u64,
pub size: u64, pub size: u64,
pub size_known: bool, pub kind: SymbolKind,
pub kind: ObjSymbolKind, pub section: Option<usize>,
pub flags: ObjSymbolFlagSet, pub flags: SymbolFlagSet,
pub orig_section_index: Option<usize>, /// Alignment (from Metrowerks .comment section)
pub align: Option<NonZeroU32>,
/// Original virtual address (from .note.split section) /// Original virtual address (from .note.split section)
pub virtual_address: Option<u64>, pub virtual_address: Option<u64>,
/// Original index in object symbol table
pub original_index: Option<usize>,
pub bytes: Vec<u8>,
} }
pub struct ObjInfo { #[derive(Debug)]
pub arch: Box<dyn ObjArch>, pub struct Object {
pub path: Option<PathBuf>, pub arch: Box<dyn Arch>,
pub timestamp: Option<FileTime>, pub endianness: object::Endianness,
pub sections: Vec<ObjSection>, pub symbols: Vec<Symbol>,
/// Common BSS symbols pub sections: Vec<Section>,
pub common: Vec<ObjSymbol>,
/// Split object metadata (.note.split section) /// Split object metadata (.note.split section)
pub split_meta: Option<SplitMeta>, pub split_meta: Option<SplitMeta>,
#[cfg(feature = "std")]
pub path: Option<std::path::PathBuf>,
#[cfg(feature = "std")]
pub timestamp: Option<filetime::FileTime>,
pub flow_analysis_results: BTreeMap<u64, Box<dyn FlowAnalysisResult>>,
} }
#[derive(Debug, Clone)] impl Default for Object {
pub struct ObjReloc { fn default() -> Self {
Self {
arch: ArchDummy::new(),
endianness: object::Endianness::Little,
symbols: vec![],
sections: vec![],
split_meta: None,
#[cfg(feature = "std")]
path: None,
#[cfg(feature = "std")]
timestamp: None,
flow_analysis_results: BTreeMap::<u64, Box<dyn FlowAnalysisResult>>::new(),
}
}
}
impl Object {
pub fn resolve_instruction_ref(
&self,
symbol_index: usize,
ins_ref: InstructionRef,
) -> Option<ResolvedInstructionRef<'_>> {
let symbol = self.symbols.get(symbol_index)?;
let section_index = symbol.section?;
let section = self.sections.get(section_index)?;
let offset = ins_ref.address.checked_sub(section.address)?;
let code = section.data.get(offset as usize..offset as usize + ins_ref.size as usize)?;
let relocation = section.relocation_at(self, ins_ref);
Some(ResolvedInstructionRef {
ins_ref,
symbol_index,
symbol,
section,
section_index,
code,
relocation,
})
}
pub fn symbol_data(&self, symbol_index: usize) -> Option<&[u8]> {
let symbol = self.symbols.get(symbol_index)?;
let section_index = symbol.section?;
let section = self.sections.get(section_index)?;
let offset = symbol.address.checked_sub(section.address)?;
section.data.get(offset as usize..offset as usize + symbol.size as usize)
}
pub fn symbol_by_name(&self, name: &str) -> Option<usize> {
self.symbols.iter().position(|symbol| symbol.section.is_some() && symbol.name == name)
}
pub fn get_flow_analysis_result(&self, symbol: &Symbol) -> Option<&dyn FlowAnalysisResult> {
let key = symbol.section.unwrap_or_default() as u64 * 1024 * 1024 * 1024 + symbol.address;
self.flow_analysis_results.get(&key).map(|result| result.as_ref())
}
pub fn add_flow_analysis_result(
&mut self,
symbol: &Symbol,
result: Box<dyn FlowAnalysisResult>,
) {
let key = symbol.section.unwrap_or_default() as u64 * 1024 * 1024 * 1024 + symbol.address;
self.flow_analysis_results.insert(key, result);
}
pub fn has_flow_analysis_result(&self) -> bool { !self.flow_analysis_results.is_empty() }
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Relocation {
pub flags: RelocationFlags, pub flags: RelocationFlags,
pub address: u64, pub address: u64,
pub target: ObjSymbol, pub target_symbol: usize,
pub addend: i64, pub addend: i64,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct SymbolRef { pub enum RelocationFlags {
pub section_idx: usize, Elf(u32),
pub symbol_idx: usize, Coff(u16),
} }
pub const SECTION_COMMON: usize = usize::MAX - 1; #[derive(Debug, Copy, Clone)]
pub struct ResolvedRelocation<'a> {
pub relocation: &'a Relocation,
pub symbol: &'a Symbol,
}
impl ObjInfo { #[derive(Debug, Copy, Clone)]
pub fn section_symbol(&self, symbol_ref: SymbolRef) -> (Option<&ObjSection>, &ObjSymbol) { pub struct ResolvedSymbol<'obj> {
if symbol_ref.section_idx == SECTION_COMMON { pub obj: &'obj Object,
let symbol = &self.common[symbol_ref.symbol_idx]; pub symbol_index: usize,
return (None, symbol); pub symbol: &'obj Symbol,
pub section_index: usize,
pub section: &'obj Section,
pub data: &'obj [u8],
}
#[derive(Debug, Copy, Clone)]
pub struct ResolvedInstructionRef<'obj> {
pub ins_ref: InstructionRef,
pub symbol_index: usize,
pub symbol: &'obj Symbol,
pub section_index: usize,
pub section: &'obj Section,
pub code: &'obj [u8],
pub relocation: Option<ResolvedRelocation<'obj>>,
}
static DUMMY_SYMBOL: Symbol = Symbol {
name: String::new(),
demangled_name: None,
address: 0,
size: 0,
kind: SymbolKind::Unknown,
section: None,
flags: SymbolFlagSet::empty(),
align: None,
virtual_address: None,
};
static DUMMY_SECTION: Section = Section {
id: String::new(),
name: String::new(),
address: 0,
size: 0,
kind: SectionKind::Unknown,
data: SectionData(Vec::new()),
flags: SectionFlagSet::empty(),
align: None,
relocations: Vec::new(),
line_info: BTreeMap::new(),
virtual_address: None,
};
impl Default for ResolvedInstructionRef<'_> {
fn default() -> Self {
Self {
ins_ref: InstructionRef::default(),
symbol_index: 0,
symbol: &DUMMY_SYMBOL,
section_index: 0,
section: &DUMMY_SECTION,
code: &[],
relocation: None,
} }
let section = &self.sections[symbol_ref.section_idx];
let symbol = &section.symbols[symbol_ref.symbol_idx];
(Some(section), symbol)
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,165 @@
---
source: objdiff-core/src/obj/read.rs
expression: "(sections, symbols)"
---
(
[
Section {
id: ".text-0",
name: ".text",
address: 0,
size: 8,
kind: Code,
data: SectionData(
8,
),
flags: FlagSet(),
align: None,
relocations: [
Relocation {
flags: Elf(
0,
),
address: 0,
target_symbol: 0,
addend: 4,
},
Relocation {
flags: Elf(
0,
),
address: 2,
target_symbol: 1,
addend: 0,
},
Relocation {
flags: Elf(
0,
),
address: 4,
target_symbol: 0,
addend: 10,
},
],
line_info: {},
virtual_address: None,
},
Section {
id: ".data-combined",
name: ".data",
address: 0,
size: 12,
kind: Data,
data: SectionData(
12,
),
flags: FlagSet(Combined),
align: None,
relocations: [
Relocation {
flags: Elf(
0,
),
address: 0,
target_symbol: 2,
addend: 0,
},
Relocation {
flags: Elf(
0,
),
address: 4,
target_symbol: 2,
addend: 0,
},
],
line_info: {
0: 1,
8: 2,
},
virtual_address: None,
},
Section {
id: ".data-1",
name: ".data",
address: 0,
size: 0,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
align: None,
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".data-2",
name: ".data",
address: 0,
size: 0,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
align: None,
relocations: [],
line_info: {},
virtual_address: None,
},
],
[
Symbol {
name: ".data",
demangled_name: None,
address: 0,
size: 0,
kind: Section,
section: Some(
1,
),
flags: FlagSet(),
align: None,
virtual_address: None,
},
Symbol {
name: "symbol",
demangled_name: None,
address: 4,
size: 4,
kind: Object,
section: Some(
1,
),
flags: FlagSet(),
align: None,
virtual_address: None,
},
Symbol {
name: "function",
demangled_name: None,
address: 0,
size: 8,
kind: Function,
section: Some(
0,
),
flags: FlagSet(),
align: None,
virtual_address: None,
},
Symbol {
name: ".data",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(),
align: None,
virtual_address: None,
},
],
)

View File

@ -1,6 +1,11 @@
use std::{io, io::Write}; use alloc::{string::String, vec, vec::Vec};
use object::{elf::SHT_NOTE, Endian, ObjectSection}; use anyhow::{Result, anyhow};
use object::{Endian, ObjectSection, elf::SHT_NOTE};
#[cfg(feature = "std")]
use crate::util::align_data_to_4;
use crate::util::align_size_to_4;
pub const SPLITMETA_SECTION: &str = ".note.split"; pub const SPLITMETA_SECTION: &str = ".note.split";
pub const SHT_SPLITMETA: u32 = SHT_NOTE; pub const SHT_SPLITMETA: u32 = SHT_NOTE;
@ -27,10 +32,10 @@ const NT_SPLIT_MODULE_ID: u32 = u32::from_be_bytes(*b"MODI");
const NT_SPLIT_VIRTUAL_ADDRESSES: u32 = u32::from_be_bytes(*b"VIRT"); const NT_SPLIT_VIRTUAL_ADDRESSES: u32 = u32::from_be_bytes(*b"VIRT");
impl SplitMeta { impl SplitMeta {
pub fn from_section<E>(section: object::Section, e: E, is_64: bool) -> io::Result<Self> pub fn from_section<E>(section: object::Section, e: E, is_64: bool) -> Result<Self>
where E: Endian { where E: Endian {
let mut result = SplitMeta::default(); let mut result = SplitMeta::default();
let data = section.uncompressed_data().map_err(object_io_error)?; let data = section.uncompressed_data().map_err(object_error)?;
let mut iter = NoteIterator::new(data.as_ref(), section.align(), e, is_64)?; let mut iter = NoteIterator::new(data.as_ref(), section.align(), e, is_64)?;
while let Some(note) = iter.next(e)? { while let Some(note) = iter.next(e)? {
if note.name != ELF_NOTE_SPLIT { if note.name != ELF_NOTE_SPLIT {
@ -38,20 +43,19 @@ impl SplitMeta {
} }
match note.n_type { match note.n_type {
NT_SPLIT_GENERATOR => { NT_SPLIT_GENERATOR => {
let string = String::from_utf8(note.desc.to_vec()) let string =
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; String::from_utf8(note.desc.to_vec()).map_err(anyhow::Error::new)?;
result.generator = Some(string); result.generator = Some(string);
} }
NT_SPLIT_MODULE_NAME => { NT_SPLIT_MODULE_NAME => {
let string = String::from_utf8(note.desc.to_vec()) let string =
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; String::from_utf8(note.desc.to_vec()).map_err(anyhow::Error::new)?;
result.module_name = Some(string); result.module_name = Some(string);
} }
NT_SPLIT_MODULE_ID => { NT_SPLIT_MODULE_ID => {
result.module_id = result.module_id = Some(e.read_u32_bytes(
Some(e.read_u32_bytes(note.desc.try_into().map_err(|_| { note.desc.try_into().map_err(|_| anyhow!("Invalid module ID size"))?,
io::Error::new(io::ErrorKind::InvalidData, "Invalid module ID size") ));
})?));
} }
NT_SPLIT_VIRTUAL_ADDRESSES => { NT_SPLIT_VIRTUAL_ADDRESSES => {
let vec = if is_64 { let vec = if is_64 {
@ -79,10 +83,11 @@ impl SplitMeta {
Ok(result) Ok(result)
} }
pub fn to_writer<E, W>(&self, writer: &mut W, e: E, is_64: bool) -> io::Result<()> #[cfg(feature = "std")]
pub fn to_writer<E, W>(&self, writer: &mut W, e: E, is_64: bool) -> std::io::Result<()>
where where
E: Endian, E: Endian,
W: Write + ?Sized, W: std::io::Write + ?Sized,
{ {
if let Some(generator) = &self.generator { if let Some(generator) = &self.generator {
write_note_header(writer, e, NT_SPLIT_GENERATOR, generator.len())?; write_note_header(writer, e, NT_SPLIT_GENERATOR, generator.len())?;
@ -137,10 +142,9 @@ impl SplitMeta {
} }
} }
/// Convert an object::read::Error to an io::Error. /// Convert an object::read::Error to a String.
fn object_io_error(err: object::read::Error) -> io::Error { #[inline]
io::Error::new(io::ErrorKind::InvalidData, err) fn object_error(err: object::read::Error) -> anyhow::Error { anyhow::Error::new(err) }
}
/// An ELF note entry. /// An ELF note entry.
struct Note<'data> { struct Note<'data> {
@ -161,27 +165,27 @@ where E: Endian
impl<'data, E> NoteIterator<'data, E> impl<'data, E> NoteIterator<'data, E>
where E: Endian where E: Endian
{ {
fn new(data: &'data [u8], align: u64, e: E, is_64: bool) -> io::Result<Self> { fn new(data: &'data [u8], align: u64, e: E, is_64: bool) -> Result<Self> {
Ok(if is_64 { Ok(if is_64 {
NoteIterator::B64( NoteIterator::B64(
object::read::elf::NoteIterator::new(e, align, data).map_err(object_io_error)?, object::read::elf::NoteIterator::new(e, align, data).map_err(object_error)?,
) )
} else { } else {
NoteIterator::B32( NoteIterator::B32(
object::read::elf::NoteIterator::new(e, align as u32, data) object::read::elf::NoteIterator::new(e, align as u32, data)
.map_err(object_io_error)?, .map_err(object_error)?,
) )
}) })
} }
fn next(&mut self, e: E) -> io::Result<Option<Note<'data>>> { fn next(&mut self, e: E) -> Result<Option<Note<'data>>> {
match self { match self {
NoteIterator::B32(iter) => Ok(iter.next().map_err(object_io_error)?.map(|note| Note { NoteIterator::B32(iter) => Ok(iter.next().map_err(object_error)?.map(|note| Note {
n_type: note.n_type(e), n_type: note.n_type(e),
name: note.name(), name: note.name(),
desc: note.desc(), desc: note.desc(),
})), })),
NoteIterator::B64(iter) => Ok(iter.next().map_err(object_io_error)?.map(|note| Note { NoteIterator::B64(iter) => Ok(iter.next().map_err(object_error)?.map(|note| Note {
n_type: note.n_type(e), n_type: note.n_type(e),
name: note.name(), name: note.name(),
desc: note.desc(), desc: note.desc(),
@ -190,16 +194,6 @@ where E: Endian
} }
} }
fn align_size_to_4(size: usize) -> usize { (size + 3) & !3 }
fn align_data_to_4<W: Write + ?Sized>(writer: &mut W, len: usize) -> io::Result<()> {
const ALIGN_BYTES: &[u8] = &[0; 4];
if len % 4 != 0 {
writer.write_all(&ALIGN_BYTES[..4 - len % 4])?;
}
Ok(())
}
// ELF note format: // ELF note format:
// Name Size | 4 bytes (integer) // Name Size | 4 bytes (integer)
// Desc Size | 4 bytes (integer) // Desc Size | 4 bytes (integer)
@ -208,10 +202,11 @@ fn align_data_to_4<W: Write + ?Sized>(writer: &mut W, len: usize) -> io::Result<
// Desc | variable size, padded to a 4 byte boundary // Desc | variable size, padded to a 4 byte boundary
const NOTE_HEADER_SIZE: usize = 12 + ((ELF_NOTE_SPLIT.len() + 4) & !3); const NOTE_HEADER_SIZE: usize = 12 + ((ELF_NOTE_SPLIT.len() + 4) & !3);
fn write_note_header<E, W>(writer: &mut W, e: E, kind: u32, desc_len: usize) -> io::Result<()> #[cfg(feature = "std")]
fn write_note_header<E, W>(writer: &mut W, e: E, kind: u32, desc_len: usize) -> std::io::Result<()>
where where
E: Endian, E: Endian,
W: Write + ?Sized, W: std::io::Write + ?Sized,
{ {
writer.write_all(&e.write_u32_bytes(ELF_NOTE_SPLIT.len() as u32 + 1))?; // Name Size writer.write_all(&e.write_u32_bytes(ELF_NOTE_SPLIT.len() as u32 + 1))?; // Name Size
writer.write_all(&e.write_u32_bytes(desc_len as u32))?; // Desc Size writer.write_all(&e.write_u32_bytes(desc_len as u32))?; // Desc Size

View File

@ -1,18 +1,15 @@
use std::{ use alloc::{format, vec::Vec};
fmt::{LowerHex, UpperHex}, use core::fmt;
io::Read,
};
use anyhow::Result; use anyhow::{Result, ensure};
use byteorder::{NativeEndian, ReadBytesExt};
use num_traits::PrimInt; use num_traits::PrimInt;
use object::{Endian, Object}; use object::{Endian, Object};
// https://stackoverflow.com/questions/44711012/how-do-i-format-a-signed-integer-to-a-sign-aware-hexadecimal-representation // https://stackoverflow.com/questions/44711012/how-do-i-format-a-signed-integer-to-a-sign-aware-hexadecimal-representation
pub struct ReallySigned<N: PrimInt>(pub(crate) N); pub struct ReallySigned<N: PrimInt>(pub N);
impl<N: PrimInt> LowerHex for ReallySigned<N> { impl<N: PrimInt> fmt::LowerHex for ReallySigned<N> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let num = self.0.to_i64().unwrap(); let num = self.0.to_i64().unwrap();
let prefix = if f.alternate() { "0x" } else { "" }; let prefix = if f.alternate() { "0x" } else { "" };
let bare_hex = format!("{:x}", num.abs()); let bare_hex = format!("{:x}", num.abs());
@ -20,8 +17,8 @@ impl<N: PrimInt> LowerHex for ReallySigned<N> {
} }
} }
impl<N: PrimInt> UpperHex for ReallySigned<N> { impl<N: PrimInt> fmt::UpperHex for ReallySigned<N> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let num = self.0.to_i64().unwrap(); let num = self.0.to_i64().unwrap();
let prefix = if f.alternate() { "0x" } else { "" }; let prefix = if f.alternate() { "0x" } else { "" };
let bare_hex = format!("{:X}", num.abs()); let bare_hex = format!("{:X}", num.abs());
@ -29,10 +26,66 @@ impl<N: PrimInt> UpperHex for ReallySigned<N> {
} }
} }
pub fn read_u32<R: Read>(obj_file: &object::File, reader: &mut R) -> Result<u32> { pub fn read_u32(obj_file: &object::File, reader: &mut &[u8]) -> Result<u32> {
Ok(obj_file.endianness().read_u32(reader.read_u32::<NativeEndian>()?)) ensure!(reader.len() >= 4, "Not enough bytes to read u32");
let value = u32::from_ne_bytes(reader[..4].try_into()?);
*reader = &reader[4..];
Ok(obj_file.endianness().read_u32(value))
} }
pub fn read_u16<R: Read>(obj_file: &object::File, reader: &mut R) -> Result<u16> { pub fn read_u16(obj_file: &object::File, reader: &mut &[u8]) -> Result<u16> {
Ok(obj_file.endianness().read_u16(reader.read_u16::<NativeEndian>()?)) ensure!(reader.len() >= 2, "Not enough bytes to read u16");
let value = u16::from_ne_bytes(reader[..2].try_into()?);
*reader = &reader[2..];
Ok(obj_file.endianness().read_u16(value))
}
pub fn align_size_to_4(size: usize) -> usize { (size + 3) & !3 }
#[cfg(feature = "std")]
pub fn align_data_to_4<W: std::io::Write + ?Sized>(
writer: &mut W,
len: usize,
) -> std::io::Result<()> {
const ALIGN_BYTES: &[u8] = &[0; 4];
if !len.is_multiple_of(4) {
writer.write_all(&ALIGN_BYTES[..4 - len % 4])?;
}
Ok(())
}
pub fn align_u64_to(len: u64, align: u64) -> u64 { len + ((align - (len % align)) % align) }
pub fn align_data_slice_to(data: &mut Vec<u8>, align: u64) {
data.resize(align_u64_to(data.len() as u64, align) as usize, 0);
}
// Float where we specifically care about comparing the raw bits rather than
// caring about IEEE semantics.
#[derive(Copy, Clone, Debug)]
pub struct RawFloat(pub f32);
impl PartialEq for RawFloat {
fn eq(&self, other: &Self) -> bool { self.0.to_bits() == other.0.to_bits() }
}
impl Eq for RawFloat {}
impl Ord for RawFloat {
fn cmp(&self, other: &Self) -> core::cmp::Ordering { self.0.to_bits().cmp(&other.0.to_bits()) }
}
impl PartialOrd for RawFloat {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { Some(self.cmp(other)) }
}
// Double where we specifically care about comparing the raw bits rather than
// caring about IEEE semantics.
#[derive(Copy, Clone, Debug)]
pub struct RawDouble(pub f64);
impl PartialEq for RawDouble {
fn eq(&self, other: &Self) -> bool { self.0.to_bits() == other.0.to_bits() }
}
impl Eq for RawDouble {}
impl Ord for RawDouble {
fn cmp(&self, other: &Self) -> core::cmp::Ordering { self.0.to_bits().cmp(&other.0.to_bits()) }
}
impl PartialOrd for RawDouble {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { Some(self.cmp(other)) }
} }

View File

@ -0,0 +1,46 @@
use objdiff_core::{diff, obj};
mod common;
#[test]
#[cfg(feature = "arm")]
fn read_arm() {
let diff_config = diff::DiffObjConfig { ..Default::default() };
let obj = obj::read::parse(include_object!("data/arm/LinkStateItem.o"), &diff_config).unwrap();
insta::assert_debug_snapshot!(obj);
let symbol_idx =
obj.symbols.iter().position(|s| s.name == "_ZN13LinkStateItem12OnStateLeaveEi").unwrap();
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
insta::assert_debug_snapshot!(diff.instruction_rows);
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
insta::assert_snapshot!(output);
}
#[test]
#[cfg(feature = "arm")]
fn read_thumb() {
let diff_config = diff::DiffObjConfig { ..Default::default() };
let obj = obj::read::parse(include_object!("data/arm/thumb.o"), &diff_config).unwrap();
insta::assert_debug_snapshot!(obj);
let symbol_idx = obj
.symbols
.iter()
.position(|s| s.name == "THUMB_BRANCH_ServerDisplay_UncategorizedMove")
.unwrap();
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
insta::assert_debug_snapshot!(diff.instruction_rows);
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
insta::assert_snapshot!(output);
}
#[test]
#[cfg(feature = "arm")]
fn combine_text_sections() {
let diff_config = diff::DiffObjConfig { combine_text_sections: true, ..Default::default() };
let obj = obj::read::parse(include_object!("data/arm/enemy300.o"), &diff_config).unwrap();
let symbol_idx = obj.symbols.iter().position(|s| s.name == "Enemy300Draw").unwrap();
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
insta::assert_debug_snapshot!(diff.instruction_rows);
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
insta::assert_snapshot!(output);
}

View File

@ -0,0 +1,47 @@
use objdiff_core::{diff, obj};
mod common;
#[test]
#[cfg(feature = "mips")]
fn read_mips() {
let diff_config = diff::DiffObjConfig { mips_register_prefix: true, ..Default::default() };
let obj = obj::read::parse(include_object!("data/mips/main.c.o"), &diff_config).unwrap();
insta::assert_debug_snapshot!(obj);
let symbol_idx = obj.symbols.iter().position(|s| s.name == "ControlEntry").unwrap();
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
insta::assert_debug_snapshot!(diff.instruction_rows);
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
insta::assert_snapshot!(output);
}
#[test]
#[cfg(feature = "mips")]
fn cross_endian_diff() {
let diff_config = diff::DiffObjConfig::default();
let obj_be = obj::read::parse(include_object!("data/mips/code_be.o"), &diff_config).unwrap();
assert_eq!(obj_be.endianness, object::Endianness::Big);
let obj_le = obj::read::parse(include_object!("data/mips/code_le.o"), &diff_config).unwrap();
assert_eq!(obj_le.endianness, object::Endianness::Little);
let left_symbol_idx = obj_be.symbols.iter().position(|s| s.name == "func_00000000").unwrap();
let right_symbol_idx =
obj_le.symbols.iter().position(|s| s.name == "func_00000000__FPcPc").unwrap();
let (left_diff, right_diff) =
diff::code::diff_code(&obj_be, &obj_le, left_symbol_idx, right_symbol_idx, &diff_config)
.unwrap();
// Although the objects differ in endianness, the instructions should match.
assert_eq!(left_diff.instruction_rows[0].kind, diff::InstructionDiffKind::None);
assert_eq!(right_diff.instruction_rows[0].kind, diff::InstructionDiffKind::None);
assert_eq!(left_diff.instruction_rows[1].kind, diff::InstructionDiffKind::None);
assert_eq!(right_diff.instruction_rows[1].kind, diff::InstructionDiffKind::None);
assert_eq!(left_diff.instruction_rows[2].kind, diff::InstructionDiffKind::None);
assert_eq!(right_diff.instruction_rows[2].kind, diff::InstructionDiffKind::None);
}
#[test]
#[cfg(feature = "mips")]
fn filter_non_matching() {
let diff_config = diff::DiffObjConfig::default();
let obj = obj::read::parse(include_object!("data/mips/vw_main.c.o"), &diff_config).unwrap();
insta::assert_debug_snapshot!(obj.symbols);
}

View File

@ -0,0 +1,101 @@
use objdiff_core::{
diff::{self, display},
obj,
obj::SectionKind,
};
mod common;
#[test]
#[cfg(feature = "ppc")]
fn read_ppc() {
let diff_config = diff::DiffObjConfig::default();
let obj = obj::read::parse(include_object!("data/ppc/IObj.o"), &diff_config).unwrap();
insta::assert_debug_snapshot!(obj);
let symbol_idx =
obj.symbols.iter().position(|s| s.name == "Type2Text__10SObjectTagFUi").unwrap();
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
insta::assert_debug_snapshot!(diff.instruction_rows);
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
insta::assert_snapshot!(output);
}
#[test]
#[cfg(feature = "ppc")]
fn read_dwarf1_line_info() {
let diff_config = diff::DiffObjConfig::default();
let obj = obj::read::parse(include_object!("data/ppc/m_Do_hostIO.o"), &diff_config).unwrap();
let line_infos = obj
.sections
.iter()
.filter(|s| s.kind == SectionKind::Code)
.map(|s| s.line_info.clone())
.collect::<Vec<_>>();
insta::assert_debug_snapshot!(line_infos);
}
#[test]
#[cfg(feature = "ppc")]
fn read_extab() {
let diff_config = diff::DiffObjConfig::default();
let obj = obj::read::parse(include_object!("data/ppc/NMWException.o"), &diff_config).unwrap();
insta::assert_debug_snapshot!(obj);
}
#[test]
#[cfg(feature = "ppc")]
fn diff_ppc() {
let diff_config = diff::DiffObjConfig::default();
let mapping_config = diff::MappingConfig::default();
let target_obj =
obj::read::parse(include_object!("data/ppc/CDamageVulnerability_target.o"), &diff_config)
.unwrap();
let base_obj =
obj::read::parse(include_object!("data/ppc/CDamageVulnerability_base.o"), &diff_config)
.unwrap();
let diff =
diff::diff_objs(Some(&target_obj), Some(&base_obj), None, &diff_config, &mapping_config)
.unwrap();
let target_diff = diff.left.as_ref().unwrap();
let base_diff = diff.right.as_ref().unwrap();
let sections_display = display::display_sections(
&target_obj,
target_diff,
display::SymbolFilter::None,
false,
false,
true,
);
insta::assert_debug_snapshot!(sections_display);
let target_symbol_idx = target_obj
.symbols
.iter()
.position(|s| s.name == "WeaponHurts__20CDamageVulnerabilityCFRC11CWeaponModei")
.unwrap();
let target_symbol_diff = &target_diff.symbols[target_symbol_idx];
let base_symbol_idx = base_obj
.symbols
.iter()
.position(|s| s.name == "WeaponHurts__20CDamageVulnerabilityCFRC11CWeaponModei")
.unwrap();
let base_symbol_diff = &base_diff.symbols[base_symbol_idx];
assert_eq!(target_symbol_diff.target_symbol, Some(base_symbol_idx));
assert_eq!(base_symbol_diff.target_symbol, Some(target_symbol_idx));
insta::assert_debug_snapshot!((target_symbol_diff, base_symbol_diff));
}
#[test]
#[cfg(feature = "ppc")]
fn read_vmx128_coff() {
let diff_config = diff::DiffObjConfig { combine_data_sections: true, ..Default::default() };
let obj = obj::read::parse(include_object!("data/ppc/vmx128.obj"), &diff_config).unwrap();
insta::assert_debug_snapshot!(obj);
let symbol_idx =
obj.symbols.iter().position(|s| s.name == "?FloatingPointExample@@YAXXZ").unwrap();
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
insta::assert_debug_snapshot!(diff.instruction_rows);
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
insta::assert_snapshot!(output);
}

View File

@ -0,0 +1,79 @@
use objdiff_core::{diff, diff::display::SymbolFilter, obj};
mod common;
#[test]
#[cfg(feature = "x86")]
fn read_x86() {
let diff_config = diff::DiffObjConfig::default();
let obj = obj::read::parse(include_object!("data/x86/staticdebug.obj"), &diff_config).unwrap();
insta::assert_debug_snapshot!(obj);
let symbol_idx = obj.symbols.iter().position(|s| s.name == "?PrintThing@@YAXXZ").unwrap();
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
insta::assert_debug_snapshot!(diff.instruction_rows);
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
insta::assert_snapshot!(output);
}
#[test]
#[cfg(feature = "x86")]
fn read_x86_combine_sections() {
let diff_config = diff::DiffObjConfig {
combine_data_sections: true,
combine_text_sections: true,
..Default::default()
};
let obj = obj::read::parse(include_object!("data/x86/rtest.obj"), &diff_config).unwrap();
insta::assert_debug_snapshot!(obj.sections);
}
#[test]
#[cfg(feature = "x86")]
fn read_x86_64() {
let diff_config = diff::DiffObjConfig::default();
let obj = obj::read::parse(include_object!("data/x86_64/vs2022.o"), &diff_config).unwrap();
insta::assert_debug_snapshot!(obj);
let symbol_idx =
obj.symbols.iter().position(|s| s.name == "?Dot@Vector@@QEAAMPEAU1@@Z").unwrap();
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
insta::assert_debug_snapshot!(diff.instruction_rows);
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
insta::assert_snapshot!(output);
}
#[test]
#[cfg(feature = "x86")]
fn display_section_ordering() {
let diff_config = diff::DiffObjConfig::default();
let obj = obj::read::parse(include_object!("data/x86/basenode.obj"), &diff_config).unwrap();
let obj_diff =
diff::diff_objs(Some(&obj), None, None, &diff_config, &diff::MappingConfig::default())
.unwrap()
.left
.unwrap();
let section_display =
diff::display::display_sections(&obj, &obj_diff, SymbolFilter::None, false, false, false);
insta::assert_debug_snapshot!(section_display);
}
#[test]
#[cfg(feature = "x86")]
fn read_x86_jumptable() {
let diff_config = diff::DiffObjConfig::default();
let obj = obj::read::parse(include_object!("data/x86/jumptable.o"), &diff_config).unwrap();
insta::assert_debug_snapshot!(obj);
let symbol_idx = obj.symbols.iter().position(|s| s.name == "?test@@YAHH@Z").unwrap();
let diff = diff::code::no_diff_code(&obj, symbol_idx, &diff_config).unwrap();
insta::assert_debug_snapshot!(diff.instruction_rows);
let output = common::display_diff(&obj, &diff, symbol_idx, &diff_config);
insta::assert_snapshot!(output);
}
// Inferred size of functions should ignore symbols with specific prefixes
#[test]
#[cfg(feature = "x86")]
fn read_x86_local_labels() {
let diff_config = diff::DiffObjConfig::default();
let obj = obj::read::parse(include_object!("data/x86/local_labels.obj"), &diff_config).unwrap();
insta::assert_debug_snapshot!(obj);
}

View File

@ -0,0 +1,52 @@
use objdiff_core::{
diff::{DiffObjConfig, SymbolDiff, display::DiffTextSegment},
obj::Object,
};
pub fn display_diff(
obj: &Object,
diff: &SymbolDiff,
symbol_idx: usize,
diff_config: &DiffObjConfig,
) -> String {
let mut output = String::new();
for row in &diff.instruction_rows {
output.push('[');
let mut separator = false;
objdiff_core::diff::display::display_row(obj, symbol_idx, row, diff_config, |segment| {
if separator {
output.push_str(", ");
} else {
separator = true;
}
let DiffTextSegment { text, color, pad_to } = segment;
output.push_str(&format!("({text:?}, {color:?}, {pad_to:?})"));
Ok(())
})
.unwrap();
output.push_str("]\n");
}
output
}
#[repr(C)]
pub struct AlignedAs<Align, Bytes: ?Sized> {
pub _align: [Align; 0],
pub bytes: Bytes,
}
#[macro_export]
macro_rules! include_bytes_align_as {
($align_ty:ty, $path:literal) => {{
static ALIGNED: &common::AlignedAs<$align_ty, [u8]> =
&common::AlignedAs { _align: [], bytes: *include_bytes!($path) };
&ALIGNED.bytes
}};
}
#[macro_export]
macro_rules! include_object {
($path:literal) => {
include_bytes_align_as!(u64, $path)
};
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,7 @@
---
source: objdiff-core/tests/arch_arm.rs
expression: output
---
[(Line(90), Dim, 5), (Address(0), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r12")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(8), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Line(90), Dim, 5), (Address(4), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bx", 32779), Normal, 10), (Argument(Opaque("r12")), Normal, 0), (Eol, Normal, 0)]
[(Line(90), Dim, 5), (Address(8), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Symbol(Symbol { name: "esEnemyDraw", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]

View File

@ -0,0 +1,48 @@
---
source: objdiff-core/tests/arch_arm.rs
expression: diff.instruction_rows
---
[
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 76,
size: 4,
opcode: 32799,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 80,
size: 4,
opcode: 32779,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 84,
size: 4,
opcode: 65535,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,112 @@
---
source: objdiff-core/tests/arch_arm.rs
expression: output
---
[(Address(0), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stmdb", 32895), Normal, 10), (Argument(Opaque("sp")), Normal, 0), (Argument(Opaque("!")), Normal, 0), (Basic(", "), Normal, 0), (Basic("{"), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("lr")), Normal, 0), (Basic("}"), Normal, 0), (Eol, Normal, 0)]
[(Address(4), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
[(Address(8), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r1")), Normal, 0), (Eol, Normal, 0)]
[(Address(12), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateBase12OnStateLeaveEi", demangled_name: Some("LinkStateBase::OnStateLeave(int)"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
[(Address(16), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(20)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(20), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(10)), Normal, 0), (Eol, Normal, 0)]
[(Address(24), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addls", 32770), Normal, 10), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("lsl")), Normal, 0), (Basic(" #"), Normal, 0), (Argument(Unsigned(2)), Normal, 0), (Eol, Normal, 0)]
[(Address(28), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
[(Address(32), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
[(Address(36), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
[(Address(40), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
[(Address(44), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(192), Normal, 0), (Basic(" ~>"), Rotating(1), 0), (Eol, Normal, 0)]
[(Address(48), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(124), Normal, 0), (Basic(" ~>"), Rotating(2), 0), (Eol, Normal, 0)]
[(Address(52), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
[(Address(56), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(140), Normal, 0), (Basic(" ~>"), Rotating(3), 0), (Eol, Normal, 0)]
[(Address(60), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(76), Normal, 0), (Basic(" ~>"), Rotating(4), 0), (Eol, Normal, 0)]
[(Address(64), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(152), Normal, 0), (Basic(" ~>"), Rotating(5), 0), (Eol, Normal, 0)]
[(Address(68), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(164), Normal, 0), (Basic(" ~>"), Rotating(6), 0), (Eol, Normal, 0)]
[(Address(72), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(164), Normal, 0), (Basic(" ~>"), Rotating(6), 0), (Eol, Normal, 0)]
[(Address(76), Normal, 5), (Basic(" ~> "), Rotating(4), 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(336)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(420), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(80), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(84), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN18UnkStruct_027e103c19func_ov000_020cf01cEv", demangled_name: Some("UnkStruct_027e103c::func_ov000_020cf01c()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
[(Address(88), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldrb", 32800), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(224)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(92), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
[(Address(96), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 32773), Normal, 10), (BranchDest(108), Normal, 0), (Basic(" ~>"), Rotating(7), 0), (Eol, Normal, 0)]
[(Address(100), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (BranchDest(424), Normal, 0), (Basic(" ~>"), Rotating(8), 0), (Eol, Normal, 0)]
[(Address(104), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN12EquipBombchu19func_ov014_0213ec64Ev", demangled_name: Some("EquipBombchu::func_ov014_0213ec64()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
[(Address(108), Normal, 5), (Basic(" ~> "), Rotating(7), 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(308)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(424), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(112), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(116), Normal, 5), (Spacing(4), Normal, 0), (Opcode("blx", 32777), Normal, 10), (Symbol(Symbol { name: "_Z19func_ov014_0211fd04Pi", demangled_name: Some("func_ov014_0211fd04(int*)"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
[(Address(120), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
[(Address(124), Normal, 5), (Basic(" ~> "), Rotating(2), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
[(Address(128), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Eol, Normal, 0)]
[(Address(132), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateItem13StopUsingBombEi", demangled_name: Some("LinkStateItem::StopUsingBomb(int)"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
[(Address(136), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
[(Address(140), Normal, 5), (Basic(" ~> "), Rotating(3), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
[(Address(144), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateItem13StopUsingRopeEv", demangled_name: Some("LinkStateItem::StopUsingRope()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
[(Address(148), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
[(Address(152), Normal, 5), (Basic(" ~> "), Rotating(5), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
[(Address(156), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateItem15StopUsingHammerEv", demangled_name: Some("LinkStateItem::StopUsingHammer()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
[(Address(160), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
[(Address(164), Normal, 5), (Basic(" ~> "), Rotating(6), 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(248)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(420), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(168), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
[(Address(172), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(176), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r1")), Normal, 0), (Eol, Normal, 0)]
[(Address(180), Normal, 5), (Spacing(4), Normal, 0), (Opcode("strb", 32899), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(42)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(184), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN18UnkStruct_027e103c19func_ov000_020cf9dcEii", demangled_name: Some("UnkStruct_027e103c::func_ov000_020cf9dc(int, int)"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
[(Address(188), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
[(Address(192), Normal, 5), (Basic(" ~> "), Rotating(1), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
[(Address(196), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateItem14StopUsingScoopEv", demangled_name: Some("LinkStateItem::StopUsingScoop()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
[(Address(200), Normal, 5), (Basic(" ~> "), Rotating(0), 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(20)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(204), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mvn", 32829), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
[(Address(208), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
[(Address(212), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 32773), Normal, 10), (BranchDest(236), Normal, 0), (Basic(" ~>"), Rotating(9), 0), (Eol, Normal, 0)]
[(Address(216), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
[(Address(220), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateBase12GetEquipItemEi", demangled_name: Some("LinkStateBase::GetEquipItem(int)"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
[(Address(224), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(228), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(28)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(232), Normal, 5), (Spacing(4), Normal, 0), (Opcode("blx", 32778), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Eol, Normal, 0)]
[(Address(236), Normal, 5), (Basic(" ~> "), Rotating(9), 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(20)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(240), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(9)), Normal, 0), (Eol, Normal, 0)]
[(Address(244), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bgt", 32773), Normal, 10), (BranchDest(288), Normal, 0), (Basic(" ~>"), Rotating(10), 0), (Eol, Normal, 0)]
[(Address(248), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bge", 32773), Normal, 10), (BranchDest(296), Normal, 0), (Basic(" ~>"), Rotating(11), 0), (Eol, Normal, 0)]
[(Address(252), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(1)), Normal, 0), (Eol, Normal, 0)]
[(Address(256), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bgt", 32773), Normal, 10), (BranchDest(308), Normal, 0), (Basic(" ~>"), Rotating(12), 0), (Eol, Normal, 0)]
[(Address(260), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mvn", 32829), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
[(Address(264), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
[(Address(268), Normal, 5), (Spacing(4), Normal, 0), (Opcode("blt", 32773), Normal, 10), (BranchDest(308), Normal, 0), (Basic(" ~>"), Rotating(12), 0), (Eol, Normal, 0)]
[(Address(272), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmpne", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
[(Address(276), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmpne", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(1)), Normal, 0), (Eol, Normal, 0)]
[(Address(280), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 32773), Normal, 10), (BranchDest(340), Normal, 0), (Basic(" ~>"), Rotating(13), 0), (Eol, Normal, 0)]
[(Address(284), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(308), Normal, 0), (Basic(" ~>"), Rotating(12), 0), (Eol, Normal, 0)]
[(Address(288), Normal, 5), (Basic(" ~> "), Rotating(10), 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(10)), Normal, 0), (Eol, Normal, 0)]
[(Address(292), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 32773), Normal, 10), (BranchDest(308), Normal, 0), (Basic(" ~>"), Rotating(12), 0), (Eol, Normal, 0)]
[(Address(296), Normal, 5), (Basic(" ~> "), Rotating(11), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
[(Address(300), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateBase18EquipItem_vfunc_28Ev", demangled_name: Some("LinkStateBase::EquipItem_vfunc_28()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
[(Address(304), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 32773), Normal, 10), (BranchDest(340), Normal, 0), (Basic(" ~>"), Rotating(13), 0), (Eol, Normal, 0)]
[(Address(308), Normal, 5), (Basic(" ~> "), Rotating(12), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
[(Address(312), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateBase18EquipItem_vfunc_28Ev", demangled_name: Some("LinkStateBase::EquipItem_vfunc_28()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
[(Address(316), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(4)), Normal, 0), (Eol, Normal, 0)]
[(Address(320), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmpne", 32786), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(2)), Normal, 0), (Eol, Normal, 0)]
[(Address(324), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 32773), Normal, 10), (BranchDest(340), Normal, 0), (Basic(" ~>"), Rotating(13), 0), (Eol, Normal, 0)]
[(Address(328), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13LinkStateItem16GetLinkStateMoveEv", demangled_name: Some("LinkStateItem::GetLinkStateMove()"), address: 488, size: 16, kind: Function, section: Some(0), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
[(Address(332), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(1)), Normal, 0), (Eol, Normal, 0)]
[(Address(336), Normal, 5), (Spacing(4), Normal, 0), (Opcode("strb", 32899), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(20)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(340), Normal, 5), (Basic(" ~> "), Rotating(13), 0), (Opcode("mvn", 32829), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
[(Address(344), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 32770), Normal, 10), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(80)), Normal, 0), (Eol, Normal, 0)]
[(Address(348), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 32770), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(88)), Normal, 0), (Eol, Normal, 0)]
[(Address(352), Normal, 5), (Spacing(4), Normal, 0), (Opcode("str", 32898), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(24)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(356), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Eol, Normal, 0)]
[(Address(360), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 32773), Normal, 10), (BranchDest(384), Normal, 0), (Basic(" ~>"), Rotating(14), 0), (Eol, Normal, 0)]
[(Address(364), Normal, 5), (Basic(" ~> "), Rotating(15), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Eol, Normal, 0)]
[(Address(368), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_Z19func_ov000_020b7e6cPi", demangled_name: Some("func_ov000_020b7e6c(int*)"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
[(Address(372), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 32770), Normal, 10), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(4)), Normal, 0), (Eol, Normal, 0)]
[(Address(376), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Eol, Normal, 0)]
[(Address(380), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 32773), Normal, 10), (BranchDest(364), Normal, 0), (Basic(" ~>"), Rotating(15), 0), (Eol, Normal, 0)]
[(Address(384), Normal, 5), (Basic(" ~> "), Rotating(14), 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(36)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(428), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(388), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32799), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(392), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldrb", 32800), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(128)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(396), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 32786), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
[(Address(400), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 32773), Normal, 10), (BranchDest(408), Normal, 0), (Basic(" ~>"), Rotating(16), 0), (Eol, Normal, 0)]
[(Address(404), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 32774), Normal, 10), (Symbol(Symbol { name: "_ZN13PlayerControl13StopFollowingEv", demangled_name: Some("PlayerControl::StopFollowing()"), address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 0), (Eol, Normal, 0)]
[(Address(408), Normal, 5), (Basic(" ~> "), Rotating(16), 0), (Opcode("mov", 32818), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
[(Address(412), Normal, 5), (Spacing(4), Normal, 0), (Opcode("strb", 32899), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(38)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(416), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldmia", 32793), Normal, 10), (Argument(Opaque("sp")), Normal, 0), (Argument(Opaque("!")), Normal, 0), (Basic(", "), Normal, 0), (Basic("{"), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic("}"), Normal, 0), (Eol, Normal, 0)]
[(Address(420), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Symbol(Symbol { name: "data_027e103c", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
[(Address(424), Normal, 5), (Basic(" ~> "), Rotating(8), 0), (Opcode(".word", 65535), Normal, 10), (Symbol(Symbol { name: "data_027e1098", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
[(Address(428), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Symbol(Symbol { name: "gPlayerControl", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global | Weak), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,111 @@
---
source: objdiff-core/tests/arch_arm.rs
assertion_line: 33
expression: output
---
[(Line(37), Dim, 5), (Address(0), Normal, 5), (Spacing(4), Normal, 0), (Opcode("push", 56), Normal, 10), (Basic("{"), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r7")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("lr")), Normal, 0), (Basic("}"), Normal, 0), (Eol, Normal, 0)]
[(Line(37), Dim, 5), (Address(2), Normal, 5), (Spacing(4), Normal, 0), (Opcode("sub", 74), Normal, 10), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(16)), Normal, 0), (Eol, Normal, 0)]
[(Line(37), Dim, 5), (Address(4), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
[(Line(39), Dim, 5), (Address(6), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Eol, Normal, 0)]
[(Line(39), Dim, 5), (Address(8), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r1")), Normal, 0), (Eol, Normal, 0)]
[(Line(39), Dim, 5), (Address(10), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r2")), Normal, 0), (Eol, Normal, 0)]
[(Line(39), Dim, 5), (Address(12), Normal, 5), (Spacing(4), Normal, 0), (Opcode("str", 66), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(4)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Line(39), Dim, 5), (Address(14), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 19), Normal, 10), (Symbol(Symbol { name: "PokeSet_IsRemovedAll", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-4), Bright, 0), (Eol, Normal, 0)]
[(Line(39), Dim, 5), (Address(18), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 25), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
[(Line(39), Dim, 5), (Address(20), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 15), Normal, 10), (BranchDest(212), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
[(Line(44), Dim, 5), (Address(22), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldrh", 38), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Line(44), Dim, 5), (Address(24), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 25), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(164)), Normal, 0), (Eol, Normal, 0)]
[(Line(44), Dim, 5), (Address(26), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 15), Normal, 10), (BranchDest(48), Normal, 0), (Basic(" ~>"), Rotating(1), 0), (Eol, Normal, 0)]
[(Line(44), Dim, 5), (Address(28), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 34), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(184)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(216), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Line(44), Dim, 5), (Address(30), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 26), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
[(Line(44), Dim, 5), (Address(32), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 15), Normal, 10), (BranchDest(94), Normal, 0), (Basic(" ~>"), Rotating(2), 0), (Eol, Normal, 0)]
[(Line(47), Dim, 5), (Address(34), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 35), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(4)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Line(47), Dim, 5), (Address(36), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
[(Line(47), Dim, 5), (Address(38), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Eol, Normal, 0)]
[(Line(47), Dim, 5), (Address(40), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 19), Normal, 10), (Symbol(Symbol { name: "ServerDisplay_SkillSwap", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-4), Bright, 0), (Eol, Normal, 0)]
[(Line(86), Dim, 5), (Address(44), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 7), Normal, 10), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(16)), Normal, 0), (Eol, Normal, 0)]
[(Line(86), Dim, 5), (Address(46), Normal, 5), (Spacing(4), Normal, 0), (Opcode("pop", 55), Normal, 10), (Basic("{"), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r7")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic("}"), Normal, 0), (Eol, Normal, 0)]
[(Line(50), Dim, 5), (Address(48), Normal, 5), (Basic(" ~> "), Rotating(1), 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
[(Line(50), Dim, 5), (Address(50), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Eol, Normal, 0)]
[(Line(50), Dim, 5), (Address(52), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 19), Normal, 10), (Symbol(Symbol { name: "ServerEvent_CreateSubstitute", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-4), Bright, 0), (Eol, Normal, 0)]
[(Line(50), Dim, 5), (Address(56), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 25), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
[(Line(50), Dim, 5), (Address(58), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 15), Normal, 10), (BranchDest(212), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
[(Line(52), Dim, 5), (Address(60), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 34), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(156)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(220), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Line(52), Dim, 5), (Address(62), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 33), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Line(52), Dim, 5), (Address(64), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldrb", 36), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(5)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Line(52), Dim, 5), (Address(66), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lsl", 42), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(31)), Normal, 0), (Eol, Normal, 0)]
[(Line(52), Dim, 5), (Address(68), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lsr", 44), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(31)), Normal, 0), (Eol, Normal, 0)]
[(Line(52), Dim, 5), (Address(70), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 15), Normal, 10), (BranchDest(212), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
[(Line(52), Dim, 5), (Address(72), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 46), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(1)), Normal, 0), (Eol, Normal, 0)]
[(Line(52), Dim, 5), (Address(74), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bic", 17), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r1")), Normal, 0), (Eol, Normal, 0)]
[(Line(52), Dim, 5), (Address(76), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 46), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(1)), Normal, 0), (Eol, Normal, 0)]
[(Line(52), Dim, 5), (Address(78), Normal, 5), (Spacing(4), Normal, 0), (Opcode("orr", 54), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r2")), Normal, 0), (Eol, Normal, 0)]
[(Line(52), Dim, 5), (Address(80), Normal, 5), (Spacing(4), Normal, 0), (Opcode("strb", 67), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(5)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Line(52), Dim, 5), (Address(82), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldrb", 36), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(5)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Line(52), Dim, 5), (Address(84), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 46), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(2)), Normal, 0), (Eol, Normal, 0)]
[(Line(86), Dim, 5), (Address(86), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 7), Normal, 10), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(16)), Normal, 0), (Eol, Normal, 0)]
[(Line(52), Dim, 5), (Address(88), Normal, 5), (Spacing(4), Normal, 0), (Opcode("orr", 54), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r2")), Normal, 0), (Eol, Normal, 0)]
[(Line(52), Dim, 5), (Address(90), Normal, 5), (Spacing(4), Normal, 0), (Opcode("strb", 67), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(5)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Line(86), Dim, 5), (Address(92), Normal, 5), (Spacing(4), Normal, 0), (Opcode("pop", 55), Normal, 10), (Basic("{"), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r7")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic("}"), Normal, 0), (Eol, Normal, 0)]
[(Line(57), Dim, 5), (Address(94), Normal, 5), (Basic(" ~> "), Rotating(2), 0), (Opcode("ldr", 34), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(128)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(224), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Line(57), Dim, 5), (Address(96), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 34), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(128)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(228), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Line(57), Dim, 5), (Address(98), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 4), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
[(Line(57), Dim, 5), (Address(100), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 19), Normal, 10), (Symbol(Symbol { name: "HEManager_PushState", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-4), Bright, 0), (Eol, Normal, 0)]
[(Line(57), Dim, 5), (Address(104), Normal, 5), (Spacing(4), Normal, 0), (Opcode("str", 66), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(8)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Line(61), Dim, 5), (Address(106), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
[(Line(61), Dim, 5), (Address(108), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 19), Normal, 10), (Symbol(Symbol { name: "BattleHandler_Result", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-4), Bright, 0), (Eol, Normal, 0)]
[(Line(61), Dim, 5), (Address(112), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r7")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
[(Line(63), Dim, 5), (Address(114), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 6), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(12)), Normal, 0), (Eol, Normal, 0)]
[(Line(63), Dim, 5), (Address(116), Normal, 5), (Spacing(4), Normal, 0), (Opcode("str", 66), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Line(63), Dim, 5), (Address(118), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 35), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(4)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Line(63), Dim, 5), (Address(120), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
[(Line(63), Dim, 5), (Address(122), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Eol, Normal, 0)]
[(Line(63), Dim, 5), (Address(124), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Eol, Normal, 0)]
[(Line(63), Dim, 5), (Address(126), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 19), Normal, 10), (Symbol(Symbol { name: "ServerEvent_UncategorizedMove", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-4), Bright, 0), (Eol, Normal, 0)]
[(Line(63), Dim, 5), (Address(130), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 25), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
[(Line(63), Dim, 5), (Address(132), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 15), Normal, 10), (BranchDest(168), Normal, 0), (Basic(" ~>"), Rotating(3), 0), (Eol, Normal, 0)]
[(Line(65), Dim, 5), (Address(134), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Eol, Normal, 0)]
[(Line(65), Dim, 5), (Address(136), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 19), Normal, 10), (Symbol(Symbol { name: "BattleHandler_Result", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-4), Bright, 0), (Eol, Normal, 0)]
[(Line(65), Dim, 5), (Address(140), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 47), Normal, 10), (Argument(Opaque("r7")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
[(Line(66), Dim, 5), (Address(142), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 25), Normal, 10), (Argument(Opaque("r7")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(2)), Normal, 0), (Eol, Normal, 0)]
[(Line(66), Dim, 5), (Address(144), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 15), Normal, 10), (BranchDest(168), Normal, 0), (Basic(" ~>"), Rotating(3), 0), (Eol, Normal, 0)]
[(Line(68), Dim, 5), (Address(146), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 34), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(72)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(220), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Line(68), Dim, 5), (Address(148), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 33), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Line(68), Dim, 5), (Address(150), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldrb", 36), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(5)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Line(68), Dim, 5), (Address(152), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lsl", 42), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(31)), Normal, 0), (Eol, Normal, 0)]
[(Line(68), Dim, 5), (Address(154), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lsr", 44), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(31)), Normal, 0), (Eol, Normal, 0)]
[(Line(68), Dim, 5), (Address(156), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 15), Normal, 10), (BranchDest(168), Normal, 0), (Basic(" ~>"), Rotating(3), 0), (Eol, Normal, 0)]
[(Line(68), Dim, 5), (Address(158), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 46), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(1)), Normal, 0), (Eol, Normal, 0)]
[(Line(68), Dim, 5), (Address(160), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bic", 17), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r1")), Normal, 0), (Eol, Normal, 0)]
[(Line(68), Dim, 5), (Address(162), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 46), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(1)), Normal, 0), (Eol, Normal, 0)]
[(Line(68), Dim, 5), (Address(164), Normal, 5), (Spacing(4), Normal, 0), (Opcode("orr", 54), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r2")), Normal, 0), (Eol, Normal, 0)]
[(Line(68), Dim, 5), (Address(166), Normal, 5), (Spacing(4), Normal, 0), (Opcode("strb", 67), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(5)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Line(72), Dim, 5), (Address(168), Normal, 5), (Basic(" ~> "), Rotating(3), 0), (Opcode("cmp", 25), Normal, 10), (Argument(Opaque("r7")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(1)), Normal, 0), (Eol, Normal, 0)]
[(Line(72), Dim, 5), (Address(170), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bhi", 15), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(4), 0), (Eol, Normal, 0)]
[(Line(75), Dim, 5), (Address(172), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 35), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(12)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Line(75), Dim, 5), (Address(174), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmp", 25), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(0)), Normal, 0), (Eol, Normal, 0)]
[(Line(75), Dim, 5), (Address(176), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 15), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(4), 0), (Eol, Normal, 0)]
[(Line(75), Dim, 5), (Address(178), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 34), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(52)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(232), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Line(75), Dim, 5), (Address(180), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldrb", 37), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Line(75), Dim, 5), (Address(182), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lsl", 42), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(27)), Normal, 0), (Eol, Normal, 0)]
[(Line(75), Dim, 5), (Address(184), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lsr", 44), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(31)), Normal, 0), (Eol, Normal, 0)]
[(Line(75), Dim, 5), (Address(186), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 15), Normal, 10), (BranchDest(200), Normal, 0), (Basic(" ~>"), Rotating(4), 0), (Eol, Normal, 0)]
[(Line(77), Dim, 5), (Address(188), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 32), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(12)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Line(77), Dim, 5), (Address(190), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 34), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(44)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(236), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Line(77), Dim, 5), (Address(192), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 46), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(90)), Normal, 0), (Eol, Normal, 0)]
[(Line(77), Dim, 5), (Address(194), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 46), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Unsigned(71)), Normal, 0), (Eol, Normal, 0)]
[(Line(77), Dim, 5), (Address(196), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 19), Normal, 10), (Symbol(Symbol { name: "SCQUE_PUT_MsgImpl", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-4), Bright, 0), (Eol, Normal, 0)]
[(Line(81), Dim, 5), (Address(200), Normal, 5), (Basic(" ~> "), Rotating(4), 0), (Opcode("ldr", 34), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(20)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(224), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Line(81), Dim, 5), (Address(202), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 35), Normal, 10), (Argument(Opaque("r1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(8)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Line(81), Dim, 5), (Address(204), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ldr", 34), Normal, 10), (Argument(Opaque("r2")), Normal, 0), (Basic(", "), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(32)), Normal, 0), (Basic("]"), Normal, 0), (Basic(" (->"), Normal, 0), (BranchDest(240), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Line(81), Dim, 5), (Address(206), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 4), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Eol, Normal, 0)]
[(Line(81), Dim, 5), (Address(208), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bl", 19), Normal, 10), (Symbol(Symbol { name: "HEManager_PopState", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-4), Bright, 0), (Eol, Normal, 0)]
[(Line(86), Dim, 5), (Address(212), Normal, 5), (Basic(" ~> "), Rotating(0), 0), (Opcode("add", 7), Normal, 10), (Argument(Opaque("sp")), Normal, 0), (Basic(", "), Normal, 0), (Basic("#"), Normal, 0), (Argument(Signed(16)), Normal, 0), (Eol, Normal, 0)]
[(Line(86), Dim, 5), (Address(214), Normal, 5), (Spacing(4), Normal, 0), (Opcode("pop", 55), Normal, 10), (Basic("{"), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r6")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r7")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("pc")), Normal, 0), (Basic("}"), Normal, 0), (Eol, Normal, 0)]
[(Line(86), Dim, 5), (Address(216), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Basic("#"), Normal, 0), (Argument(Unsigned(285)), Normal, 0), (Eol, Normal, 0)]
[(Line(86), Dim, 5), (Address(220), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Basic("#"), Normal, 0), (Argument(Unsigned(1192)), Normal, 0), (Eol, Normal, 0)]
[(Line(86), Dim, 5), (Address(224), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Basic("#"), Normal, 0), (Argument(Unsigned(7544)), Normal, 0), (Eol, Normal, 0)]
[(Line(86), Dim, 5), (Address(228), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Basic("#"), Normal, 0), (Argument(Unsigned(9103)), Normal, 0), (Eol, Normal, 0)]
[(Line(86), Dim, 5), (Address(232), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Basic("#"), Normal, 0), (Argument(Unsigned(1930)), Normal, 0), (Eol, Normal, 0)]
[(Line(86), Dim, 5), (Address(236), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Basic("#"), Normal, 0), (Argument(Unsigned(4294901760)), Normal, 0), (Eol, Normal, 0)]
[(Line(86), Dim, 5), (Address(240), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".word", 65535), Normal, 10), (Basic("#"), Normal, 0), (Argument(Unsigned(9129)), Normal, 0), (Eol, Normal, 0)]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,452 @@
---
source: objdiff-core/tests/arch_mips.rs
expression: obj.symbols
---
[
Symbol {
name: "build/src/bodyprog/view/vw_main.i",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.text]",
demangled_name: None,
address: 0,
size: 0,
kind: Section,
section: Some(
0,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.data]",
demangled_name: None,
address: 0,
size: 0,
kind: Section,
section: Some(
2,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.bss]",
demangled_name: None,
address: 0,
size: 0,
kind: Section,
section: Some(
3,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "gcc2_compiled.",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: Some(
0,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "__gnu_compiled_c",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: Some(
0,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: ".endfunc_80048AF4",
demangled_name: None,
address: 424,
size: 0,
kind: Unknown,
section: Some(
0,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: ".endfunc_80048DA8",
demangled_name: None,
address: 1028,
size: 0,
kind: Unknown,
section: Some(
0,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: ".endfunc_80048E3C",
demangled_name: None,
address: 1264,
size: 0,
kind: Unknown,
section: Some(
0,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.reginfo]",
demangled_name: None,
address: 0,
size: 24,
kind: Section,
section: Some(
4,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.MIPS.abiflags]",
demangled_name: None,
address: 0,
size: 24,
kind: Section,
section: Some(
5,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.pdr]",
demangled_name: None,
address: 0,
size: 320,
kind: Section,
section: Some(
6,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.gnu.attributes]",
demangled_name: None,
address: 0,
size: 16,
kind: Section,
section: Some(
8,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "vwInitViewInfo",
demangled_name: None,
address: 0,
size: 88,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Global),
align: None,
virtual_address: None,
},
Symbol {
name: "vwViewPointInfo",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Global),
align: None,
virtual_address: None,
},
Symbol {
name: "GsInitCoordinate2",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Global),
align: None,
virtual_address: None,
},
Symbol {
name: "vwSetViewInfo",
demangled_name: None,
address: 784,
size: 96,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Global),
align: None,
virtual_address: None,
},
Symbol {
name: "vwGetViewCoord",
demangled_name: None,
address: 88,
size: 12,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Global),
align: None,
virtual_address: None,
},
Symbol {
name: "vwGetViewPosition",
demangled_name: None,
address: 100,
size: 40,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Global),
align: None,
virtual_address: None,
},
Symbol {
name: "vwGetViewAngle",
demangled_name: None,
address: 140,
size: 48,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Global),
align: None,
virtual_address: None,
},
Symbol {
name: "func_80048AF4",
demangled_name: None,
address: 188,
size: 236,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Global | Ignored),
align: None,
virtual_address: None,
},
Symbol {
name: "ratan2",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Global),
align: None,
virtual_address: None,
},
Symbol {
name: "SquareRoot0",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Global),
align: None,
virtual_address: None,
},
Symbol {
name: "func_80096C94",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Global),
align: None,
virtual_address: None,
},
Symbol {
name: "vwSetViewInfoDirectMatrix",
demangled_name: None,
address: 696,
size: 88,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Global),
align: None,
virtual_address: None,
},
Symbol {
name: "func_80048AF4.NON_MATCHING",
demangled_name: None,
address: 188,
size: 236,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Global | Ignored),
align: None,
virtual_address: None,
},
Symbol {
name: "vwSetCoordRefAndEntou",
demangled_name: None,
address: 424,
size: 272,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Global),
align: None,
virtual_address: None,
},
Symbol {
name: "func_80096E78",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Global),
align: None,
virtual_address: None,
},
Symbol {
name: "shRsin",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Global),
align: None,
virtual_address: None,
},
Symbol {
name: "shRcos",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Global),
align: None,
virtual_address: None,
},
Symbol {
name: "vbSetRefView",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Global),
align: None,
virtual_address: None,
},
Symbol {
name: "vwMatrixToAngleYXZ",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Global),
align: None,
virtual_address: None,
},
Symbol {
name: "func_80048DA8",
demangled_name: None,
address: 880,
size: 148,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Global | Ignored),
align: None,
virtual_address: None,
},
Symbol {
name: "func_80048DA8.NON_MATCHING",
demangled_name: None,
address: 880,
size: 148,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Global | Ignored),
align: None,
virtual_address: None,
},
Symbol {
name: "func_80048E3C",
demangled_name: None,
address: 1028,
size: 236,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Global | Ignored),
align: None,
virtual_address: None,
},
Symbol {
name: "func_80048E3C.NON_MATCHING",
demangled_name: None,
address: 1028,
size: 236,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Global | Ignored),
align: None,
virtual_address: None,
},
]

View File

@ -0,0 +1,694 @@
---
source: objdiff-core/tests/arch_mips.rs
expression: diff.instruction_rows
---
[
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 0,
size: 4,
opcode: 12,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 4,
size: 4,
opcode: 44,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 8,
size: 4,
opcode: 44,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 12,
size: 4,
opcode: 44,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 16,
size: 4,
opcode: 44,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 20,
size: 4,
opcode: 2,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 24,
size: 4,
opcode: 113,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 28,
size: 4,
opcode: 26,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 32,
size: 4,
opcode: 20,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 36,
size: 4,
opcode: 97,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 40,
size: 4,
opcode: 2,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 44,
size: 4,
opcode: 12,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 48,
size: 4,
opcode: 20,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 52,
size: 4,
opcode: 26,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 56,
size: 4,
opcode: 2,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 60,
size: 4,
opcode: 12,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 64,
size: 4,
opcode: 26,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 68,
size: 4,
opcode: 97,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 72,
size: 4,
opcode: 2,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 76,
size: 4,
opcode: 97,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 80,
size: 4,
opcode: 2,
branch_dest: None,
},
),
kind: None,
branch_from: Some(
InstructionBranchFrom {
ins_idx: [
22,
],
branch_idx: 0,
},
),
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 84,
size: 4,
opcode: 97,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 88,
size: 4,
opcode: 56,
branch_dest: Some(
80,
),
},
),
kind: None,
branch_from: None,
branch_to: Some(
InstructionBranchTo {
ins_idx: 20,
branch_idx: 0,
},
),
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 92,
size: 4,
opcode: 113,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 96,
size: 4,
opcode: 2,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 100,
size: 4,
opcode: 20,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 104,
size: 4,
opcode: 2,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 108,
size: 4,
opcode: 12,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 112,
size: 4,
opcode: 2,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 116,
size: 4,
opcode: 16,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 120,
size: 4,
opcode: 20,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 124,
size: 4,
opcode: 12,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 128,
size: 4,
opcode: 2,
branch_dest: None,
},
),
kind: None,
branch_from: Some(
InstructionBranchFrom {
ins_idx: [
36,
38,
44,
],
branch_idx: 1,
},
),
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 132,
size: 4,
opcode: 12,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 136,
size: 4,
opcode: 2,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 140,
size: 4,
opcode: 113,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 144,
size: 4,
opcode: 55,
branch_dest: Some(
128,
),
},
),
kind: None,
branch_from: None,
branch_to: Some(
InstructionBranchTo {
ins_idx: 32,
branch_idx: 1,
},
),
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 148,
size: 4,
opcode: 90,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 152,
size: 4,
opcode: 3,
branch_dest: Some(
128,
),
},
),
kind: None,
branch_from: None,
branch_to: Some(
InstructionBranchTo {
ins_idx: 32,
branch_idx: 1,
},
),
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 156,
size: 4,
opcode: 113,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 160,
size: 4,
opcode: 2,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 164,
size: 4,
opcode: 60,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 168,
size: 4,
opcode: 77,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 172,
size: 4,
opcode: 113,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 176,
size: 4,
opcode: 54,
branch_dest: Some(
128,
),
},
),
kind: None,
branch_from: None,
branch_to: Some(
InstructionBranchTo {
ins_idx: 32,
branch_idx: 1,
},
),
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 180,
size: 4,
opcode: 113,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
]

View File

@ -0,0 +1,50 @@
---
source: objdiff-core/tests/arch_mips.rs
expression: output
---
[(Address(0), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$sp")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$sp")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(-32)), Normal, 0), (Eol, Normal, 0)]
[(Address(4), Normal, 5), (Spacing(4), Normal, 0), (Opcode("sd", 44), Normal, 10), (Argument(Opaque("$s0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$sp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(8), Normal, 5), (Spacing(4), Normal, 0), (Opcode("sd", 44), Normal, 10), (Argument(Opaque("$s1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(8)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$sp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(12), Normal, 5), (Spacing(4), Normal, 0), (Opcode("sd", 44), Normal, 10), (Argument(Opaque("$s2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(16)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$sp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(16), Normal, 5), (Spacing(4), Normal, 0), (Opcode("sd", 44), Normal, 10), (Argument(Opaque("$ra")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(24)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$sp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(20), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglSleep", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
[(Address(24), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]
[(Address(28), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lw", 26), Normal, 10), (Argument(Opaque("$a1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%gp_rel("), Normal, 0), (Symbol(Symbol { name: "WorkEnd", demangled_name: None, address: 64, size: 4, kind: Object, section: Some(8), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$gp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(32), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lui", 20), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%hi("), Normal, 0), (Symbol(Symbol { name: "[.sdata]", demangled_name: None, address: 0, size: 64, kind: Section, section: Some(8), flags: FlagSet(Local), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(36), Normal, 5), (Spacing(4), Normal, 0), (Opcode("daddu", 97), Normal, 10), (Argument(Opaque("$a2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Eol, Normal, 0)]
[(Address(40), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglSoundLoadEffect", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
[(Address(44), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%lo("), Normal, 0), (Symbol(Symbol { name: "[.sdata]", demangled_name: None, address: 0, size: 64, kind: Section, section: Some(8), flags: FlagSet(Local), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(48), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lui", 20), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%hi("), Normal, 0), (Symbol(Symbol { name: "PacketBottomNewVu1DropMicroCode", demangled_name: None, address: 0, size: 12, kind: Object, section: Some(7), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(52), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lw", 26), Normal, 10), (Argument(Opaque("$a1")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%gp_rel("), Normal, 0), (Symbol(Symbol { name: "WorkEnd", demangled_name: None, address: 64, size: 4, kind: Object, section: Some(8), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$gp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(56), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglSoundLoadSwd", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
[(Address(60), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%lo("), Normal, 0), (Symbol(Symbol { name: "PacketBottomNewVu1DropMicroCode", demangled_name: None, address: 0, size: 12, kind: Object, section: Some(7), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(64), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lw", 26), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%gp_rel("), Normal, 0), (Symbol(Symbol { name: "WorkEnd", demangled_name: None, address: 64, size: 4, kind: Object, section: Some(8), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("$gp")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(68), Normal, 5), (Spacing(4), Normal, 0), (Opcode("daddu", 97), Normal, 10), (Argument(Opaque("$a1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Eol, Normal, 0)]
[(Address(72), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "SsdAddWaveData", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
[(Address(76), Normal, 5), (Spacing(4), Normal, 0), (Opcode("daddu", 97), Normal, 10), (Argument(Opaque("$a2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Eol, Normal, 0)]
[(Address(80), Normal, 5), (Basic(" ~> "), Rotating(0), 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "SsdSpuDmaCompleted", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
[(Address(84), Normal, 5), (Spacing(4), Normal, 0), (Opcode("daddu", 97), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Eol, Normal, 0)]
[(Address(88), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bnez", 56), Normal, 10), (Argument(Opaque("$v0")), Normal, 0), (Basic(", "), Normal, 0), (BranchDest(80), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
[(Address(92), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]
[(Address(96), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglRenderDispOn", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
[(Address(100), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lui", 20), Normal, 10), (Argument(Opaque("$s1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Unsigned(255)), Normal, 0), (Eol, Normal, 0)]
[(Address(104), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglCdLoadOverlay", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
[(Address(108), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(2)), Normal, 0), (Eol, Normal, 0)]
[(Address(112), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "LogoFirst", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
[(Address(116), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ori", 16), Normal, 10), (Argument(Opaque("$s1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$s1")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Unsigned(65535)), Normal, 0), (Eol, Normal, 0)]
[(Address(120), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lui", 20), Normal, 10), (Argument(Opaque("$v0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%hi("), Normal, 0), (Symbol(Symbol { name: "Title", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(124), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$s2")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$v0")), Normal, 0), (Basic(", "), Normal, 0), (Basic("%lo("), Normal, 0), (Symbol(Symbol { name: "Title", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(128), Normal, 5), (Basic(" ~> "), Rotating(1), 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglCdLoadOverlay", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
[(Address(132), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addiu", 12), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$zero")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(2)), Normal, 0), (Eol, Normal, 0)]
[(Address(136), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "Title", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
[(Address(140), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]
[(Address(144), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beqz", 55), Normal, 10), (Argument(Opaque("$v0")), Normal, 0), (Basic(", "), Normal, 0), (BranchDest(128), Normal, 0), (Basic(" ~>"), Rotating(1), 0), (Eol, Normal, 0)]
[(Address(148), Normal, 5), (Spacing(4), Normal, 0), (Opcode("and", 90), Normal, 10), (Argument(Opaque("$s0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$v0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$s1")), Normal, 0), (Eol, Normal, 0)]
[(Address(152), Normal, 5), (Spacing(4), Normal, 0), (Opcode("beq", 3), Normal, 10), (Argument(Opaque("$s0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$s2")), Normal, 0), (Basic(", "), Normal, 0), (BranchDest(128), Normal, 0), (Basic(" ~>"), Rotating(1), 0), (Eol, Normal, 0)]
[(Address(156), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]
[(Address(160), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jal", 2), Normal, 10), (Symbol(Symbol { name: "xglCdLoadOverlay", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
[(Address(164), Normal, 5), (Spacing(4), Normal, 0), (Opcode("srl", 60), Normal, 10), (Argument(Opaque("$a0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("$v0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("24")), Normal, 0), (Eol, Normal, 0)]
[(Address(168), Normal, 5), (Spacing(4), Normal, 0), (Opcode("jalr", 77), Normal, 10), (Argument(Opaque("$s0")), Normal, 0), (Eol, Normal, 0)]
[(Address(172), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]
[(Address(176), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 54), Normal, 10), (BranchDest(128), Normal, 0), (Basic(" ~>"), Rotating(1), 0), (Eol, Normal, 0)]
[(Address(180), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 113), Normal, 10), (Eol, Normal, 0)]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,87 @@
---
source: objdiff-core/tests/arch_ppc.rs
assertion_line: 70
expression: sections_display
---
[
SectionDisplay {
id: ".comm",
name: ".comm",
size: 0,
match_percent: None,
symbols: [
SectionDisplaySymbol {
symbol: 11,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 12,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 13,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 14,
is_mapping_symbol: false,
},
],
},
SectionDisplay {
id: ".ctors-0",
name: ".ctors",
size: 4,
match_percent: Some(
100.0,
),
symbols: [
SectionDisplaySymbol {
symbol: 2,
is_mapping_symbol: false,
},
],
},
SectionDisplay {
id: ".text-0",
name: ".text",
size: 3060,
match_percent: Some(
58.662746,
),
symbols: [
SectionDisplaySymbol {
symbol: 3,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 10,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 9,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 8,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 7,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 6,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 5,
is_mapping_symbol: false,
},
SectionDisplaySymbol {
symbol: 4,
is_mapping_symbol: false,
},
],
},
]

View File

@ -0,0 +1,40 @@
---
source: objdiff-core/tests/arch_ppc.rs
expression: line_infos
---
[
{
0: 13,
4: 16,
32: 17,
44: 18,
60: 20,
76: 21,
84: 23,
92: 25,
108: 26,
124: 27,
136: 28,
144: 29,
152: 31,
164: 34,
184: 35,
212: 39,
228: 40,
236: 41,
260: 43,
288: 44,
292: 45,
300: 48,
436: 0,
},
{
0: 48,
132: 35,
244: 26,
304: 22,
312: 23,
316: 24,
320: 0,
},
]

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