Compare commits

..

57 Commits

Author SHA1 Message Date
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
LagoLunatic
a4fdb61f04 Implement diffing relocations within data sections (#154)
* Data view: Show data bytes with differing relocations as a diff

* Data view: Show differing relocations on hover

* Symbol list view: Adjust symbol/section match %s when relocations differ

* Improve data reloc diffing logic

* Don't make reloc diffs cause bytes to show as red or green

* Properly detect byte size of each relocation

* Data view: Add context menu for copying relocation target symbols

* Also show already-matching relocations on hover/right click

* Change font color for nonmatching relocs on hover
2025-01-18 16:20:07 -07:00
LagoLunatic
2876be37a3 Show relocation diffs in function view when the data's content differs (#153)
* Show reloc diff in func view when data content differs

* Add "Relax shifted data diffs" option

* Display fake pool relocations at end of line

* Diff reloc data by display string instead of raw bytes

This is to handle data symbols that contain multiple values in them at once, such as stringBase. If you compare the target symbol's bytes directly, then any part of the symbol having different bytes will cause *all* relocations to that symbol to show as a diff, even if the specific string being accessed is the same.

* Fix weak stripped symbols showing as a false diff

Fixed this by showing extern symbols correctly instead of skipping them.

* Add "Relax shifted data diffs" option to objdiff-cli

Includes both a command line argument and a keyboard shortcut (S).

* Remove addi string data hack and ... pool name hack

* Clippy fix

* PPC: Clear relocs from GPRs when overwritten

* PPC: Follow branches to improve pool detection accuracy

* PPC: Handle following bctr jump table control flow

* Clippy fixes

* PPC: Fix extern relocations not having their addend copied

* Add option to disable func data value diffing

* PPC: Handle lmw when clearing GPRs

* PPC: Handle moving reloc address with `add` inst

* Combine "relax reloc diffs" with other reloc diff options

* Add v3 config and migrate from v2

---------

Co-authored-by: Luke Street <luke@street.dev>
2025-01-18 16:18:05 -07:00
11171763eb Use cargo-deny-action@v2 2025-01-18 16:16:12 -07:00
6037a79ba2 Update all dependencies 2025-01-18 15:58:38 -07:00
f7efe5fdff cargo update 2025-01-04 21:29:29 -07:00
0692deac59 Use ObjInsArgValue::loose_eq in arg_eq 2025-01-04 21:02:54 -07:00
c3e3d175c5 Create schema for diff config properties 2025-01-04 21:02:54 -07:00
c45f4bbc99 Diff schema updates & WASM updates 2025-01-04 21:02:54 -07:00
b0c5431ac5 Add version to notify deps 2025-01-04 21:02:54 -07:00
LagoLunatic
9ab246367b Add buttons to collapse or expand all sections in the symbol list view for an object simultaneously (#149)
* Add buttons to expand/collapse all sections to symbol list view

* Add buttons to expand/collapse all sections to the split view
2025-01-01 20:48:25 -07:00
NWPlayer123
dcafe51eda Update Dependencies (#150)
* Update Dependencies

* Fix non-WGPU builds

---------

Co-authored-by: NWPlayer123 <NWPlayer123@users.noreply.github.com>
2025-01-01 20:45:48 -07:00
c65e87c382 Version 2.5.0 2024-12-08 21:48:21 -07:00
1756b9f6c5 Repaint after view action 2024-12-08 21:42:33 -07:00
303f2938a2 Update dependencies 2024-12-08 21:40:13 -07:00
526e031251 Experimental objdiff-cli diff auto-rebuild 2024-12-08 21:40:13 -07:00
LagoLunatic
10b2a9c129 PPC: Display data values on hover for pools as well (#140)
* Fix missing dependency feature for objdiff-gui

* Update .gitignore

* PPC: Display data values on hover for pools as well

* Tooltip data display: Format floats and doubles better

Floats and doubles will now always be displayed with a decimal point and one digit after it, even if they are whole numbers. Floats will also have the f suffix. This is so you can tell the data type just by glancing at the value.

* Move big functions to bottom ppc.rs

* Clear pool relocs in volatile registers on function call

This fixes some false positives.

* Revert ObjArch API changes, add fake target symbol hack

Because we no longer have access to the actual symbol name via sections, guess_data_type can no longer detect the String data type for pooled references.

* Add hack to detect strings via the addi opcode

* Move hack to resolve placeholder symbol into process_code_symbol

* Merge reloc and fake_pool_reloc fields of ObjIns
2024-12-03 22:50:05 -07:00
LagoLunatic
abe68ef2f2 objdiff-gui: Implement keyboard shortcuts (#139)
* Fix missing dependency feature for objdiff-gui

* Update .gitignore

* Add enter and back hotkeys

* Add scroll hotkeys

* Add hotkeys to select the next symbol above/below the current one in the listing

* Do not clear highlighted symbol when backing out of diff view

* Do not clear highlighted symbol when hovering mouse over an unpaired symbol

* Auto-scroll the keyboard-selected symbols into view if offscreen

* Fix some hotkeys stealing input from focused widgets

e.g. The symbol list was stealing the W/S key presses when typing into the symbol filter text edit.

If the user actually wants to use these shortcuts while a widget is focused, they can simply press the escape key to unfocus all widgets and then press the shortcut.

* Add Ctrl+F/S shortcuts for focusing the object and symbol filter text edits

* Add space as alternative to enter hotkey

This is for consistency with egui's builtint enter/space hotkey for interacting with the focused widget.

* Add hotkeys to change target and base functions

* Split function diff view: Enable PageUp/PageDown/Home/End for scrolling

* Add escape as an alternative to back hotkey

* Fix auto-scrolling to highlighted symbol only working for the left side

The flag is cleared after one scroll to avoid doing it continuously, but this breaks when we need to scroll to both the left and the right symbol at the same time. So now each side has its own flag to keep track of this state independently.

* Simplify clearing of the autoscroll flag, remove &mut State

* Found a better place to clear the autoscroll flag

DiffViewState::post_update is where the flag gets set, so clearing it right before that at the start of the function seems to make the most sense, instead of doing it in App::update.
2024-12-02 21:51:37 -07:00
LagoLunatic
304df96411 Display decoded rlwinm info to hover tooltip (#141)
* Fix missing dependency feature for objdiff-gui

* Update .gitignore

* Display decoded rlwinm info to hover tooltip

* Remove trailing newline when displaying decoded rlwinm info

* Change variable name

* Also update variable name in rlwinm.rs
2024-12-02 21:40:05 -07:00
132 changed files with 31660 additions and 11138 deletions

View File

@@ -10,7 +10,6 @@ on:
env:
BUILD_PROFILE: release-lto
CARGO_TARGET_DIR: target
CARGO_INCREMENTAL: 0
jobs:
@@ -33,9 +32,9 @@ jobs:
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Cargo check
run: cargo check --all-features --all-targets
run: cargo check --features all
- name: Cargo clippy
run: cargo clippy --all-features --all-targets
run: cargo clippy --features all
fmt:
name: Format
@@ -65,13 +64,12 @@ jobs:
continue-on-error: ${{ matrix.checks == 'advisories' }}
steps:
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v1
- uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check ${{ matrix.checks }}
test:
name: Test
if: 'false' # No tests yet
strategy:
matrix:
platform: [ ubuntu-latest, windows-latest, macos-latest ]
@@ -90,7 +88,7 @@ jobs:
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Cargo test
run: cargo test --release --all-features
run: cargo test --release --features all
build-cli:
name: Build objdiff-cli
@@ -150,7 +148,7 @@ jobs:
python3 -m venv .venv
. .venv/bin/activate
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
uses: dtolnay/rust-toolchain@stable
with:
@@ -168,8 +166,8 @@ jobs:
with:
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
path: |
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
target/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
target/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
if-no-files-found: error
build-gui:
@@ -180,21 +178,26 @@ jobs:
matrix:
include:
- 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
packages: libgtk-3-dev
build: zigbuild
features: default
- platform: windows-latest
target: x86_64-pc-windows-msvc
name: windows-x86_64
build: build
features: default
- platform: macos-latest
target: x86_64-apple-darwin
name: macos-x86_64
build: build
features: default
- platform: macos-latest
target: aarch64-apple-darwin
name: macos-arm64
build: build
features: default
fail-fast: false
runs-on: ${{ matrix.platform }}
@@ -206,27 +209,51 @@ jobs:
sudo apt-get -y install ${{ matrix.packages }}
- name: Checkout
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
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
targets: ${{ matrix.target_base || matrix.target }}
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.target }}
- name: Cargo build
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 }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: ${{ env.CARGO_BIN_NAME }}-${{ matrix.name }}
path: |
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
${{ env.CARGO_TARGET_DIR }}/${{ matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
target/${{ matrix.target_base || matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}
target/${{ matrix.target_base || matrix.target }}/${{ env.BUILD_PROFILE }}/${{ env.CARGO_BIN_NAME }}.exe
if-no-files-found: error
build-wasm:
name: Build objdiff-wasm
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Install dependencies
run: npm -C objdiff-wasm install
- name: Build
run: npm -C objdiff-wasm run build
release:
name: Release
if: startsWith(github.ref, 'refs/tags/')

2427
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -73,7 +73,7 @@ ignore = [
#{ 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
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
{ id = "RUSTSEC-2024-0384", reason = "Unmaintained indirect dependency" },
{ id = "RUSTSEC-2024-0436", reason = "Unmaintained paste crate is an indirect dependency" },
]
# If this is true, then cargo deny will use the git executable to fetch advisory database.
# If this is false, then it uses a built-in git library.
@@ -102,8 +102,7 @@ allow = [
"Zlib",
"0BSD",
"OFL-1.1",
"LicenseRef-UFL-1.0",
"OpenSSL",
"Ubuntu-font-1.0",
]
# The confidence threshold for detecting a license from license text.
# The higher the value, the more closely the license text must be to the
@@ -240,7 +239,12 @@ allow-git = []
[sources.allow-org]
# github.com organizations to allow git sources for
github = ["notify-rs"]
github = [
"CelestialAmber", # cwextab, rlwinmdec
"Decompollaborate", # rabbitizer
"enarx", # flagset
"encounter",
]
# gitlab.com organizations to allow git sources for
gitlab = []
# bitbucket.org organizations to allow git sources for

View File

@@ -14,7 +14,7 @@ publish = false
[dependencies]
anyhow = "1.0"
argp = "0.3"
argp = "0.4"
crossterm = "0.28"
enable-ansi-support = "0.2"
memmap2 = "0.9"
@@ -28,6 +28,7 @@ supports-color = "3.0"
time = { version = "0.3", features = ["formatting", "local-offset"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
typed-path = "0.10"
[target.'cfg(target_env = "musl")'.dependencies]
mimalloc = "0.1"

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,28 +1,25 @@
use std::{
collections::HashSet,
fs::File,
io::Read,
path::{Path, PathBuf},
time::Instant,
};
use std::{collections::HashSet, fs::File, io::Read, time::Instant};
use anyhow::{bail, Context, Result};
use anyhow::{Context, Result, bail};
use argp::FromArgs;
use objdiff_core::{
bindings::report::{
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, Report,
ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
REPORT_VERSION,
ChangeItem, ChangeItemInfo, ChangeUnit, Changes, ChangesInput, Measures, REPORT_VERSION,
Report, ReportCategory, ReportItem, ReportItemMetadata, ReportUnit, ReportUnitMetadata,
},
config::ProjectObject,
config::path::platform_path,
diff, obj,
obj::{ObjSectionKind, ObjSymbolFlags},
obj::{SectionKind, SymbolFlag},
};
use prost::Message;
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use tracing::{info, warn};
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
use crate::util::output::{write_output, OutputFormat};
use crate::{
cmd::diff::ObjectConfig,
util::output::{OutputFormat, write_output},
};
#[derive(FromArgs, PartialEq, Debug)]
/// Generate a progress report for a project.
@@ -43,12 +40,12 @@ pub enum SubCommand {
/// Generate a progress report for a project.
#[argp(subcommand, name = "generate")]
pub struct GenerateArgs {
#[argp(option, short = 'p')]
#[argp(option, short = 'p', from_str_fn(platform_path))]
/// Project directory
project: Option<PathBuf>,
#[argp(option, short = 'o')]
project: Option<Utf8PlatformPathBuf>,
#[argp(option, short = 'o', from_str_fn(platform_path))]
/// Output file
output: Option<PathBuf>,
output: Option<Utf8PlatformPathBuf>,
#[argp(switch, short = 'd')]
/// Deduplicate global and weak symbols (runs single-threaded)
deduplicate: bool,
@@ -61,15 +58,15 @@ pub struct GenerateArgs {
/// List any changes from a previous report.
#[argp(subcommand, name = "changes")]
pub struct ChangesArgs {
#[argp(positional)]
#[argp(positional, from_str_fn(platform_path))]
/// Previous report file
previous: PathBuf,
#[argp(positional)]
previous: Utf8PlatformPathBuf,
#[argp(positional, from_str_fn(platform_path))]
/// Current report file
current: PathBuf,
#[argp(option, short = 'o')]
current: Utf8PlatformPathBuf,
#[argp(option, short = 'o', from_str_fn(platform_path))]
/// Output file
output: Option<PathBuf>,
output: Option<Utf8PlatformPathBuf>,
#[argp(option, short = 'f')]
/// Output format (json, json-pretty, proto) (default: json)
format: Option<String>,
@@ -84,10 +81,10 @@ pub fn run(args: Args) -> Result<()> {
fn generate(args: GenerateArgs) -> Result<()> {
let output_format = OutputFormat::from_option(args.format.as_deref())?;
let project_dir = args.project.as_deref().unwrap_or_else(|| Path::new("."));
info!("Loading project {}", project_dir.display());
let project_dir = args.project.as_deref().unwrap_or_else(|| Utf8PlatformPath::new("."));
info!("Loading project {}", project_dir);
let mut project = match objdiff_core::config::try_project_config(project_dir) {
let project = match objdiff_core::config::try_project_config(project_dir.as_ref()) {
Some((Ok(config), _)) => config,
Some((Err(err), _)) => bail!("Failed to load project configuration: {}", err),
None => bail!("No project configuration found"),
@@ -98,37 +95,33 @@ fn generate(args: GenerateArgs) -> Result<()> {
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 mut units = vec![];
let mut existing_functions: HashSet<String> = HashSet::new();
if args.deduplicate {
// If deduplicating, we need to run single-threaded
for object in project.units.as_deref_mut().unwrap_or_default() {
if let Some(unit) = report_object(
object,
project_dir,
project.target_dir.as_deref(),
project.base_dir.as_deref(),
Some(&mut existing_functions),
)? {
for object in &objects {
if let Some(unit) = report_object(object, Some(&mut existing_functions))? {
units.push(unit);
}
}
} else {
let vec = project
.units
.as_deref_mut()
.unwrap_or_default()
.par_iter_mut()
.map(|object| {
report_object(
object,
project_dir,
project.target_dir.as_deref(),
project.base_dir.as_deref(),
None,
)
})
let vec = objects
.par_iter()
.map(|object| report_object(object, None))
.collect::<Result<Vec<Option<ReportUnit>>>>()?;
units = vec.into_iter().flatten().collect();
}
@@ -151,55 +144,54 @@ fn generate(args: GenerateArgs) -> Result<()> {
}
fn report_object(
object: &mut ProjectObject,
project_dir: &Path,
target_dir: Option<&Path>,
base_dir: Option<&Path>,
object: &ObjectConfig,
mut existing_functions: Option<&mut HashSet<String>>,
) -> Result<Option<ReportUnit>> {
object.resolve_paths(project_dir, target_dir, base_dir);
match (&object.target_path, &object.base_path) {
(None, Some(_)) if !object.complete().unwrap_or(false) => {
warn!("Skipping object without target: {}", object.name());
(None, Some(_)) if !object.complete.unwrap_or(false) => {
warn!("Skipping object without target: {}", object.name);
return Ok(None);
}
(None, None) => {
warn!("Skipping object without target or base: {}", object.name());
warn!("Skipping object without target or base: {}", object.name);
return Ok(None);
}
_ => {}
}
let config = diff::DiffObjConfig { relax_reloc_diffs: true, ..Default::default() };
let diff_config = diff::DiffObjConfig {
function_reloc_diffs: diff::FunctionRelocDiffs::None,
..Default::default()
};
let mapping_config = diff::MappingConfig::default();
let target = object
.target_path
.as_ref()
.map(|p| {
obj::read::read(p, &config).with_context(|| format!("Failed to open {}", p.display()))
obj::read::read(p.as_ref(), &diff_config)
.with_context(|| format!("Failed to open {}", p))
})
.transpose()?;
let base = object
.base_path
.as_ref()
.map(|p| {
obj::read::read(p, &config).with_context(|| format!("Failed to open {}", p.display()))
obj::read::read(p.as_ref(), &diff_config)
.with_context(|| format!("Failed to open {}", p))
})
.transpose()?;
let result = diff::diff_objs(&config, target.as_ref(), base.as_ref(), None)?;
let result =
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
let metadata = ReportUnitMetadata {
complete: object.complete(),
complete: object.metadata.complete,
module_name: target
.as_ref()
.and_then(|o| o.split_meta.as_ref())
.and_then(|m| m.module_name.clone()),
module_id: target.as_ref().and_then(|o| o.split_meta.as_ref()).and_then(|m| m.module_id),
source_path: object.metadata.as_ref().and_then(|m| m.source_path.clone()),
progress_categories: object
.metadata
.as_ref()
.and_then(|m| m.progress_categories.clone())
.unwrap_or_default(),
auto_generated: object.metadata.as_ref().and_then(|m| m.auto_generated),
source_path: object.metadata.source_path.as_ref().map(|p| p.to_string()),
progress_categories: object.metadata.progress_categories.clone().unwrap_or_default(),
auto_generated: object.metadata.auto_generated,
};
let mut measures = Measures { total_units: 1, ..Default::default() };
let mut sections = vec![];
@@ -207,15 +199,13 @@ fn report_object(
let obj = target.as_ref().or(base.as_ref()).unwrap();
let obj_diff = result.left.as_ref().or(result.right.as_ref()).unwrap();
for (section, section_diff) in obj.sections.iter().zip(&obj_diff.sections) {
for ((section_idx, section), section_diff) in
obj.sections.iter().enumerate().zip(&obj_diff.sections)
{
let section_match_percent = section_diff.match_percent.unwrap_or_else(|| {
// Support cases where we don't have a target object,
// assume complete means 100% match
if object.complete().unwrap_or(false) {
100.0
} else {
0.0
}
if object.complete.unwrap_or(false) { 100.0 } else { 0.0 }
});
sections.push(ReportItem {
name: section.name.clone(),
@@ -228,23 +218,26 @@ fn report_object(
});
match section.kind {
ObjSectionKind::Data | ObjSectionKind::Bss => {
SectionKind::Data | SectionKind::Bss => {
measures.total_data += section.size;
if section_match_percent == 100.0 {
measures.matched_data += section.size;
}
continue;
}
ObjSectionKind::Code => (),
_ => {}
}
for (symbol, symbol_diff) in section.symbols.iter().zip(&section_diff.symbols) {
if symbol.size == 0 || symbol.flags.0.contains(ObjSymbolFlags::Hidden) {
for (symbol, symbol_diff) in obj.symbols.iter().zip(&obj_diff.symbols) {
if symbol.section != Some(section_idx)
|| symbol.size == 0
|| symbol.flags.contains(SymbolFlag::Hidden)
{
continue;
}
if let Some(existing_functions) = &mut existing_functions {
if (symbol.flags.0.contains(ObjSymbolFlags::Global)
|| symbol.flags.0.contains(ObjSymbolFlags::Weak))
if (symbol.flags.contains(SymbolFlag::Global)
|| symbol.flags.contains(SymbolFlag::Weak))
&& !existing_functions.insert(symbol.name.clone())
{
continue;
@@ -253,11 +246,7 @@ fn report_object(
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
// Support cases where we don't have a target object,
// assume complete means 100% match
if object.complete().unwrap_or(false) {
100.0
} else {
0.0
}
if object.complete.unwrap_or(false) { 100.0 } else { 0.0 }
});
measures.fuzzy_match_percent += match_percent * symbol.size as f32;
measures.total_code += symbol.size;
@@ -287,7 +276,7 @@ fn report_object(
measures.calc_fuzzy_match_percent();
measures.calc_matched_percent();
Ok(Some(ReportUnit {
name: object.name().to_string(),
name: object.name.clone(),
measures: Some(measures),
sections,
functions,
@@ -297,7 +286,7 @@ fn report_object(
fn changes(args: ChangesArgs) -> Result<()> {
let output_format = OutputFormat::from_option(args.format.as_deref())?;
let (previous, current) = if args.previous == Path::new("-") && args.current == Path::new("-") {
let (previous, current) = if args.previous == "-" && args.current == "-" {
// Special case for comparing two reports from stdin
let mut data = vec![];
std::io::stdin().read_to_end(&mut data)?;
@@ -412,15 +401,14 @@ fn process_new_items(items: &[ReportItem]) -> Vec<ChangeItem> {
.collect()
}
fn read_report(path: &Path) -> Result<Report> {
if path == Path::new("-") {
fn read_report(path: &Utf8PlatformPath) -> Result<Report> {
if path == Utf8PlatformPath::new("-") {
let mut data = vec![];
std::io::stdin().read_to_end(&mut data)?;
return Report::parse(&data).with_context(|| "Failed to load report from stdin");
}
let file = File::open(path).with_context(|| format!("Failed to open {}", path.display()))?;
let mmap = unsafe { memmap2::Mmap::map(&file) }
.with_context(|| format!("Failed to map {}", path.display()))?;
Report::parse(mmap.as_ref())
.with_context(|| format!("Failed to load report {}", path.display()))
let file = File::open(path).with_context(|| format!("Failed to open {}", path))?;
let mmap =
unsafe { memmap2::Mmap::map(&file) }.with_context(|| format!("Failed to map {}", path))?;
Report::parse(mmap.as_ref()).with_context(|| format!("Failed to load report {}", path))
}

View File

@@ -1,6 +1,9 @@
#![allow(clippy::too_many_arguments)]
mod argp_version;
mod cmd;
mod util;
mod views;
// musl's allocator is very slow, so use mimalloc when targeting musl.
// Otherwise, use the system allocator to avoid extra code size.
@@ -14,7 +17,7 @@ use anyhow::{Error, Result};
use argp::{FromArgValue, FromArgs};
use enable_ansi_support::enable_ansi_support;
use supports_color::Stream;
use tracing_subscriber::{filter::LevelFilter, EnvFilter};
use tracing_subscriber::{EnvFilter, filter::LevelFilter};
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
enum LogLevel {

View File

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

View File

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

View File

@@ -0,0 +1,661 @@
use core::cmp::Ordering;
use anyhow::Result;
use crossterm::event::{Event, KeyCode, KeyEventKind, KeyModifiers, MouseButton, MouseEventKind};
use objdiff_core::{
build::BuildStatus,
diff::{
DiffObjConfig, FunctionRelocDiffs, InstructionDiffKind, ObjectDiff, SymbolDiff,
display::{DiffText, DiffTextColor, HighlightKind, display_row},
},
obj::Object,
};
use ratatui::{
Frame,
prelude::*,
widgets::{Block, Borders, Clear, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState},
};
use super::{EventControlFlow, EventResult, UiView};
use crate::cmd::diff::AppState;
#[allow(dead_code)]
#[derive(Default)]
pub struct FunctionDiffUi {
pub symbol_name: String,
pub left_highlight: HighlightKind,
pub right_highlight: HighlightKind,
pub scroll_x: usize,
pub scroll_state_x: ScrollbarState,
pub scroll_y: usize,
pub scroll_state_y: ScrollbarState,
pub per_page: usize,
pub num_rows: usize,
pub left_sym: Option<usize>,
pub right_sym: Option<usize>,
pub prev_sym: Option<usize>,
pub open_options: bool,
pub three_way: bool,
}
impl UiView for FunctionDiffUi {
fn draw(&mut self, state: &AppState, f: &mut Frame, result: &mut EventResult) {
let chunks = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]).split(f.area());
let header_chunks = Layout::horizontal([
Constraint::Fill(1),
Constraint::Length(3),
Constraint::Fill(1),
Constraint::Length(2),
])
.split(chunks[0]);
let content_chunks = if self.three_way {
Layout::horizontal([
Constraint::Fill(1),
Constraint::Length(3),
Constraint::Fill(1),
Constraint::Length(3),
Constraint::Fill(1),
Constraint::Length(2),
])
.split(chunks[1])
} else {
Layout::horizontal([
Constraint::Fill(1),
Constraint::Length(3),
Constraint::Fill(1),
Constraint::Length(2),
])
.split(chunks[1])
};
self.per_page = chunks[1].height.saturating_sub(2) as usize;
let max_scroll_y = self.num_rows.saturating_sub(self.per_page);
if self.scroll_y > max_scroll_y {
self.scroll_y = max_scroll_y;
}
self.scroll_state_y =
self.scroll_state_y.content_length(max_scroll_y).position(self.scroll_y);
let mut line_l = Line::default();
line_l
.spans
.push(Span::styled(self.symbol_name.clone(), Style::new().fg(Color::White).bold()));
f.render_widget(line_l, header_chunks[0]);
let mut line_r = Line::default();
if let Some(percent) = get_symbol(state.right_obj.as_ref(), self.right_sym)
.and_then(|(_, _, d)| d.match_percent)
{
line_r.spans.push(Span::styled(
format!("{:.2}% ", percent),
Style::new().fg(match_percent_color(percent)),
));
}
let reload_time = state
.reload_time
.as_ref()
.and_then(|t| t.format(&state.time_format).ok())
.unwrap_or_else(|| "N/A".to_string());
line_r.spans.push(Span::styled(
format!("Last reload: {}", reload_time),
Style::new().fg(Color::White),
));
line_r.spans.push(Span::styled(
format!(" ({} jobs)", state.jobs.jobs.len()),
Style::new().fg(Color::LightYellow),
));
f.render_widget(line_r, header_chunks[2]);
let mut left_text = None;
let mut left_highlight = None;
let mut max_width = 0;
if let Some((obj, symbol_idx, symbol_diff)) =
get_symbol(state.left_obj.as_ref(), self.left_sym)
{
let mut text = Text::default();
let rect = content_chunks[0].inner(Margin::new(0, 1));
left_highlight = self.print_sym(
&mut text,
obj,
symbol_idx,
symbol_diff,
&state.diff_obj_config,
rect,
&self.left_highlight,
result,
false,
);
max_width = max_width.max(text.width());
left_text = Some(text);
} else if let Some(status) = &state.left_status {
let mut text = Text::default();
self.print_build_status(&mut text, status);
max_width = max_width.max(text.width());
left_text = Some(text);
}
let mut right_text = None;
let mut right_highlight = None;
let mut margin_text = None;
if let Some((obj, symbol_idx, symbol_diff)) =
get_symbol(state.right_obj.as_ref(), self.right_sym)
{
let mut text = Text::default();
let rect = content_chunks[2].inner(Margin::new(0, 1));
right_highlight = self.print_sym(
&mut text,
obj,
symbol_idx,
symbol_diff,
&state.diff_obj_config,
rect,
&self.right_highlight,
result,
false,
);
max_width = max_width.max(text.width());
right_text = Some(text);
// Render margin
let mut text = Text::default();
let rect = content_chunks[1].inner(Margin::new(1, 1));
self.print_margin(&mut text, symbol_diff, rect);
margin_text = Some(text);
} else if let Some(status) = &state.right_status {
let mut text = Text::default();
self.print_build_status(&mut text, status);
max_width = max_width.max(text.width());
right_text = Some(text);
}
let mut prev_text = None;
let mut prev_margin_text = None;
if self.three_way {
if let Some((obj, symbol_idx, symbol_diff)) =
get_symbol(state.prev_obj.as_ref(), self.prev_sym)
{
let mut text = Text::default();
let rect = content_chunks[4].inner(Margin::new(0, 1));
self.print_sym(
&mut text,
obj,
symbol_idx,
symbol_diff,
&state.diff_obj_config,
rect,
&self.right_highlight,
result,
true,
);
max_width = max_width.max(text.width());
prev_text = Some(text);
// Render margin
let mut text = Text::default();
let rect = content_chunks[3].inner(Margin::new(1, 1));
self.print_margin(&mut text, symbol_diff, rect);
prev_margin_text = Some(text);
}
}
let max_scroll_x =
max_width.saturating_sub(content_chunks[0].width.min(content_chunks[2].width) as usize);
if self.scroll_x > max_scroll_x {
self.scroll_x = max_scroll_x;
}
self.scroll_state_x =
self.scroll_state_x.content_length(max_scroll_x).position(self.scroll_x);
if let Some(text) = left_text {
// Render left column
f.render_widget(
Paragraph::new(text)
.block(
Block::new()
.borders(Borders::TOP)
.border_style(Style::new().fg(Color::Gray))
.title_style(Style::new().bold())
.title("TARGET"),
)
.scroll((0, self.scroll_x as u16)),
content_chunks[0],
);
}
if let Some(text) = margin_text {
f.render_widget(text, content_chunks[1].inner(Margin::new(1, 1)));
}
if let Some(text) = right_text {
f.render_widget(
Paragraph::new(text)
.block(
Block::new()
.borders(Borders::TOP)
.border_style(Style::new().fg(Color::Gray))
.title_style(Style::new().bold())
.title("CURRENT"),
)
.scroll((0, self.scroll_x as u16)),
content_chunks[2],
);
}
if self.three_way {
if let Some(text) = prev_margin_text {
f.render_widget(text, content_chunks[3].inner(Margin::new(1, 1)));
}
let block = Block::new()
.borders(Borders::TOP)
.border_style(Style::new().fg(Color::Gray))
.title_style(Style::new().bold())
.title("SAVED");
if let Some(text) = prev_text {
f.render_widget(
Paragraph::new(text).block(block.clone()).scroll((0, self.scroll_x as u16)),
content_chunks[4],
);
} else {
f.render_widget(block, content_chunks[4]);
}
}
// Render scrollbars
f.render_stateful_widget(
Scrollbar::new(ScrollbarOrientation::VerticalRight).begin_symbol(None).end_symbol(None),
chunks[1].inner(Margin::new(0, 1)),
&mut self.scroll_state_y,
);
f.render_stateful_widget(
Scrollbar::new(ScrollbarOrientation::HorizontalBottom).thumb_symbol(""),
content_chunks[0],
&mut self.scroll_state_x,
);
f.render_stateful_widget(
Scrollbar::new(ScrollbarOrientation::HorizontalBottom).thumb_symbol(""),
content_chunks[2],
&mut self.scroll_state_x,
);
if self.three_way {
f.render_stateful_widget(
Scrollbar::new(ScrollbarOrientation::HorizontalBottom).thumb_symbol(""),
content_chunks[4],
&mut self.scroll_state_x,
);
}
if let Some(new_highlight) = left_highlight {
if new_highlight == self.left_highlight {
if self.left_highlight != self.right_highlight {
self.right_highlight = self.left_highlight.clone();
} else {
self.left_highlight = HighlightKind::None;
self.right_highlight = HighlightKind::None;
}
} else {
self.left_highlight = new_highlight;
}
result.redraw = true;
} else if let Some(new_highlight) = right_highlight {
if new_highlight == self.right_highlight {
if self.left_highlight != self.right_highlight {
self.left_highlight = self.right_highlight.clone();
} else {
self.left_highlight = HighlightKind::None;
self.right_highlight = HighlightKind::None;
}
} else {
self.right_highlight = new_highlight;
}
result.redraw = true;
}
if self.open_options {
self.draw_options(f, result);
}
}
fn handle_event(&mut self, state: &mut AppState, event: Event) -> EventControlFlow {
let mut result = EventResult::default();
match event {
Event::Key(event)
if matches!(event.kind, KeyEventKind::Press | KeyEventKind::Repeat) =>
{
match event.code {
// Quit
KeyCode::Esc | KeyCode::Char('q') => return EventControlFlow::Break,
// Page up
KeyCode::PageUp => {
self.page_up(false);
result.redraw = true;
}
// Page up (shift + space)
KeyCode::Char(' ') if event.modifiers.contains(KeyModifiers::SHIFT) => {
self.page_up(false);
result.redraw = true;
}
// Page down
KeyCode::Char(' ') | KeyCode::PageDown => {
self.page_down(false);
result.redraw = true;
}
// Page down (ctrl + f)
KeyCode::Char('f') if event.modifiers.contains(KeyModifiers::CONTROL) => {
self.page_down(false);
result.redraw = true;
}
// Page up (ctrl + b)
KeyCode::Char('b') if event.modifiers.contains(KeyModifiers::CONTROL) => {
self.page_up(false);
result.redraw = true;
}
// Half page down (ctrl + d)
KeyCode::Char('d') if event.modifiers.contains(KeyModifiers::CONTROL) => {
self.page_down(true);
result.redraw = true;
}
// Half page up (ctrl + u)
KeyCode::Char('u') if event.modifiers.contains(KeyModifiers::CONTROL) => {
self.page_up(true);
result.redraw = true;
}
// Scroll down
KeyCode::Down | KeyCode::Char('j') => {
self.scroll_y += 1;
result.redraw = true;
}
// Scroll up
KeyCode::Up | KeyCode::Char('k') => {
self.scroll_y = self.scroll_y.saturating_sub(1);
result.redraw = true;
}
// Scroll to start
KeyCode::Char('g') => {
self.scroll_y = 0;
result.redraw = true;
}
// Scroll to end
KeyCode::Char('G') => {
self.scroll_y = self.num_rows;
result.redraw = true;
}
// Reload
KeyCode::Char('r') => {
result.redraw = true;
return EventControlFlow::Reload;
}
// Scroll right
KeyCode::Right | KeyCode::Char('l') => {
self.scroll_x += 1;
result.redraw = true;
}
// Scroll left
KeyCode::Left | KeyCode::Char('h') => {
self.scroll_x = self.scroll_x.saturating_sub(1);
result.redraw = true;
}
// Cycle through function relocation diff mode
KeyCode::Char('x') => {
state.diff_obj_config.function_reloc_diffs =
match state.diff_obj_config.function_reloc_diffs {
FunctionRelocDiffs::None => FunctionRelocDiffs::NameAddress,
FunctionRelocDiffs::NameAddress => FunctionRelocDiffs::DataValue,
FunctionRelocDiffs::DataValue => FunctionRelocDiffs::All,
FunctionRelocDiffs::All => FunctionRelocDiffs::None,
};
result.redraw = true;
return EventControlFlow::Reload;
}
// Toggle three-way diff
KeyCode::Char('3') => {
self.three_way = !self.three_way;
result.redraw = true;
}
// Toggle options
KeyCode::Char('o') => {
self.open_options = !self.open_options;
result.redraw = true;
}
_ => {}
}
}
Event::Mouse(event) => match event.kind {
MouseEventKind::ScrollDown => {
self.scroll_y += 3;
result.redraw = true;
}
MouseEventKind::ScrollUp => {
self.scroll_y = self.scroll_y.saturating_sub(3);
result.redraw = true;
}
MouseEventKind::ScrollRight => {
self.scroll_x += 3;
result.redraw = true;
}
MouseEventKind::ScrollLeft => {
self.scroll_x = self.scroll_x.saturating_sub(3);
result.redraw = true;
}
MouseEventKind::Down(MouseButton::Left) => {
result.click_xy = Some((event.column, event.row));
result.redraw = true;
}
_ => {}
},
Event::Resize(_, _) => {
result.redraw = true;
}
_ => {}
}
EventControlFlow::Continue(result)
}
fn reload(&mut self, state: &AppState) -> Result<()> {
let left_sym =
state.left_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name));
let right_sym =
state.right_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name));
let prev_sym =
state.prev_obj.as_ref().and_then(|(o, _)| find_function(o, &self.symbol_name));
self.num_rows = match (
get_symbol(state.left_obj.as_ref(), left_sym),
get_symbol(state.right_obj.as_ref(), right_sym),
) {
(Some((_l, _ls, ld)), Some((_r, _rs, rd))) => {
ld.instruction_rows.len().max(rd.instruction_rows.len())
}
(Some((_l, _ls, ld)), None) => ld.instruction_rows.len(),
(None, Some((_r, _rs, rd))) => rd.instruction_rows.len(),
(None, None) => 0,
};
self.left_sym = left_sym;
self.right_sym = right_sym;
self.prev_sym = prev_sym;
Ok(())
}
}
impl FunctionDiffUi {
pub fn draw_options(&mut self, f: &mut Frame, _result: &mut EventResult) {
let percent_x = 50;
let percent_y = 50;
let popup_rect = Layout::vertical([
Constraint::Percentage((100 - percent_y) / 2),
Constraint::Percentage(percent_y),
Constraint::Percentage((100 - percent_y) / 2),
])
.split(f.area())[1];
let popup_rect = Layout::horizontal([
Constraint::Percentage((100 - percent_x) / 2),
Constraint::Percentage(percent_x),
Constraint::Percentage((100 - percent_x) / 2),
])
.split(popup_rect)[1];
let popup = Block::default()
.borders(Borders::ALL)
.title("Options")
.title_style(Style::default().fg(Color::White).bg(Color::Black));
f.render_widget(Clear, popup_rect);
f.render_widget(popup, popup_rect);
}
fn page_up(&mut self, half: bool) {
self.scroll_y = self.scroll_y.saturating_sub(self.per_page / if half { 2 } else { 1 });
}
fn page_down(&mut self, half: bool) {
self.scroll_y += self.per_page / if half { 2 } else { 1 };
}
fn print_sym(
&self,
out: &mut Text<'static>,
obj: &Object,
symbol_index: usize,
symbol_diff: &SymbolDiff,
diff_config: &DiffObjConfig,
rect: Rect,
highlight: &HighlightKind,
result: &EventResult,
only_changed: bool,
) -> Option<HighlightKind> {
let mut new_highlight = None;
for (y, ins_row) in symbol_diff
.instruction_rows
.iter()
.skip(self.scroll_y)
.take(rect.height as usize)
.enumerate()
{
if only_changed && ins_row.kind == InstructionDiffKind::None {
out.lines.push(Line::default());
continue;
}
let mut sx = rect.x;
let sy = rect.y + y as u16;
let mut line = Line::default();
display_row(obj, symbol_index, ins_row, diff_config, |segment| {
let highlight_kind = HighlightKind::from(&segment.text);
let label_text = match segment.text {
DiffText::Basic(text) => text.to_string(),
DiffText::Line(num) => format!("{num} "),
DiffText::Address(addr) => format!("{:x}:", addr),
DiffText::Opcode(mnemonic, _op) => format!("{mnemonic} "),
DiffText::Argument(arg) => arg.to_string(),
DiffText::BranchDest(addr) => format!("{addr:x}"),
DiffText::Symbol(sym) => {
sym.demangled_name.as_ref().unwrap_or(&sym.name).clone()
}
DiffText::Addend(addend) => match addend.cmp(&0i64) {
Ordering::Greater => format!("+{:#x}", addend),
Ordering::Less => format!("-{:#x}", -addend),
_ => String::new(),
},
DiffText::Spacing(n) => {
line.spans.push(Span::raw(" ".repeat(n as usize)));
sx += n as u16;
return Ok(());
}
DiffText::Eol => return Ok(()),
};
let len = label_text.len();
let highlighted =
highlight_kind != HighlightKind::None && *highlight == highlight_kind;
if let Some((cx, cy)) = result.click_xy {
if cx >= sx && cx < sx + len as u16 && cy == sy {
new_highlight = Some(highlight_kind);
}
}
let mut style = Style::new().fg(match segment.color {
DiffTextColor::Normal => Color::Gray,
DiffTextColor::Dim => Color::DarkGray,
DiffTextColor::Bright => Color::White,
DiffTextColor::Replace => Color::Cyan,
DiffTextColor::Delete => Color::Red,
DiffTextColor::Insert => Color::Green,
DiffTextColor::Rotating(i) => COLOR_ROTATION[i as usize % COLOR_ROTATION.len()],
});
if highlighted {
style = style.bg(Color::DarkGray);
}
line.spans.push(Span::styled(label_text, style));
sx += len as u16;
if segment.pad_to as usize > len {
let pad = (segment.pad_to as usize - len) as u16;
line.spans.push(Span::raw(" ".repeat(pad as usize)));
sx += pad;
}
Ok(())
})
.unwrap();
out.lines.push(line);
}
new_highlight
}
fn print_margin(&self, out: &mut Text, symbol: &SymbolDiff, rect: Rect) {
for ins_row in symbol.instruction_rows.iter().skip(self.scroll_y).take(rect.height as usize)
{
if ins_row.kind != InstructionDiffKind::None {
out.lines.push(Line::raw(match ins_row.kind {
InstructionDiffKind::Delete => "<",
InstructionDiffKind::Insert => ">",
_ => "|",
}));
} else {
out.lines.push(Line::raw(" "));
}
}
}
fn print_build_status<'a>(&self, out: &mut Text<'a>, status: &'a BuildStatus) {
if !status.cmdline.is_empty() {
out.lines.push(Line::styled(status.cmdline.clone(), Style::new().fg(Color::LightBlue)));
}
for line in status.stdout.lines() {
out.lines.push(Line::styled(line, Style::new().fg(Color::White)));
}
for line in status.stderr.lines() {
out.lines.push(Line::styled(line, Style::new().fg(Color::Red)));
}
}
}
pub const COLOR_ROTATION: [Color; 7] = [
Color::Magenta,
Color::Cyan,
Color::Green,
Color::Red,
Color::Yellow,
Color::Blue,
Color::Green,
];
pub fn match_percent_color(match_percent: f32) -> Color {
if match_percent == 100.0 {
Color::Green
} else if match_percent >= 50.0 {
Color::LightBlue
} else {
Color::LightRed
}
}
#[inline]
fn get_symbol(
obj: Option<&(Object, ObjectDiff)>,
sym: Option<usize>,
) -> Option<(&Object, usize, &SymbolDiff)> {
let (obj, diff) = obj?;
let sym = sym?;
Some((obj, sym, &diff.symbols[sym]))
}
fn find_function(obj: &Object, name: &str) -> Option<usize> {
for (symbol_idx, symbol) in obj.symbols.iter().enumerate() {
if symbol.name == name {
return Some(symbol_idx);
}
}
None
}

View File

@@ -0,0 +1,25 @@
use anyhow::Result;
use crossterm::event::Event;
use ratatui::Frame;
use crate::cmd::diff::AppState;
pub mod function_diff;
#[derive(Default)]
pub struct EventResult {
pub redraw: bool,
pub click_xy: Option<(u16, u16)>,
}
pub enum EventControlFlow {
Break,
Continue(EventResult),
Reload,
}
pub trait UiView {
fn draw(&mut self, state: &AppState, f: &mut Frame, result: &mut EventResult);
fn handle_event(&mut self, state: &mut AppState, event: Event) -> EventControlFlow;
fn reload(&mut self, state: &AppState) -> Result<()>;
}

View File

@@ -12,75 +12,184 @@ A local diffing tool for decompilation projects.
"""
documentation = "https://docs.rs/objdiff-core"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
all = ["config", "dwarf", "mips", "ppc", "x86", "arm", "arm64", "bindings"]
any-arch = ["config", "dep:bimap", "dep:strum", "dep:similar", "dep:flagset", "dep:log", "dep:memmap2", "dep:byteorder", "dep:num-traits"] # Implicit, used to check if any arch is enabled
config = ["dep:bimap", "dep:globset", "dep:semver", "dep:serde_json", "dep:serde_yaml", "dep:serde", "dep:filetime"]
default = ["std"]
all = [
# Features
"bindings",
"build",
"config",
"dwarf",
"serde",
# Architectures
"arm",
"arm64",
"mips",
"ppc",
"x86",
]
# Implicit, used to check if any arch is enabled
any-arch = [
"dep:flagset",
"dep:heck",
"dep:log",
"dep:num-traits",
"dep:prettyplease",
"dep:proc-macro2",
"dep:quote",
"dep:regex",
"dep:similar",
"dep:syn",
]
bindings = [
"dep:prost",
"dep:prost-build",
]
build = [
"dep:notify",
"dep:notify-debouncer-full",
"dep:reqwest",
"dep:self_update",
"dep:shell-escape",
"dep:tempfile",
"dep:time",
"dep:winapi",
]
config = [
"dep:globset",
"dep:semver",
"dep:typed-path",
]
dwarf = ["dep:gimli"]
mips = ["any-arch", "dep:rabbitizer"]
ppc = ["any-arch", "dep:cwdemangle", "dep:cwextab", "dep:ppc750cl"]
x86 = ["any-arch", "dep:cpp_demangle", "dep:iced-x86", "dep:msvc-demangler"]
arm = ["any-arch", "dep:cpp_demangle", "dep:unarm", "dep:arm-attr"]
arm64 = ["any-arch", "dep:cpp_demangle", "dep:yaxpeax-arch", "dep:yaxpeax-arm"]
bindings = ["dep:serde_json", "dep:prost", "dep:pbjson", "dep:serde", "dep:prost-build", "dep:pbjson-build"]
wasm = ["bindings", "any-arch", "dep:console_error_panic_hook", "dep:console_log", "dep:wasm-bindgen", "dep:tsify-next", "dep:log"]
serde = [
"dep:pbjson",
"dep:pbjson-build",
"dep:serde",
"dep:serde_json",
]
std = [
"anyhow/std",
"flagset?/std",
"log?/std",
"num-traits?/std",
"object/std",
"prost?/std",
"serde?/std",
"typed-path?/std",
"dep:filetime",
"dep:memmap2",
]
mips = [
"any-arch",
"dep:rabbitizer",
]
ppc = [
"any-arch",
"dep:cwdemangle",
"dep:cwextab",
"dep:ppc750cl",
"dep:rlwinmdec",
]
x86 = [
"any-arch",
"dep:cpp_demangle",
"dep:iced-x86",
"dep:msvc-demangler",
]
arm = [
"any-arch",
"dep:arm-attr",
"dep:cpp_demangle",
"dep:unarm",
]
arm64 = [
"any-arch",
"dep:cpp_demangle",
"dep:yaxpeax-arch",
"dep:yaxpeax-arm",
]
[package.metadata.docs.rs]
features = ["all"]
[dependencies]
anyhow = "1.0"
bimap = { version = "0.6", features = ["serde"], optional = true }
byteorder = { version = "1.5", optional = true }
anyhow = { version = "1.0", default-features = false }
filetime = { version = "0.2", optional = true }
flagset = { version = "0.4", optional = true }
log = { version = "0.4", optional = true }
flagset = { version = "0.4", default-features = false, optional = true, git = "https://github.com/enarx/flagset.git", rev = "a1fe9369b3741e43fec45da1998e83b9d78966a2" }
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 }
num-traits = { version = "0.2", optional = true }
object = { version = "0.36", features = ["read_core", "std", "elf", "pe"], default-features = false }
pbjson = { version = "0.7", optional = true }
prost = { version = "0.13", optional = true }
serde = { version = "1.0", features = ["derive"], optional = true }
similar = { version = "2.6", default-features = false, optional = true }
strum = { version = "0.26", features = ["derive"], optional = true }
wasm-bindgen = { version = "0.2", 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 }
num-traits = { version = "0.2", default-features = false, optional = true }
object = { version = "0.36", default-features = false, features = ["read_core", "elf", "pe"] }
pbjson = { version = "0.7", default-features = false, optional = true }
prost = { version = "0.13", default-features = false, features = ["prost-derive"], optional = true }
regex = { version = "1.11", default-features = false, features = [], optional = true }
serde = { version = "1.0", default-features = false, features = ["derive"], optional = true }
similar = { version = "2.7", default-features = false, optional = true, git = "https://github.com/encounter/similar.git", branch = "no_std" }
typed-path = { version = "0.10", default-features = false, optional = true }
# config
globset = { version = "0.4", features = ["serde1"], optional = true }
semver = { version = "1.0", optional = true }
serde_json = { version = "1.0", optional = true }
serde_yaml = { version = "0.9", optional = true }
globset = { version = "0.4", default-features = false, optional = true }
semver = { version = "1.0", default-features = false, optional = true }
serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true }
# dwarf
gimli = { version = "0.31", default-features = false, features = ["read-all"], optional = true }
gimli = { version = "0.31", default-features = false, features = ["read"], optional = true }
# ppc
cwdemangle = { version = "1.0", optional = true }
cwextab = { version = "1.0.2", optional = true }
cwextab = { version = "1.0", optional = true, git = "https://github.com/CelestialAmber/cwextab.git" }
ppc750cl = { version = "0.3", optional = true }
rlwinmdec = { version = "1.1", optional = true, git = "https://github.com/CelestialAmber/rlwinmdec.git" }
# mips
rabbitizer = { version = "1.12", optional = true }
rabbitizer = { git = "https://github.com/Decompollaborate/rabbitizer.git", branch = "🦀", default-features = false, features = ["all_extensions"], optional = true }
# x86
cpp_demangle = { version = "0.4", optional = true }
iced-x86 = { version = "1.21", default-features = false, features = ["std", "decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums"], optional = true }
msvc-demangler = { version = "0.10", optional = true }
cpp_demangle = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
iced-x86 = { version = "1.21", default-features = false, features = ["decoder", "intel", "gas", "masm", "nasm", "exhaustive_enums", "no_std"], optional = true }
msvc-demangler = { version = "0.11", optional = true }
# arm
unarm = { version = "1.6", optional = true }
arm-attr = { version = "0.1", optional = true }
unarm = { version = "1.7", optional = true }
arm-attr = { version = "0.2", optional = true }
# arm64
yaxpeax-arch = { version = "0.3", default-features = false, features = ["std"], optional = true }
yaxpeax-arm = { 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, optional = true }
# build
notify = { version = "8.0.0", optional = true }
notify-debouncer-full = { version = "0.5.0", optional = true }
shell-escape = { version = "0.1", optional = true }
tempfile = { version = "3.17", optional = true }
time = { version = "0.3", optional = true }
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", optional = true }
# For Linux static binaries, use rustls
[target.'cfg(target_os = "linux")'.dependencies]
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "rustls-tls"], optional = true }
self_update = { version = "0.42", default-features = false, features = ["rustls"], optional = true }
# For all other platforms, use native TLS
[target.'cfg(not(target_os = "linux"))'.dependencies]
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "default-tls"], optional = true }
self_update = { version = "0.42", optional = true }
[build-dependencies]
prost-build = { version = "0.13", optional = true }
heck = { version = "0.5", optional = true }
pbjson-build = { version = "0.7", optional = true }
prettyplease = { version = "0.2", optional = true }
proc-macro2 = { version = "1.0", optional = true }
prost-build = { version = "0.13", optional = true }
quote = { version = "1.0", optional = true }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" }
syn = { version = "2.0", optional = true }
[dev-dependencies]
# Enable all features for tests
objdiff-core = { path = ".", features = ["all"] }
insta = "1.42"

View File

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

View File

@@ -0,0 +1,278 @@
{
"properties": [
{
"id": "functionRelocDiffs",
"type": "choice",
"default": "name_address",
"name": "Function relocation diffs",
"description": "How relocation targets will be diffed in the function view.",
"items": [
{
"value": "none",
"name": "None"
},
{
"value": "name_address",
"name": "Name or address"
},
{
"value": "data_value",
"name": "Data value"
},
{
"value": "all",
"name": "Name or address, data value"
}
]
},
{
"id": "spaceBetweenArgs",
"type": "boolean",
"default": true,
"name": "Space between args",
"description": "Adds a space between arguments in the diff output."
},
{
"id": "combineDataSections",
"type": "boolean",
"default": false,
"name": "Combine data sections",
"description": "Combines data sections with equal names."
},
{
"id": "combineTextSections",
"type": "boolean",
"default": false,
"name": "Combine text sections",
"description": "Combines all text sections into one."
},
{
"id": "arm.archVersion",
"type": "choice",
"default": "auto",
"name": "Architecture version",
"description": "ARM architecture version to use for disassembly.",
"items": [
{
"value": "auto",
"name": "Auto"
},
{
"value": "v4t",
"name": "ARMv4T (GBA)"
},
{
"value": "v5te",
"name": "ARMv5TE (DS)"
},
{
"value": "v6k",
"name": "ARMv6K (3DS)"
}
]
},
{
"id": "arm.unifiedSyntax",
"type": "boolean",
"default": false,
"name": "Unified syntax",
"description": "Disassemble as unified assembly language (UAL)."
},
{
"id": "arm.avRegisters",
"type": "boolean",
"default": false,
"name": "Use A/V registers",
"description": "Display R0-R3 as A1-A4 and R4-R11 as V1-V8."
},
{
"id": "arm.r9Usage",
"type": "choice",
"default": "generalPurpose",
"name": "Display R9 as",
"items": [
{
"value": "generalPurpose",
"name": "R9 or V6",
"description": "Use R9 as a general-purpose register."
},
{
"value": "sb",
"name": "SB (static base)",
"description": "Used for position-independent data (PID)."
},
{
"value": "tr",
"name": "TR (TLS register)",
"description": "Used for thread-local storage."
}
]
},
{
"id": "arm.slUsage",
"type": "boolean",
"default": false,
"name": "Display R10 as SL",
"description": "Used for explicit stack limits."
},
{
"id": "arm.fpUsage",
"type": "boolean",
"default": false,
"name": "Display R11 as FP",
"description": "Used for frame pointers."
},
{
"id": "arm.ipUsage",
"type": "boolean",
"default": false,
"name": "Display R12 as IP",
"description": "Used for interworking and long branches."
},
{
"id": "mips.abi",
"type": "choice",
"default": "auto",
"name": "ABI",
"description": "MIPS ABI to use for disassembly.",
"items": [
{
"value": "auto",
"name": "Auto"
},
{
"value": "o32",
"name": "O32"
},
{
"value": "n32",
"name": "N32"
},
{
"value": "n64",
"name": "N64"
}
]
},
{
"id": "mips.instrCategory",
"type": "choice",
"default": "auto",
"name": "Instruction category",
"description": "MIPS instruction category to use for disassembly.",
"items": [
{
"value": "auto",
"name": "Auto"
},
{
"value": "cpu",
"name": "CPU"
},
{
"value": "rsp",
"name": "RSP (N64)"
},
{
"value": "r3000gte",
"name": "R3000 GTE (PS1)"
},
{
"value": "r4000allegrex",
"name": "R4000 ALLEGREX (PSP)"
},
{
"value": "r5900",
"name": "R5900 EE (PS2)"
}
]
},
{
"id": "mips.registerPrefix",
"type": "boolean",
"default": false,
"name": "Register '$' prefix",
"description": "Display MIPS register names with a '$' prefix."
},
{
"id": "ppc.calculatePoolRelocations",
"type": "boolean",
"default": true,
"name": "Calculate pooled data references",
"description": "Display pooled data references in functions as fake relocations."
},
{
"id": "x86.formatter",
"type": "choice",
"default": "intel",
"name": "Format",
"description": "x86 disassembly syntax.",
"items": [
{
"value": "intel",
"name": "Intel"
},
{
"value": "gas",
"name": "AT&T"
},
{
"value": "nasm",
"name": "NASM"
},
{
"value": "masm",
"name": "MASM"
}
]
}
],
"groups": [
{
"id": "general",
"name": "General",
"properties": [
"functionRelocDiffs",
"spaceBetweenArgs",
"combineDataSections",
"combineTextSections"
]
},
{
"id": "arm",
"name": "ARM",
"properties": [
"arm.archVersion",
"arm.unifiedSyntax",
"arm.avRegisters",
"arm.r9Usage",
"arm.slUsage",
"arm.fpUsage",
"arm.ipUsage"
]
},
{
"id": "mips",
"name": "MIPS",
"properties": [
"mips.abi",
"mips.instrCategory",
"mips.registerPrefix"
]
},
{
"id": "ppc",
"name": "PowerPC",
"properties": [
"ppc.calculatePoolRelocations"
]
},
{
"id": "x86",
"name": "x86",
"properties": [
"x86.formatter"
]
}
]
}

500
objdiff-core/config_gen.rs Normal file
View File

@@ -0,0 +1,500 @@
use std::{
fs::File,
path::{Path, PathBuf},
};
use heck::{ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
#[derive(Debug, serde::Deserialize)]
pub struct ConfigSchema {
pub properties: Vec<ConfigProperty>,
pub groups: Vec<ConfigGroup>,
}
#[derive(Debug, serde::Deserialize)]
#[serde(tag = "type")]
pub enum ConfigProperty {
#[serde(rename = "boolean")]
Boolean(ConfigPropertyBoolean),
#[serde(rename = "choice")]
Choice(ConfigPropertyChoice),
}
#[derive(Debug, serde::Deserialize)]
pub struct ConfigPropertyBase {
pub id: String,
pub name: String,
pub description: Option<String>,
}
#[derive(Debug, serde::Deserialize)]
pub struct ConfigPropertyBoolean {
#[serde(flatten)]
pub base: ConfigPropertyBase,
pub default: bool,
}
#[derive(Debug, serde::Deserialize)]
pub struct ConfigPropertyChoice {
#[serde(flatten)]
pub base: ConfigPropertyBase,
pub default: String,
pub items: Vec<ConfigPropertyChoiceItem>,
}
#[derive(Debug, serde::Deserialize)]
pub struct ConfigPropertyChoiceItem {
pub value: String,
pub name: String,
pub description: Option<String>,
}
#[derive(Debug, serde::Deserialize)]
pub struct ConfigGroup {
pub id: String,
pub name: String,
pub description: Option<String>,
pub properties: Vec<String>,
}
fn build_doc(name: &str, description: Option<&str>) -> TokenStream {
let mut doc = format!(" {}", name);
let mut out = quote! { #[doc = #doc] };
if let Some(description) = description {
doc = format!(" {}", description);
out.extend(quote! { #[doc = ""] });
out.extend(quote! { #[doc = #doc] });
}
out
}
pub fn generate_diff_config() {
let schema_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("config-schema.json");
println!("cargo:rerun-if-changed={}", schema_path.display());
let schema_file = File::open(schema_path).expect("Failed to open config schema file");
let schema: ConfigSchema =
serde_json::from_reader(schema_file).expect("Failed to parse config schema");
let mut enums = TokenStream::new();
for property in &schema.properties {
let ConfigProperty::Choice(choice) = property else {
continue;
};
let enum_ident = format_ident!("{}", choice.base.id.to_upper_camel_case());
let mut variants = TokenStream::new();
let mut full_variants = TokenStream::new();
let mut variant_info = TokenStream::new();
let mut variant_to_str = TokenStream::new();
let mut variant_to_name = TokenStream::new();
let mut variant_to_description = TokenStream::new();
let mut variant_from_str = TokenStream::new();
for item in &choice.items {
let variant_name = item.value.to_upper_camel_case();
let variant_ident = format_ident!("{}", variant_name);
let is_default = item.value == choice.default;
variants.extend(build_doc(&item.name, item.description.as_deref()));
if is_default {
variants.extend(quote! { #[default] });
}
let value = &item.value;
variants.extend(quote! {
#[cfg_attr(feature = "serde", serde(rename = #value, alias = #variant_name))]
#variant_ident,
});
full_variants.extend(quote! { #enum_ident::#variant_ident, });
variant_to_str.extend(quote! { #enum_ident::#variant_ident => #value, });
let name = &item.name;
variant_to_name.extend(quote! { #enum_ident::#variant_ident => #name, });
if let Some(description) = &item.description {
variant_to_description.extend(quote! {
#enum_ident::#variant_ident => Some(#description),
});
} else {
variant_to_description.extend(quote! {
#enum_ident::#variant_ident => None,
});
}
let description = if let Some(description) = &item.description {
quote! { Some(#description) }
} else {
quote! { None }
};
variant_info.extend(quote! {
ConfigEnumVariantInfo {
value: #value,
name: #name,
description: #description,
is_default: #is_default,
},
});
variant_from_str.extend(quote! {
if s.eq_ignore_ascii_case(#value) { return Ok(#enum_ident::#variant_ident); }
});
}
enums.extend(quote! {
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum #enum_ident {
#variants
}
impl ConfigEnum for #enum_ident {
#[inline]
fn variants() -> &'static [Self] {
static VARIANTS: &[#enum_ident] = &[#full_variants];
VARIANTS
}
#[inline]
fn variant_info() -> &'static [ConfigEnumVariantInfo] {
static VARIANT_INFO: &[ConfigEnumVariantInfo] = &[
#variant_info
];
VARIANT_INFO
}
fn as_str(&self) -> &'static str {
match self {
#variant_to_str
}
}
fn name(&self) -> &'static str {
match self {
#variant_to_name
}
}
fn description(&self) -> Option<&'static str> {
match self {
#variant_to_description
}
}
}
impl core::str::FromStr for #enum_ident {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
#variant_from_str
Err(())
}
}
});
}
let mut groups = TokenStream::new();
let mut group_idents = Vec::new();
for group in &schema.groups {
let ident = format_ident!("CONFIG_GROUP_{}", group.id.to_shouty_snake_case());
let id = &group.id;
let name = &group.name;
let description = if let Some(description) = &group.description {
quote! { Some(#description) }
} else {
quote! { None }
};
let properties =
group.properties.iter().map(|p| format_ident!("{}", p.to_upper_camel_case()));
groups.extend(quote! {
ConfigPropertyGroup {
id: #id,
name: #name,
description: #description,
properties: &[#(ConfigPropertyId::#properties,)*],
},
});
group_idents.push(ident);
}
let mut property_idents = Vec::new();
let mut property_variants = TokenStream::new();
let mut variant_info = TokenStream::new();
let mut config_property_id_to_str = TokenStream::new();
let mut config_property_id_to_name = TokenStream::new();
let mut config_property_id_to_description = TokenStream::new();
let mut config_property_id_to_kind = TokenStream::new();
let mut property_fields = TokenStream::new();
let mut default_fields = TokenStream::new();
let mut get_property_value_variants = TokenStream::new();
let mut set_property_value_variants = TokenStream::new();
let mut set_property_value_str_variants = TokenStream::new();
let mut config_property_id_from_str = TokenStream::new();
for property in &schema.properties {
let base = match property {
ConfigProperty::Boolean(b) => &b.base,
ConfigProperty::Choice(c) => &c.base,
};
let id = &base.id;
let enum_ident = format_ident!("{}", id.to_upper_camel_case());
property_idents.push(enum_ident.clone());
config_property_id_to_str.extend(quote! { Self::#enum_ident => #id, });
let name = &base.name;
config_property_id_to_name.extend(quote! { Self::#enum_ident => #name, });
if let Some(description) = &base.description {
config_property_id_to_description.extend(quote! {
Self::#enum_ident => Some(#description),
});
} else {
config_property_id_to_description.extend(quote! {
Self::#enum_ident => None,
});
}
let doc = build_doc(name, base.description.as_deref());
property_variants.extend(quote! { #doc #enum_ident, });
property_fields.extend(doc);
let field_ident = format_ident!("{}", id.to_snake_case());
match property {
ConfigProperty::Boolean(b) => {
let default = b.default;
if default {
property_fields.extend(quote! {
#[cfg_attr(feature = "serde", serde(default = "default_true"))]
});
}
property_fields.extend(quote! {
pub #field_ident: bool,
});
default_fields.extend(quote! {
#field_ident: #default,
});
}
ConfigProperty::Choice(_) => {
property_fields.extend(quote! {
pub #field_ident: #enum_ident,
});
default_fields.extend(quote! {
#field_ident: #enum_ident::default(),
});
}
}
let property_value = match property {
ConfigProperty::Boolean(_) => {
quote! { ConfigPropertyValue::Boolean(self.#field_ident) }
}
ConfigProperty::Choice(_) => {
quote! { ConfigPropertyValue::Choice(self.#field_ident.as_str()) }
}
};
get_property_value_variants.extend(quote! {
ConfigPropertyId::#enum_ident => #property_value,
});
match property {
ConfigProperty::Boolean(_) => {
set_property_value_variants.extend(quote! {
ConfigPropertyId::#enum_ident => {
if let ConfigPropertyValue::Boolean(value) = value {
self.#field_ident = value;
Ok(())
} else {
Err(())
}
},
});
set_property_value_str_variants.extend(quote! {
ConfigPropertyId::#enum_ident => {
if let Ok(value) = value.parse() {
self.#field_ident = value;
Ok(())
} else {
Err(())
}
},
});
}
ConfigProperty::Choice(_) => {
set_property_value_variants.extend(quote! {
ConfigPropertyId::#enum_ident => {
if let ConfigPropertyValue::Choice(value) = value {
if let Ok(value) = value.parse() {
self.#field_ident = value;
Ok(())
} else {
Err(())
}
} else {
Err(())
}
},
});
set_property_value_str_variants.extend(quote! {
ConfigPropertyId::#enum_ident => {
if let Ok(value) = value.parse() {
self.#field_ident = value;
Ok(())
} else {
Err(())
}
},
});
}
}
let description = if let Some(description) = &base.description {
quote! { Some(#description) }
} else {
quote! { None }
};
variant_info.extend(quote! {
ConfigEnumVariantInfo {
value: #id,
name: #name,
description: #description,
is_default: false,
},
});
match property {
ConfigProperty::Boolean(_) => {
config_property_id_to_kind.extend(quote! {
Self::#enum_ident => ConfigPropertyKind::Boolean,
});
}
ConfigProperty::Choice(_) => {
config_property_id_to_kind.extend(quote! {
Self::#enum_ident => ConfigPropertyKind::Choice(#enum_ident::variant_info()),
});
}
}
let snake_id = id.to_snake_case();
config_property_id_from_str.extend(quote! {
if s.eq_ignore_ascii_case(#id) || s.eq_ignore_ascii_case(#snake_id) {
return Ok(Self::#enum_ident);
}
});
}
let tokens = quote! {
pub trait ConfigEnum: Sized {
fn variants() -> &'static [Self];
fn variant_info() -> &'static [ConfigEnumVariantInfo];
fn as_str(&self) -> &'static str;
fn name(&self) -> &'static str;
fn description(&self) -> Option<&'static str>;
}
#[derive(Clone, Debug)]
pub struct ConfigEnumVariantInfo {
pub value: &'static str,
pub name: &'static str,
pub description: Option<&'static str>,
pub is_default: bool,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum ConfigPropertyId {
#property_variants
}
impl ConfigEnum for ConfigPropertyId {
#[inline]
fn variants() -> &'static [Self] {
static VARIANTS: &[ConfigPropertyId] = &[#(ConfigPropertyId::#property_idents,)*];
VARIANTS
}
#[inline]
fn variant_info() -> &'static [ConfigEnumVariantInfo] {
static VARIANT_INFO: &[ConfigEnumVariantInfo] = &[
#variant_info
];
VARIANT_INFO
}
fn as_str(&self) -> &'static str {
match self {
#config_property_id_to_str
}
}
fn name(&self) -> &'static str {
match self {
#config_property_id_to_name
}
}
fn description(&self) -> Option<&'static str> {
match self {
#config_property_id_to_description
}
}
}
impl ConfigPropertyId {
pub fn kind(&self) -> ConfigPropertyKind {
match self {
#config_property_id_to_kind
}
}
}
impl core::str::FromStr for ConfigPropertyId {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
#config_property_id_from_str
Err(())
}
}
#[derive(Clone, Debug)]
pub struct ConfigPropertyGroup {
pub id: &'static str,
pub name: &'static str,
pub description: Option<&'static str>,
pub properties: &'static [ConfigPropertyId],
}
pub static CONFIG_GROUPS: &[ConfigPropertyGroup] = &[#groups];
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum ConfigPropertyValue {
Boolean(bool),
Choice(&'static str),
}
impl ConfigPropertyValue {
#[cfg(feature = "serde")]
pub fn to_json(&self) -> serde_json::Value {
match self {
ConfigPropertyValue::Boolean(value) => serde_json::Value::Bool(*value),
ConfigPropertyValue::Choice(value) => serde_json::Value::String(value.to_string()),
}
}
}
impl core::fmt::Display for ConfigPropertyValue {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ConfigPropertyValue::Boolean(value) => write!(f, "{}", value),
ConfigPropertyValue::Choice(value) => write!(f, "{}", value),
}
}
}
#[derive(Clone, Debug)]
pub enum ConfigPropertyKind {
Boolean,
Choice(&'static [ConfigEnumVariantInfo]),
}
#enums
#[cfg(feature = "serde")]
#[inline(always)]
fn default_true() -> bool { true }
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(default))]
pub struct DiffObjConfig {
#property_fields
}
impl Default for DiffObjConfig {
fn default() -> Self {
Self {
#default_fields
}
}
}
impl DiffObjConfig {
pub fn get_property_value(&self, id: ConfigPropertyId) -> ConfigPropertyValue {
match id {
#get_property_value_variants
}
}
#[allow(clippy::result_unit_err)]
pub fn set_property_value(&mut self, id: ConfigPropertyId, value: ConfigPropertyValue) -> Result<(), ()> {
match id {
#set_property_value_variants
}
}
#[allow(clippy::result_unit_err)]
pub fn set_property_value_str(&mut self, id: ConfigPropertyId, value: &str) -> Result<(), ()> {
match id {
#set_property_value_str_variants
}
}
}
};
let file = syn::parse2(tokens).unwrap();
let formatted = prettyplease::unparse(&file);
std::fs::write(
PathBuf::from(std::env::var_os("OUT_DIR").unwrap()).join("config.gen.rs"),
formatted,
)
.unwrap();
}

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

@@ -21,9 +21,9 @@ enum SymbolFlag {
SYMBOL_NONE = 0;
SYMBOL_GLOBAL = 1;
SYMBOL_LOCAL = 2;
SYMBOL_WEAK = 3;
SYMBOL_COMMON = 4;
SYMBOL_HIDDEN = 5;
SYMBOL_WEAK = 4;
SYMBOL_COMMON = 8;
SYMBOL_HIDDEN = 16;
}
// A single parsed instruction
@@ -87,11 +87,11 @@ message Relocation {
}
message RelocationTarget {
Symbol symbol = 1;
uint32 symbol_index = 1;
int64 addend = 2;
}
message InstructionDiff {
message InstructionDiffRow {
DiffKind diff_kind = 1;
optional Instruction instruction = 2;
optional InstructionBranchFrom branch_from = 3;
@@ -122,10 +122,12 @@ message InstructionBranchTo {
uint32 branch_index = 2;
}
message FunctionDiff {
message SymbolDiff {
Symbol symbol = 1;
repeated InstructionDiff instructions = 2;
repeated InstructionDiffRow instruction_rows = 2;
optional float match_percent = 3;
// The symbol index in the _other_ object that this symbol was diffed against
optional uint32 target_symbol = 5;
}
message DataDiff {
@@ -140,7 +142,7 @@ message SectionDiff {
SectionKind kind = 2;
uint64 size = 3;
uint64 address = 4;
repeated FunctionDiff functions = 5;
reserved 5;
repeated DataDiff data = 6;
optional float match_percent = 7;
}
@@ -150,11 +152,11 @@ enum SectionKind {
SECTION_TEXT = 1;
SECTION_DATA = 2;
SECTION_BSS = 3;
SECTION_COMMON = 4;
}
message ObjectDiff {
repeated SectionDiff sections = 1;
repeated SymbolDiff symbols = 2;
}
message DiffResult {

View File

@@ -2,6 +2,18 @@ syntax = "proto3";
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
message Measures {
// Overall match percent, including partially matched functions and data
@@ -38,18 +50,6 @@ message Measures {
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 {
// The ID of the category
string id = 1;
@@ -108,57 +108,3 @@ message ReportItemMetadata {
// The virtual address of the function or section
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,39 +1,37 @@
use std::{
borrow::Cow,
collections::{BTreeMap, HashMap},
use alloc::{
collections::BTreeMap,
format,
string::{String, ToString},
vec::Vec,
};
use anyhow::{bail, Result};
use arm_attr::{enums::CpuArch, tag::Tag, BuildAttrs};
use object::{
elf::{self, SHT_ARM_ATTRIBUTES},
Endian, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, SectionIndex,
SectionKind, Symbol, SymbolKind,
};
use unarm::{
args::{Argument, OffsetImm, OffsetReg, Register},
parse::{ArmVersion, ParseMode, Parser},
DisplayOptions, ParseFlags, ParsedIns, RegNames,
};
use anyhow::{Result, bail};
use arm_attr::{BuildAttrs, enums::CpuArch, tag::Tag};
use object::{Endian as _, Object as _, ObjectSection as _, ObjectSymbol as _, elf};
use unarm::{args, arm, thumb};
use crate::{
arch::{ObjArch, ProcessCodeResult},
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
arch::Arch,
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig, display::InstructionPart},
obj::{
InstructionRef, RelocationFlags, ResolvedInstructionRef, ResolvedRelocation,
ScannedInstruction, 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
disasm_modes: HashMap<SectionIndex, Vec<DisasmMode>>,
detected_version: Option<ArmVersion>,
disasm_modes: BTreeMap<usize, Vec<DisasmMode>>,
detected_version: Option<unarm::ArmVersion>,
endianness: object::Endianness,
}
impl ObjArchArm {
pub fn new(file: &File) -> Result<Self> {
impl ArchArm {
pub fn new(file: &object::File) -> Result<Self> {
let endianness = file.endianness();
match file {
File::Elf32(_) => {
object::File::Elf32(_) => {
let disasm_modes = Self::elf_get_mapping_symbols(file);
let detected_version = Self::elf_detect_arm_version(file)?;
Ok(Self { disasm_modes, detected_version, endianness })
@@ -42,10 +40,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
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 build_attrs = BuildAttrs::new(&attr_data, match file.endianness() {
@@ -59,16 +58,12 @@ impl ObjArchArm {
}
// Only checking first CpuArch tag. Others may exist, but that's very unlikely.
let cpu_arch = subsection.into_public_tag_iter()?.find_map(|(_, tag)| {
if let Tag::CpuArch(cpu_arch) = tag {
Some(cpu_arch)
} else {
None
}
if let Tag::CpuArch(cpu_arch) = tag { Some(cpu_arch) } else { None }
});
match cpu_arch {
Some(CpuArch::V4T) => return Ok(Some(ArmVersion::V4T)),
Some(CpuArch::V5TE) => return Ok(Some(ArmVersion::V5Te)),
Some(CpuArch::V6K) => return Ok(Some(ArmVersion::V6K)),
Some(CpuArch::V4T) => return Ok(Some(unarm::ArmVersion::V4T)),
Some(CpuArch::V5TE) => return Ok(Some(unarm::ArmVersion::V5Te)),
Some(CpuArch::V6K) => return Ok(Some(unarm::ArmVersion::V6K)),
Some(arch) => bail!("ARM arch {} not supported", arch),
None => {}
};
@@ -78,9 +73,9 @@ impl ObjArchArm {
Ok(None)
}
fn elf_get_mapping_symbols(file: &File) -> HashMap<SectionIndex, Vec<DisasmMode>> {
fn elf_get_mapping_symbols(file: &object::File) -> BTreeMap<usize, Vec<DisasmMode>> {
file.sections()
.filter(|s| s.kind() == SectionKind::Text)
.filter(|s| s.kind() == object::SectionKind::Text)
.map(|s| {
let index = s.index();
let mut mapping_symbols: Vec<_> = file
@@ -89,39 +84,102 @@ impl ObjArchArm {
.filter_map(|s| DisasmMode::from_symbol(&s))
.collect();
mapping_symbols.sort_unstable_by_key(|x| x.address);
(s.index(), mapping_symbols)
(s.index().0, mapping_symbols)
})
.collect()
}
}
impl ObjArch for ObjArchArm {
fn symbol_address(&self, symbol: &Symbol) -> u64 {
let address = symbol.address();
if symbol.kind() == SymbolKind::Text {
address & !1
} else {
address
fn endian(&self) -> unarm::Endian {
match self.endianness {
object::Endianness::Little => unarm::Endian::Little,
object::Endianness::Big => unarm::Endian::Big,
}
}
fn process_code(
fn parse_flags(&self, diff_config: &DiffObjConfig) -> unarm::ParseFlags {
unarm::ParseFlags {
ual: diff_config.arm_unified_syntax,
version: match diff_config.arm_arch_version {
ArmArchVersion::Auto => self.detected_version.unwrap_or(unarm::ArmVersion::V5Te),
ArmArchVersion::V4t => unarm::ArmVersion::V4T,
ArmArchVersion::V5te => unarm::ArmVersion::V5Te,
ArmArchVersion::V6k => unarm::ArmVersion::V6K,
},
}
}
fn display_options(&self, diff_config: &DiffObjConfig) -> unarm::DisplayOptions {
unarm::DisplayOptions {
reg_names: unarm::RegNames {
av_registers: diff_config.arm_av_registers,
r9_use: match diff_config.arm_r9_usage {
ArmR9Usage::GeneralPurpose => unarm::R9Use::GeneralPurpose,
ArmR9Usage::Sb => unarm::R9Use::Pid,
ArmR9Usage::Tr => unarm::R9Use::Tls,
},
explicit_stack_limit: diff_config.arm_sl_usage,
frame_pointer: diff_config.arm_fp_usage,
ip: diff_config.arm_ip_usage,
},
}
}
fn parse_ins_ref(
&self,
ins_ref: InstructionRef,
code: &[u8],
diff_config: &DiffObjConfig,
) -> Result<(unarm::Ins, unarm::ParsedIns)> {
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));
if ins.is_half_bl() {
todo!("Combine thumb BL instructions");
} else {
(unarm::Ins::Thumb(ins), parsed)
}
};
Ok((ins, parsed_ins))
}
}
impl Arch for ArchArm {
fn scan_instructions(
&self,
address: u64,
code: &[u8],
section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>> {
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 fallback_mappings =
[DisasmMode { address: start_addr, mapping: unarm::ParseMode::Arm }];
let mapping_symbols = self
.disasm_modes
.get(&SectionIndex(section_index))
.get(&section_index)
.map(|x| x.as_slice())
.unwrap_or(&fallback_mappings);
let first_mapping_idx = mapping_symbols
@@ -134,39 +192,14 @@ impl ObjArch for ObjArchArm {
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 mut ops = Vec::<ScannedInstruction>::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 endian = self.endian();
let parse_flags = self.parse_flags(diff_config);
let mut parser = unarm::Parser::new(first_mapping, start_addr, endian, parse_flags, code);
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::Sb => unarm::R9Use::Pid,
ArmR9Usage::Tr => unarm::R9Use::Tls,
},
explicit_stack_limit: config.arm_sl_usage,
frame_pointer: config.arm_fp_usage,
ip: config.arm_ip_usage,
},
};
while let Some((address, ins, parsed_ins)) = parser.next() {
while let Some((address, ins, _parsed_ins)) = parser.next() {
let size = parser.mode.instruction_size(address);
if let Some(next) = next_mapping {
let next_address = parser.address;
if next_address >= next.address {
@@ -175,82 +208,91 @@ impl ObjArch for ObjArchArm {
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 (opcode, branch_dest) = match ins {
unarm::Ins::Arm(x) => {
let opcode = x.op as u16 | (1 << 15);
let branch_dest = match x.op {
arm::Opcode::B | arm::Opcode::Bl => {
address.checked_add_signed(x.field_branch_offset())
}
arm::Opcode::BlxI => address.checked_add_signed(x.field_blx_offset()),
_ => None,
};
(opcode, branch_dest)
}
unarm::Ins::Thumb(x) => {
let opcode = x.op as u16;
let branch_dest = match x.op {
thumb::Opcode::B | thumb::Opcode::Bl => {
address.checked_add_signed(x.field_branch_offset_8())
}
thumb::Opcode::BLong => {
address.checked_add_signed(x.field_branch_offset_11())
}
_ => None,
};
(opcode, branch_dest)
}
unarm::Ins::Data => (u16::MAX, None),
};
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,
ops.push(ScannedInstruction {
ins_ref: InstructionRef { address: address as u64, size: size as u8, opcode },
branch_dest: branch_dest.map(|x| x as u64),
});
}
Ok(ProcessCodeResult { ops, insts })
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(
&parsed_ins,
resolved.relocation,
resolved.ins_ref.address as u32,
self.display_options(diff_config),
cb,
)?;
}
Ok(())
}
fn implcit_addend(
&self,
_file: &File<'_>,
section: &ObjSection,
_file: &object::File<'_>,
section: &object::Section,
address: u64,
reloc: &Relocation,
_relocation: &object::Relocation,
flags: RelocationFlags,
) -> Result<i64> {
let section_data = section.data()?;
let address = address as usize;
Ok(match reloc.flags() {
Ok(match flags {
// ARM calls
RelocationFlags::Elf { r_type: elf::R_ARM_PC24 }
| RelocationFlags::Elf { r_type: elf::R_ARM_XPC25 }
| RelocationFlags::Elf { r_type: elf::R_ARM_CALL } => {
let data = section.data[address..address + 4].try_into()?;
RelocationFlags::Elf(elf::R_ARM_PC24)
| RelocationFlags::Elf(elf::R_ARM_XPC25)
| RelocationFlags::Elf(elf::R_ARM_CALL) => {
let data = section_data[address..address + 4].try_into()?;
let addend = self.endianness.read_i32_bytes(data);
let imm24 = addend & 0xffffff;
(imm24 << 2) << 8 >> 8
}
// Thumb calls
RelocationFlags::Elf { r_type: elf::R_ARM_THM_PC22 }
| RelocationFlags::Elf { r_type: elf::R_ARM_THM_XPC22 } => {
let data = section.data[address..address + 2].try_into()?;
RelocationFlags::Elf(elf::R_ARM_THM_PC22)
| RelocationFlags::Elf(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 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);
@@ -258,8 +300,8 @@ impl ObjArch for ObjArchArm {
}
// Data
RelocationFlags::Elf { r_type: elf::R_ARM_ABS32 } => {
let data = section.data[address..address + 4].try_into()?;
RelocationFlags::Elf(elf::R_ARM_ABS32) => {
let data = section_data[address..address + 4].try_into()?;
self.endianness.read_i32_bytes(data)
}
@@ -273,51 +315,98 @@ impl ObjArch for ObjArchArm {
.and_then(|s| s.demangle(&cpp_demangle::DemangleOptions::default()).ok())
}
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
Cow::Owned(format!("<{flags:?}>"))
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
match flags {
RelocationFlags::Elf(r_type) => match r_type {
elf::R_ARM_NONE => Some("R_ARM_NONE"),
elf::R_ARM_ABS32 => Some("R_ARM_ABS32"),
elf::R_ARM_REL32 => Some("R_ARM_REL32"),
elf::R_ARM_ABS16 => Some("R_ARM_ABS16"),
elf::R_ARM_ABS8 => Some("R_ARM_ABS8"),
elf::R_ARM_THM_PC22 => Some("R_ARM_THM_PC22"),
elf::R_ARM_THM_XPC22 => Some("R_ARM_THM_XPC22"),
elf::R_ARM_PC24 => Some("R_ARM_PC24"),
elf::R_ARM_XPC25 => Some("R_ARM_XPC25"),
elf::R_ARM_CALL => Some("R_ARM_CALL"),
_ => None,
},
_ => None,
}
}
fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
match flags {
RelocationFlags::Elf(r_type) => match r_type {
elf::R_ARM_NONE => 0,
elf::R_ARM_ABS32 => 4,
elf::R_ARM_REL32 => 4,
elf::R_ARM_ABS16 => 2,
elf::R_ARM_ABS8 => 1,
elf::R_ARM_THM_PC22 => 4,
elf::R_ARM_THM_XPC22 => 4,
elf::R_ARM_PC24 => 4,
elf::R_ARM_XPC25 => 4,
elf::R_ARM_CALL => 4,
_ => 1,
},
_ => 1,
}
}
fn symbol_address(&self, address: u64, kind: SymbolKind) -> u64 {
if kind == SymbolKind::Function { address & !1 } else { address }
}
fn extra_symbol_flags(&self, symbol: &object::Symbol) -> SymbolFlagSet {
let mut flags = SymbolFlagSet::default();
if DisasmMode::from_symbol(symbol).is_some() {
flags |= SymbolFlag::Hidden;
}
flags
}
}
#[derive(Clone, Copy, Debug)]
struct DisasmMode {
address: u32,
mapping: ParseMode,
mapping: unarm::ParseMode,
}
impl DisasmMode {
fn from_symbol<'a>(sym: &Symbol<'a, '_, &'a [u8]>) -> Option<Self> {
if let Ok(name) = sym.name() {
ParseMode::from_mapping_symbol(name)
.map(|mapping| DisasmMode { address: sym.address() as u32, mapping })
} else {
None
}
fn from_symbol<'a>(sym: &object::Symbol<'a, '_, &'a [u8]>) -> Option<Self> {
sym.name()
.ok()
.and_then(unarm::ParseMode::from_mapping_symbol)
.map(|mapping| DisasmMode { address: sym.address() as u32, mapping })
}
}
fn push_args(
parsed_ins: &ParsedIns,
config: &DiffObjConfig,
reloc_arg: Option<usize>,
parsed_ins: &unarm::ParsedIns,
relocation: Option<ResolvedRelocation>,
cur_addr: u32,
display_options: DisplayOptions,
) -> Result<(Vec<ObjInsArg>, Option<u64>)> {
let mut args = vec![];
let mut branch_dest = None;
display_options: unarm::DisplayOptions,
mut arg_cb: impl FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
let reloc_arg = find_reloc_arg(parsed_ins, relocation);
let mut writeback = false;
let mut deref = false;
for (i, arg) in parsed_ins.args_iter().enumerate() {
// Emit punctuation before separator
if deref {
match arg {
Argument::OffsetImm(OffsetImm { post_indexed: true, value: _ })
| Argument::OffsetReg(OffsetReg { add: _, post_indexed: true, reg: _ })
| Argument::CoOption(_) => {
args::Argument::OffsetImm(args::OffsetImm { post_indexed: true, value: _ })
| args::Argument::OffsetReg(args::OffsetReg {
add: _,
post_indexed: true,
reg: _,
})
| args::Argument::CoOption(_) => {
deref = false;
args.push(ObjInsArg::PlainText("]".into()));
arg_cb(InstructionPart::basic("]"))?;
if writeback {
writeback = false;
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
arg_cb(InstructionPart::opaque("!"))?;
}
}
_ => {}
@@ -325,117 +414,143 @@ fn push_args(
}
if i > 0 {
args.push(ObjInsArg::PlainText(config.separator().into()));
arg_cb(InstructionPart::separator())?;
}
if reloc_arg == Some(i) {
args.push(ObjInsArg::Reloc);
arg_cb(InstructionPart::reloc())?;
} else {
match arg {
Argument::None => {}
Argument::Reg(reg) => {
args::Argument::None => {}
args::Argument::Reg(reg) => {
if reg.deref {
deref = true;
args.push(ObjInsArg::PlainText("[".into()));
arg_cb(InstructionPart::basic("["))?;
}
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
reg.reg.display(display_options.reg_names).to_string().into(),
)));
arg_cb(InstructionPart::opaque(
reg.reg.display(display_options.reg_names).to_string(),
))?;
if reg.writeback {
if reg.deref {
writeback = true;
} else {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
arg_cb(InstructionPart::opaque("!"))?;
}
}
}
Argument::RegList(reg_list) => {
args.push(ObjInsArg::PlainText("{".into()));
args::Argument::RegList(reg_list) => {
arg_cb(InstructionPart::basic("{"))?;
let mut first = true;
for i in 0..16 {
if (reg_list.regs & (1 << i)) != 0 {
if !first {
args.push(ObjInsArg::PlainText(config.separator().into()));
arg_cb(InstructionPart::separator())?;
}
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
Register::parse(i)
arg_cb(InstructionPart::opaque(
args::Register::parse(i)
.display(display_options.reg_names)
.to_string()
.into(),
)));
.to_string(),
))?;
first = false;
}
}
args.push(ObjInsArg::PlainText("}".into()));
arg_cb(InstructionPart::basic("}"))?;
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.push(ObjInsArg::PlainText("#".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64)));
args::Argument::UImm(value)
| args::Argument::CoOpcode(value)
| args::Argument::SatImm(value) => {
arg_cb(InstructionPart::basic("#"))?;
arg_cb(InstructionPart::unsigned(*value))?;
}
Argument::SImm(value)
| Argument::OffsetImm(OffsetImm { post_indexed: _, value }) => {
args.push(ObjInsArg::PlainText("#".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(*value as i64)));
args::Argument::SImm(value)
| args::Argument::OffsetImm(args::OffsetImm { post_indexed: _, value }) => {
arg_cb(InstructionPart::basic("#"))?;
arg_cb(InstructionPart::signed(*value))?;
}
Argument::BranchDest(value) => {
let dest = cur_addr.wrapping_add_signed(*value) as u64;
args.push(ObjInsArg::BranchDest(dest));
branch_dest = Some(dest);
args::Argument::BranchDest(value) => {
arg_cb(InstructionPart::branch_dest(cur_addr.wrapping_add_signed(*value)))?;
}
Argument::CoOption(value) => {
args.push(ObjInsArg::PlainText("{".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(*value as u64)));
args.push(ObjInsArg::PlainText("}".into()));
args::Argument::CoOption(value) => {
arg_cb(InstructionPart::basic("{"))?;
arg_cb(InstructionPart::unsigned(*value))?;
arg_cb(InstructionPart::basic("}"))?;
}
Argument::CoprocNum(value) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(format!("p{}", value).into())));
args::Argument::CoprocNum(value) => {
arg_cb(InstructionPart::opaque(format!("p{}", value)))?;
}
Argument::ShiftImm(shift) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into())));
args.push(ObjInsArg::PlainText(" #".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(shift.imm as u64)));
args::Argument::ShiftImm(shift) => {
arg_cb(InstructionPart::opaque(shift.op.to_string()))?;
arg_cb(InstructionPart::basic(" #"))?;
arg_cb(InstructionPart::unsigned(shift.imm))?;
}
Argument::ShiftReg(shift) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(shift.op.to_string().into())));
args.push(ObjInsArg::PlainText(" ".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
shift.reg.display(display_options.reg_names).to_string().into(),
)));
args::Argument::ShiftReg(shift) => {
arg_cb(InstructionPart::opaque(shift.op.to_string()))?;
arg_cb(InstructionPart::basic(" "))?;
arg_cb(InstructionPart::opaque(
shift.reg.display(display_options.reg_names).to_string(),
))?;
}
Argument::OffsetReg(offset) => {
args::Argument::OffsetReg(offset) => {
if !offset.add {
args.push(ObjInsArg::PlainText("-".into()));
arg_cb(InstructionPart::basic("-"))?;
}
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
offset.reg.display(display_options.reg_names).to_string().into(),
)));
arg_cb(InstructionPart::opaque(
offset.reg.display(display_options.reg_names).to_string(),
))?;
}
Argument::CpsrMode(mode) => {
args.push(ObjInsArg::PlainText("#".into()));
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(mode.mode as u64)));
args::Argument::CpsrMode(mode) => {
arg_cb(InstructionPart::basic("#"))?;
arg_cb(InstructionPart::unsigned(mode.mode))?;
if mode.writeback {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
arg_cb(InstructionPart::opaque("!"))?;
}
}
Argument::CoReg(_)
| Argument::StatusReg(_)
| Argument::StatusMask(_)
| Argument::Shift(_)
| Argument::CpsrFlags(_)
| Argument::Endian(_) => args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
arg.display(display_options, None).to_string().into(),
))),
args::Argument::CoReg(_)
| args::Argument::StatusReg(_)
| args::Argument::StatusMask(_)
| args::Argument::Shift(_)
| args::Argument::CpsrFlags(_)
| args::Argument::Endian(_) => {
arg_cb(InstructionPart::opaque(
arg.display(display_options, None).to_string(),
))?;
}
}
}
}
if deref {
args.push(ObjInsArg::PlainText("]".into()));
arg_cb(InstructionPart::basic("]"))?;
if writeback {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque("!".into())));
arg_cb(InstructionPart::opaque("!"))?;
}
}
Ok((args, branch_dest))
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
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +1,31 @@
use std::{borrow::Cow, collections::BTreeMap, sync::Mutex};
use alloc::{collections::BTreeMap, string::ToString, vec::Vec};
use core::ops::Range;
use anyhow::{anyhow, bail, Result};
use object::{
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::{IU16, ValuedOperand},
registers_meta::Register,
};
use rabbitizer::{config, Abi, InstrCategory, Instruction, OperandType};
use crate::{
arch::{ObjArch, ProcessCodeResult},
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
arch::Arch,
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory, display::InstructionPart},
obj::{
InstructionArg, InstructionArgValue, InstructionRef, Relocation, RelocationFlags,
ResolvedInstructionRef, ResolvedRelocation, ScannedInstruction,
},
};
static RABBITIZER_MUTEX: Mutex<()> = Mutex::new(());
fn configure_rabbitizer(abi: Abi) {
unsafe {
config::RabbitizerConfig_Cfg.reg_names.fpr_abi_names = abi;
}
}
pub struct ObjArchMips {
pub endianness: Endianness,
#[derive(Debug)]
pub struct ArchMips {
pub endianness: object::Endianness,
pub abi: Abi,
pub instr_category: InstrCategory,
pub isa_extension: Option<IsaExtension>,
pub ri_gp_value: i32,
pub paired_relocations: Vec<BTreeMap<u64, i64>>,
}
const EF_MIPS_ABI: u32 = 0x0000F000;
@@ -36,13 +36,13 @@ const EF_MIPS_MACH_5900: u32 = 0x00920000;
const R_MIPS15_S3: u32 = 119;
impl ObjArchMips {
pub fn new(object: &File) -> Result<Self> {
let mut abi = Abi::NUMERIC;
let mut instr_category = InstrCategory::CPU;
impl ArchMips {
pub fn new(object: &object::File) -> Result<Self> {
let mut abi = Abi::O32;
let mut isa_extension = None;
match object.flags() {
FileFlags::None => {}
FileFlags::Elf { e_flags, .. } => {
object::FileFlags::None => {}
object::FileFlags::Elf { e_flags, .. } => {
abi = match e_flags & EF_MIPS_ABI {
elf::EF_MIPS_ABI_O32 | elf::EF_MIPS_ABI_O64 => Abi::O32,
elf::EF_MIPS_ABI_EABI32 | elf::EF_MIPS_ABI_EABI64 => Abi::N32,
@@ -50,14 +50,14 @@ impl ObjArchMips {
if e_flags & elf::EF_MIPS_ABI2 != 0 {
Abi::N32
} else {
Abi::NUMERIC
Abi::O32
}
}
};
instr_category = match e_flags & EF_MIPS_MACH {
EF_MIPS_MACH_ALLEGREX => InstrCategory::R4000ALLEGREX,
EF_MIPS_MACH_5900 => InstrCategory::R5900,
_ => InstrCategory::CPU,
isa_extension = match e_flags & EF_MIPS_MACH {
EF_MIPS_MACH_ALLEGREX => Some(IsaExtension::R4000ALLEGREX),
EF_MIPS_MACH_5900 => Some(IsaExtension::R5900EE),
_ => None,
};
}
_ => bail!("Unsupported MIPS file flags"),
@@ -65,176 +65,186 @@ impl ObjArchMips {
// Parse the ri_gp_value stored in .reginfo to be able to correctly
// 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
.section_by_name(".reginfo")
.and_then(|section| section.data().ok())
.and_then(|data| data.get(0x14..0x18))
.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);
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 {
fn process_code(
&self,
address: u64,
code: &[u8],
section_index: usize,
relocations: &[ObjReloc],
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 })
}
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_isa(IsaVersion::MIPS_III, None),
}
.with_abi(match diff_config.mips_abi {
MipsAbi::Auto => self.abi,
MipsAbi::O32 => Abi::O32,
MipsAbi::N32 => Abi::N32,
MipsAbi::N64 => Abi::N64,
});
let instr_category = match config.mips_instr_category {
MipsInstrCategory::Auto => self.instr_category,
MipsInstrCategory::Cpu => InstrCategory::CPU,
MipsInstrCategory::Rsp => InstrCategory::RSP,
MipsInstrCategory::R3000Gte => InstrCategory::R3000GTE,
MipsInstrCategory::R4000Allegrex => InstrCategory::R4000ALLEGREX,
MipsInstrCategory::R5900 => InstrCategory::R5900,
};
})
}
let start_address = address;
let end_address = address + code.len() as u64;
let ins_count = code.len() / 4;
let mut ops = Vec::<u16>::with_capacity(ins_count);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
let mut cur_addr = start_address as u32;
fn instruction_display_flags(
&self,
diff_config: &DiffObjConfig,
) -> rabbitizer::InstructionDisplayFlags {
rabbitizer::InstructionDisplayFlags::default()
.with_unknown_instr_comment(false)
.with_use_dollar(diff_config.mips_register_prefix)
}
fn parse_ins_ref(
&self,
ins_ref: InstructionRef,
code: &[u8],
diff_config: &DiffObjConfig,
) -> Result<rabbitizer::Instruction> {
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(
&self,
address: u64,
code: &[u8],
_section_index: usize,
diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>> {
let instruction_flags = self.instruction_flags(diff_config);
let mut ops = Vec::<ScannedInstruction>::with_capacity(code.len() / 4);
let mut cur_addr = 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(),
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(ScannedInstruction {
ins_ref: InstructionRef { address: cur_addr as u64, size: 4, opcode },
branch_dest,
line,
formatted,
orig: None,
});
cur_addr += 4;
}
Ok(ProcessCodeResult { ops, insts })
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))?;
let start_address = resolved.symbol.address;
let function_range = start_address..start_address + resolved.symbol.size;
push_args(
&instruction,
resolved.relocation,
function_range,
resolved.section_index,
&display_flags,
cb,
)?;
Ok(())
}
fn implcit_addend(
&self,
file: &File<'_>,
section: &ObjSection,
file: &object::File<'_>,
section: &object::Section,
address: u64,
reloc: &Relocation,
reloc: &object::Relocation,
flags: RelocationFlags,
) -> 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
// Check for paired R_MIPS_HI16 and R_MIPS_LO16 relocations.
if let RelocationFlags::Elf(elf::R_MIPS_HI16 | elf::R_MIPS_LO16) = flags {
if let Some(addend) = self
.paired_relocations
.get(section.index().0)
.and_then(|m| m.get(&address).copied())
{
return Ok(addend);
}
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 {
}
let data = section.data()?;
let code = data[address as usize..address as usize + 4].try_into()?;
let addend = self.endianness.read_u32_bytes(code);
Ok(match flags {
RelocationFlags::Elf(elf::R_MIPS_32) => addend as i64,
RelocationFlags::Elf(elf::R_MIPS_26) => ((addend & 0x03FFFFFF) << 2) as i64,
RelocationFlags::Elf(elf::R_MIPS_HI16) => ((addend & 0x0000FFFF) << 16) as i32 as i64,
RelocationFlags::Elf(elf::R_MIPS_LO16 | elf::R_MIPS_GOT16 | elf::R_MIPS_CALL16) => {
(addend & 0x0000FFFF) as i16 as i64
}
RelocationFlags::Elf(elf::R_MIPS_GPREL16 | elf::R_MIPS_LITERAL) => {
let object::RelocationTarget::Symbol(idx) = reloc.target() else {
bail!("Unsupported R_MIPS_GPREL16 relocation against a non-symbol");
};
let sym = file.symbol_by_index(idx)?;
@@ -247,66 +257,174 @@ impl ObjArch for ObjArchMips {
(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,
RelocationFlags::Elf(elf::R_MIPS_PC16) => 0, // PC-relative relocation
RelocationFlags::Elf(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 reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
match flags {
RelocationFlags::Elf { r_type } => match r_type {
elf::R_MIPS_32 => Cow::Borrowed("R_MIPS_32"),
elf::R_MIPS_26 => Cow::Borrowed("R_MIPS_26"),
elf::R_MIPS_HI16 => Cow::Borrowed("R_MIPS_HI16"),
elf::R_MIPS_LO16 => Cow::Borrowed("R_MIPS_LO16"),
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"),
elf::R_MIPS_PC16 => Cow::Borrowed("R_MIPS_PC16"),
elf::R_MIPS_CALL16 => Cow::Borrowed("R_MIPS_CALL16"),
R_MIPS15_S3 => Cow::Borrowed("R_MIPS15_S3"),
_ => Cow::Owned(format!("<{flags:?}>")),
RelocationFlags::Elf(r_type) => match r_type {
elf::R_MIPS_NONE => Some("R_MIPS_NONE"),
elf::R_MIPS_16 => Some("R_MIPS_16"),
elf::R_MIPS_32 => Some("R_MIPS_32"),
elf::R_MIPS_26 => Some("R_MIPS_26"),
elf::R_MIPS_HI16 => Some("R_MIPS_HI16"),
elf::R_MIPS_LO16 => Some("R_MIPS_LO16"),
elf::R_MIPS_GPREL16 => Some("R_MIPS_GPREL16"),
elf::R_MIPS_LITERAL => Some("R_MIPS_LITERAL"),
elf::R_MIPS_GOT16 => Some("R_MIPS_GOT16"),
elf::R_MIPS_PC16 => Some("R_MIPS_PC16"),
elf::R_MIPS_CALL16 => Some("R_MIPS_CALL16"),
R_MIPS15_S3 => Some("R_MIPS15_S3"),
_ => None,
},
_ => Cow::Owned(format!("<{flags:?}>")),
_ => None,
}
}
fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
match flags {
RelocationFlags::Elf(r_type) => match r_type {
elf::R_MIPS_16 => 2,
elf::R_MIPS_32 => 4,
_ => 1,
},
_ => 1,
}
}
}
fn push_reloc(args: &mut Vec<ObjInsArg>, reloc: &ObjReloc) -> Result<()> {
fn push_args(
instruction: &rabbitizer::Instruction,
relocation: Option<ResolvedRelocation>,
function_range: Range<u64>,
section_index: usize,
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_immediate(imm) => {
if let Some(resolved) = relocation {
push_reloc(resolved.relocation, &mut arg_cb)?;
} else {
arg_cb(match imm {
IU16::Integer(s) => InstructionPart::signed(s),
IU16::Unsigned(u) => InstructionPart::unsigned(u),
})?;
}
}
ValuedOperand::core_label(..) | ValuedOperand::core_branch_target_label(..) => {
if let Some(resolved) = relocation {
// 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 =
resolved.symbol.address.checked_add_signed(resolved.relocation.addend);
if resolved.symbol.section == Some(section_index)
&& target_address.is_some_and(|addr| {
addr > function_range.start && addr < function_range.end
})
{
// TODO move this logic up a level
let target_address = target_address.unwrap();
arg_cb(InstructionPart::branch_dest(target_address))?;
} else {
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_immediate_base(imm, base) => {
if let Some(resolved) = relocation {
push_reloc(resolved.relocation, &mut arg_cb)?;
} else {
arg_cb(InstructionPart::Arg(InstructionArg::Value(match imm {
IU16::Integer(s) => InstructionArgValue::Signed(s as i64),
IU16::Unsigned(u) => InstructionArgValue::Unsigned(u as u64),
})))?;
}
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 {
RelocationFlags::Elf { r_type } => match r_type {
RelocationFlags::Elf(r_type) => match r_type {
elf::R_MIPS_HI16 => {
args.push(ObjInsArg::PlainText("%hi(".into()));
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText(")".into()));
arg_cb(InstructionPart::basic("%hi("))?;
arg_cb(InstructionPart::reloc())?;
arg_cb(InstructionPart::basic(")"))?;
}
elf::R_MIPS_LO16 => {
args.push(ObjInsArg::PlainText("%lo(".into()));
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText(")".into()));
arg_cb(InstructionPart::basic("%lo("))?;
arg_cb(InstructionPart::reloc())?;
arg_cb(InstructionPart::basic(")"))?;
}
elf::R_MIPS_GOT16 => {
args.push(ObjInsArg::PlainText("%got(".into()));
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText(")".into()));
arg_cb(InstructionPart::basic("%got("))?;
arg_cb(InstructionPart::reloc())?;
arg_cb(InstructionPart::basic(")"))?;
}
elf::R_MIPS_CALL16 => {
args.push(ObjInsArg::PlainText("%call16(".into()));
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText(")".into()));
arg_cb(InstructionPart::basic("%call16("))?;
arg_cb(InstructionPart::reloc())?;
arg_cb(InstructionPart::basic(")"))?;
}
elf::R_MIPS_GPREL16 => {
args.push(ObjInsArg::PlainText("%gp_rel(".into()));
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText(")".into()));
arg_cb(InstructionPart::basic("%gp_rel("))?;
arg_cb(InstructionPart::reloc())?;
arg_cb(InstructionPart::basic(")"))?;
}
elf::R_MIPS_32
| elf::R_MIPS_26
| elf::R_MIPS_LITERAL
| elf::R_MIPS_PC16
| R_MIPS15_S3 => {
args.push(ObjInsArg::Reloc);
arg_cb(InstructionPart::reloc())?;
}
_ => bail!("Unsupported ELF MIPS relocation type {r_type}"),
},

View File

@@ -1,19 +1,25 @@
use std::{borrow::Cow, collections::BTreeMap, ffi::CStr};
use alloc::{borrow::Cow, boxed::Box, format, string::String, vec::Vec};
use core::{ffi::CStr, fmt, fmt::Debug};
use anyhow::{bail, Result};
use byteorder::ByteOrder;
use object::{Architecture, File, Object, ObjectSymbol, Relocation, RelocationFlags, Symbol};
use anyhow::{Result, bail};
use object::Endian as _;
use crate::{
diff::DiffObjConfig,
obj::{ObjIns, ObjReloc, ObjSection},
diff::{
DiffObjConfig,
display::{ContextItem, HoverItem, InstructionPart},
},
obj::{
InstructionArg, Object, ParsedInstruction, Relocation, RelocationFlags,
ResolvedInstructionRef, ScannedInstruction, Symbol, SymbolFlagSet, SymbolKind,
},
util::ReallySigned,
};
#[cfg(feature = "arm")]
mod arm;
pub mod arm;
#[cfg(feature = "arm64")]
mod arm64;
pub mod arm64;
#[cfg(feature = "mips")]
pub mod mips;
#[cfg(feature = "ppc")]
@@ -27,78 +33,115 @@ pub enum DataType {
Int16,
Int32,
Int64,
Int128,
Float,
Double,
Bytes,
String,
}
impl fmt::Display for DataType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
DataType::Int8 => write!(f, "Int8"),
DataType::Int16 => write!(f, "Int16"),
DataType::Int32 => write!(f, "Int32"),
DataType::Int64 => write!(f, "Int64"),
DataType::Float => write!(f, "Float"),
DataType::Double => write!(f, "Double"),
DataType::Bytes => write!(f, "Bytes"),
DataType::String => write!(f, "String"),
}
}
}
impl DataType {
pub fn display_bytes<Endian: ByteOrder>(&self, bytes: &[u8]) -> Option<String> {
// TODO: Attempt to interpret large symbols as arrays of a smaller type,
// fallback to intrepreting it as bytes.
// https://github.com/encounter/objdiff/issues/124
if self.required_len().is_some_and(|l| bytes.len() != l) {
log::warn!("Failed to display a symbol value for a symbol whose size doesn't match the instruction referencing it.");
return None;
pub fn display_labels(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<String> {
let mut strs = Vec::new();
for literal in self.display_literals(endian, bytes) {
strs.push(format!("{}: {}", self, literal))
}
strs
}
pub fn display_literals(&self, endian: object::Endianness, bytes: &[u8]) -> Vec<String> {
let mut strs = Vec::new();
if self.required_len().is_some_and(|l| bytes.len() < l) {
log::warn!(
"Failed to display a symbol value for a symbol whose size is too small for instruction referencing it."
);
return strs;
}
let mut bytes = bytes;
if self.required_len().is_some_and(|l| bytes.len() > l) {
// If the symbol's size is larger a single instance of this data type, we take just the
// bytes necessary for one of them in order to display the first element of the array.
bytes = &bytes[0..self.required_len().unwrap()];
// TODO: Attempt to interpret large symbols as arrays of a smaller type and show all
// elements of the array instead. https://github.com/encounter/objdiff/issues/124
// However, note that the stride of an array can not always be determined just by the
// data type guessed by the single instruction accessing it. There can also be arrays of
// structs that contain multiple elements of different types, so if other elements after
// the first one were to be displayed in this manner, they may be inaccurate.
}
match self {
DataType::Int8 => {
let i = i8::from_ne_bytes(bytes.try_into().unwrap());
strs.push(format!("{:#x}", i));
if i < 0 {
format!("Int8: {:#x} ({:#x})", i, ReallySigned(i))
} else {
format!("Int8: {:#x}", i)
strs.push(format!("{:#x}", ReallySigned(i)));
}
}
DataType::Int16 => {
let i = Endian::read_i16(bytes);
let i = endian.read_i16_bytes(bytes.try_into().unwrap());
strs.push(format!("{:#x}", i));
if i < 0 {
format!("Int16: {:#x} ({:#x})", i, ReallySigned(i))
} else {
format!("Int16: {:#x}", i)
strs.push(format!("{:#x}", ReallySigned(i)));
}
}
DataType::Int32 => {
let i = Endian::read_i32(bytes);
let i = endian.read_i32_bytes(bytes.try_into().unwrap());
strs.push(format!("{:#x}", i));
if i < 0 {
format!("Int32: {:#x} ({:#x})", i, ReallySigned(i))
} else {
format!("Int32: {:#x}", i)
strs.push(format!("{:#x}", ReallySigned(i)));
}
}
DataType::Int64 => {
let i = Endian::read_i64(bytes);
let i = endian.read_i64_bytes(bytes.try_into().unwrap());
strs.push(format!("{:#x}", i));
if i < 0 {
format!("Int64: {:#x} ({:#x})", i, ReallySigned(i))
} 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)
strs.push(format!("{:#x}", ReallySigned(i)));
}
}
DataType::Float => {
format!("Float: {}", Endian::read_f32(bytes))
let bytes: [u8; 4] = bytes.try_into().unwrap();
strs.push(format!("{:?}f", match endian {
object::Endianness::Little => f32::from_le_bytes(bytes),
object::Endianness::Big => f32::from_be_bytes(bytes),
}));
}
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),
}));
}
DataType::Bytes => {
format!("Bytes: {:#?}", bytes)
strs.push(format!("{:#?}", bytes));
}
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));
}
}
}
.into()
strs
}
fn required_len(&self) -> Option<usize> {
@@ -107,7 +150,6 @@ impl DataType {
DataType::Int16 => Some(2),
DataType::Int32 => Some(4),
DataType::Int64 => Some(8),
DataType::Int128 => Some(16),
DataType::Float => Some(4),
DataType::Double => Some(8),
DataType::Bytes => None,
@@ -116,59 +158,173 @@ impl DataType {
}
}
pub trait ObjArch: Send + Sync {
fn process_code(
pub trait Arch: Send + Sync + Debug {
/// 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(
&self,
address: u64,
code: &[u8],
section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult>;
diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>>;
/// 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.
fn process_instruction(
&self,
resolved: ResolvedInstructionRef,
diff_config: &DiffObjConfig,
) -> Result<ParsedInstruction> {
let mut mnemonic = None;
let mut args = Vec::with_capacity(8);
self.display_instruction(resolved, diff_config, &mut |part| {
match part {
InstructionPart::Opcode(m, _) => mnemonic = Some(Cow::Owned(m.into_owned())),
InstructionPart::Arg(arg) => 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() && !args.contains(&InstructionArg::Reloc) {
args.push(InstructionArg::Reloc);
}
Ok(ParsedInstruction {
ins_ref: resolved.ins_ref,
mnemonic: mnemonic.unwrap_or_default(),
args,
})
}
/// 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,
resolved: ResolvedInstructionRef,
diff_config: &DiffObjConfig,
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
) -> Result<()>;
/// 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()
}
fn implcit_addend(
&self,
file: &File<'_>,
section: &ObjSection,
file: &object::File<'_>,
section: &object::Section,
address: u64,
reloc: &Relocation,
relocation: &object::Relocation,
flags: RelocationFlags,
) -> Result<i64>;
fn demangle(&self, _name: &str) -> Option<String> { None }
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str>;
fn reloc_name(&self, _flags: RelocationFlags) -> Option<&'static str> { None }
fn symbol_address(&self, symbol: &Symbol) -> u64 { symbol.address() }
fn data_reloc_size(&self, flags: RelocationFlags) -> usize;
fn guess_data_type(&self, _instruction: &ObjIns) -> Option<DataType> { None }
fn symbol_address(&self, address: u64, _kind: SymbolKind) -> u64 { address }
fn display_data_type(&self, _ty: DataType, bytes: &[u8]) -> Option<String> {
Some(format!("Bytes: {:#x?}", bytes))
fn extra_symbol_flags(&self, _symbol: &object::Symbol) -> SymbolFlagSet {
SymbolFlagSet::default()
}
// Downcast methods
#[cfg(feature = "ppc")]
fn ppc(&self) -> Option<&ppc::ObjArchPpc> { None }
fn guess_data_type(&self, _resolved: ResolvedInstructionRef) -> Option<DataType> { None }
fn symbol_hover(&self, _obj: &Object, _symbol_index: usize) -> Vec<HoverItem> { Vec::new() }
fn symbol_context(&self, _obj: &Object, _symbol_index: usize) -> Vec<ContextItem> { Vec::new() }
fn instruction_hover(
&self,
_obj: &Object,
_resolved: ResolvedInstructionRef,
) -> Vec<HoverItem> {
Vec::new()
}
fn instruction_context(
&self,
_obj: &Object,
_resolved: ResolvedInstructionRef,
) -> Vec<ContextItem> {
Vec::new()
}
}
pub struct ProcessCodeResult {
pub ops: Vec<u16>,
pub insts: Vec<ObjIns>,
}
pub fn new_arch(object: &File) -> Result<Box<dyn ObjArch>> {
pub fn new_arch(object: &object::File) -> Result<Box<dyn Arch>> {
use object::Object as _;
Ok(match object.architecture() {
#[cfg(feature = "ppc")]
Architecture::PowerPc => Box::new(ppc::ObjArchPpc::new(object)?),
object::Architecture::PowerPc => Box::new(ppc::ArchPpc::new(object)?),
#[cfg(feature = "mips")]
Architecture::Mips => Box::new(mips::ObjArchMips::new(object)?),
object::Architecture::Mips => Box::new(mips::ArchMips::new(object)?),
#[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")]
Architecture::Arm => Box::new(arm::ObjArchArm::new(object)?),
object::Architecture::Arm => Box::new(arm::ArchArm::new(object)?),
#[cfg(feature = "arm64")]
Architecture::Aarch64 => Box::new(arm64::ObjArchArm64::new(object)?),
object::Architecture::Aarch64 => Box::new(arm64::ArchArm64::new(object)?),
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(
&self,
_address: u64,
_code: &[u8],
_section_index: usize,
_diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>> {
Ok(Vec::new())
}
fn display_instruction(
&self,
_resolved: ResolvedInstructionRef,
_diff_config: &DiffObjConfig,
_cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
Ok(())
}
fn implcit_addend(
&self,
_file: &object::File<'_>,
_section: &object::Section,
_address: u64,
_relocation: &object::Relocation,
_flags: RelocationFlags,
) -> Result<i64> {
Ok(0)
}
fn data_reloc_size(&self, _flags: RelocationFlags) -> usize { 0 }
}

View File

@@ -1,254 +1,362 @@
use std::{borrow::Cow, collections::BTreeMap};
use anyhow::{bail, ensure, Result};
use byteorder::BigEndian;
use cwextab::{decode_extab, ExceptionTableData};
use object::{
elf, File, Object, ObjectSection, ObjectSymbol, Relocation, RelocationFlags, RelocationTarget,
Symbol, SymbolKind,
use alloc::{
collections::{BTreeMap, BTreeSet},
string::{String, ToString},
vec,
vec::Vec,
};
use ppc750cl::{Argument, InsIter, Opcode, GPR};
use anyhow::{Result, bail, ensure};
use cwextab::{ExceptionTableData, decode_extab};
use flagset::Flags;
use object::{Object as _, ObjectSection as _, ObjectSymbol as _, elf};
use crate::{
arch::{DataType, ObjArch, ProcessCodeResult},
diff::DiffObjConfig,
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection, ObjSymbol},
arch::{Arch, DataType},
diff::{
DiffObjConfig,
data::resolve_relocation,
display::{ContextItem, HoverItem, HoverItemColor, InstructionPart, SymbolNavigationKind},
},
obj::{
InstructionRef, Object, Relocation, RelocationFlags, ResolvedInstructionRef,
ResolvedRelocation, ScannedInstruction, Symbol, SymbolFlag, SymbolFlagSet,
},
};
// Relative relocation, can be Simm, Offset or BranchDest
fn is_relative_arg(arg: &Argument) -> bool {
matches!(arg, Argument::Simm(_) | Argument::Offset(_) | Argument::BranchDest(_))
fn is_relative_arg(arg: &ppc750cl::Argument) -> bool {
matches!(
arg,
ppc750cl::Argument::Simm(_)
| ppc750cl::Argument::Offset(_)
| ppc750cl::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_rel_abs_arg(arg: &ppc750cl::Argument) -> bool {
matches!(
arg,
ppc750cl::Argument::Uimm(_) | ppc750cl::Argument::Simm(_) | ppc750cl::Argument::Offset(_)
)
}
fn is_offset_arg(arg: &Argument) -> bool { matches!(arg, Argument::Offset(_)) }
fn is_offset_arg(arg: &ppc750cl::Argument) -> bool { matches!(arg, ppc750cl::Argument::Offset(_)) }
pub struct ObjArchPpc {
#[derive(Debug)]
pub struct ArchPpc {
/// 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 ArchPpc {
pub fn new(file: &object::File) -> Result<Self> {
Ok(Self { extab: decode_exception_info(file)? })
}
fn parse_ins_ref(&self, resolved: ResolvedInstructionRef) -> Result<ppc750cl::Ins> {
let mut code = u32::from_be_bytes(resolved.code.try_into()?);
if let Some(reloc) = resolved.relocation {
code = zero_reloc(code, reloc.relocation);
}
let op = ppc750cl::Opcode::from(resolved.ins_ref.opcode as u8);
Ok(ppc750cl::Ins { code, op })
}
fn find_reloc_arg(
&self,
ins: &ppc750cl::ParsedIns,
resolved: Option<ResolvedRelocation>,
) -> Option<usize> {
match resolved?.relocation.flags {
RelocationFlags::Elf(elf::R_PPC_EMB_SDA21) => Some(1),
RelocationFlags::Elf(elf::R_PPC_REL24 | elf::R_PPC_REL14) => {
ins.args.iter().rposition(is_relative_arg)
}
RelocationFlags::Elf(
elf::R_PPC_ADDR16_HI | elf::R_PPC_ADDR16_HA | elf::R_PPC_ADDR16_LO,
) => ins.args.iter().rposition(is_rel_abs_arg),
_ => None,
}
}
}
impl ObjArch for ObjArchPpc {
fn process_code(
impl Arch for ArchPpc {
fn scan_instructions(
&self,
address: u64,
code: &[u8],
_section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
_diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>> {
ensure!(code.len() & 3 == 0, "Code length must be a multiple of 4");
let ins_count = code.len() / 4;
let mut ops = Vec::<u16>::with_capacity(ins_count);
let mut insts = Vec::<ObjIns>::with_capacity(ins_count);
for (cur_addr, mut ins) in InsIter::new(code, address as u32) {
let reloc = relocations.iter().find(|r| (r.address as u32 & !3) == cur_addr);
if let Some(reloc) = reloc {
// Zero out relocations
ins.code = match reloc.flags {
RelocationFlags::Elf { r_type: elf::R_PPC_EMB_SDA21 } => ins.code & !0x1FFFFF,
RelocationFlags::Elf { r_type: elf::R_PPC_REL24 } => ins.code & !0x3FFFFFC,
RelocationFlags::Elf { r_type: elf::R_PPC_REL14 } => ins.code & !0xFFFC,
RelocationFlags::Elf {
r_type: elf::R_PPC_ADDR16_HI | elf::R_PPC_ADDR16_HA | elf::R_PPC_ADDR16_LO,
} => ins.code & !0xFFFF,
_ => ins.code,
};
}
let orig = ins.basic().to_string();
let simplified = ins.simplified();
let formatted = simplified.to_string();
let mut reloc_arg = None;
if let Some(reloc) = reloc {
match reloc.flags {
RelocationFlags::Elf { r_type: elf::R_PPC_EMB_SDA21 } => {
reloc_arg = Some(1);
}
RelocationFlags::Elf { r_type: elf::R_PPC_REL24 | elf::R_PPC_REL14 } => {
reloc_arg = simplified.args.iter().rposition(is_relative_arg);
}
RelocationFlags::Elf {
r_type: elf::R_PPC_ADDR16_HI | elf::R_PPC_ADDR16_HA | elf::R_PPC_ADDR16_LO,
} => {
reloc_arg = simplified.args.iter().rposition(is_rel_abs_arg);
}
_ => {}
}
}
let mut args = vec![];
let mut branch_dest = None;
let mut writing_offset = false;
for (idx, arg) in simplified.args_iter().enumerate() {
if idx > 0 && !writing_offset {
args.push(ObjInsArg::PlainText(config.separator().into()));
}
if reloc_arg == Some(idx) {
let reloc = reloc.unwrap();
push_reloc(&mut args, reloc)?;
// For @sda21, we can omit the register argument
if matches!(reloc.flags, RelocationFlags::Elf { r_type: elf::R_PPC_EMB_SDA21 })
// Sanity check: the next argument should be r0
&& matches!(simplified.args.get(idx + 1), Some(Argument::GPR(GPR(0))))
{
break;
}
} else {
match arg {
Argument::Simm(simm) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(simm.0 as i64)));
}
Argument::Uimm(uimm) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(uimm.0 as u64)));
}
Argument::Offset(offset) => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(offset.0 as i64)));
}
Argument::BranchDest(dest) => {
let dest = cur_addr.wrapping_add_signed(dest.0) as u64;
args.push(ObjInsArg::BranchDest(dest));
branch_dest = Some(dest);
}
_ => {
args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(
arg.to_string().into(),
)));
}
};
}
if writing_offset {
args.push(ObjInsArg::PlainText(")".into()));
writing_offset = false;
}
if is_offset_arg(arg) {
args.push(ObjInsArg::PlainText("(".into()));
writing_offset = true;
}
}
ops.push(ins.op as u16);
let line = line_info.range(..=cur_addr as u64).last().map(|(_, &b)| b);
insts.push(ObjIns {
address: cur_addr as u64,
size: 4,
mnemonic: Cow::Borrowed(simplified.mnemonic),
args,
reloc: reloc.cloned(),
op: ins.op as u16,
branch_dest,
line,
formatted,
orig: Some(orig),
let mut insts = Vec::<ScannedInstruction>::with_capacity(ins_count);
for (cur_addr, ins) in ppc750cl::InsIter::new(code, address as u32) {
insts.push(ScannedInstruction {
ins_ref: InstructionRef {
address: cur_addr as u64,
size: 4,
opcode: u8::from(ins.op) as u16,
},
branch_dest: ins.branch_dest(cur_addr).map(u64::from),
});
}
Ok(ProcessCodeResult { ops, insts })
Ok(insts)
}
fn display_instruction(
&self,
resolved: ResolvedInstructionRef,
_diff_config: &DiffObjConfig,
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
let ins = self.parse_ins_ref(resolved)?.simplified();
cb(InstructionPart::opcode(ins.mnemonic, resolved.ins_ref.opcode))?;
let reloc_arg = self.find_reloc_arg(&ins, resolved.relocation);
let mut writing_offset = false;
for (idx, arg) in ins.args_iter().enumerate() {
if idx > 0 && !writing_offset {
cb(InstructionPart::separator())?;
}
if reloc_arg == Some(idx) {
let reloc = resolved.relocation.unwrap();
display_reloc(reloc, cb)?;
// For @sda21, we can omit the register argument
if matches!(reloc.relocation.flags, RelocationFlags::Elf(elf::R_PPC_EMB_SDA21))
// Sanity check: the next argument should be r0
&& matches!(ins.args.get(idx + 1), Some(ppc750cl::Argument::GPR(ppc750cl::GPR(0))))
{
break;
}
} else {
match arg {
ppc750cl::Argument::Simm(simm) => cb(InstructionPart::signed(simm.0)),
ppc750cl::Argument::Uimm(uimm) => cb(InstructionPart::unsigned(uimm.0)),
ppc750cl::Argument::Offset(offset) => cb(InstructionPart::signed(offset.0)),
ppc750cl::Argument::BranchDest(dest) => cb(InstructionPart::branch_dest(
(resolved.ins_ref.address as u32).wrapping_add_signed(dest.0),
)),
_ => cb(InstructionPart::opaque(arg.to_string())),
}?;
}
if writing_offset {
cb(InstructionPart::basic(")"))?;
writing_offset = false;
}
if is_offset_arg(arg) {
cb(InstructionPart::basic("("))?;
writing_offset = true;
}
}
Ok(())
}
fn generate_pooled_relocations(
&self,
address: u64,
code: &[u8],
relocations: &[Relocation],
symbols: &[Symbol],
) -> Vec<Relocation> {
generate_fake_pool_relocations_for_function(address, code, relocations, symbols)
}
fn implcit_addend(
&self,
_file: &File<'_>,
_section: &ObjSection,
_file: &object::File<'_>,
_section: &object::Section,
address: u64,
reloc: &Relocation,
_relocation: &object::Relocation,
flags: RelocationFlags,
) -> Result<i64> {
bail!("Unsupported PPC implicit relocation {:#x}:{:?}", address, reloc.flags())
bail!("Unsupported PPC implicit relocation {:#x}:{:?}", address, flags)
}
fn demangle(&self, name: &str) -> Option<String> {
cwdemangle::demangle(name, &cwdemangle::DemangleOptions::default())
}
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
match flags {
RelocationFlags::Elf { r_type } => match r_type {
elf::R_PPC_ADDR16_LO => Cow::Borrowed("R_PPC_ADDR16_LO"),
elf::R_PPC_ADDR16_HI => Cow::Borrowed("R_PPC_ADDR16_HI"),
elf::R_PPC_ADDR16_HA => Cow::Borrowed("R_PPC_ADDR16_HA"),
elf::R_PPC_EMB_SDA21 => Cow::Borrowed("R_PPC_EMB_SDA21"),
elf::R_PPC_ADDR32 => Cow::Borrowed("R_PPC_ADDR32"),
elf::R_PPC_UADDR32 => Cow::Borrowed("R_PPC_UADDR32"),
elf::R_PPC_REL24 => Cow::Borrowed("R_PPC_REL24"),
elf::R_PPC_REL14 => Cow::Borrowed("R_PPC_REL14"),
_ => Cow::Owned(format!("<{flags:?}>")),
RelocationFlags::Elf(r_type) => match r_type {
elf::R_PPC_NONE => Some("R_PPC_NONE"), // We use this for fake pool relocs
elf::R_PPC_ADDR16_LO => Some("R_PPC_ADDR16_LO"),
elf::R_PPC_ADDR16_HI => Some("R_PPC_ADDR16_HI"),
elf::R_PPC_ADDR16_HA => Some("R_PPC_ADDR16_HA"),
elf::R_PPC_EMB_SDA21 => Some("R_PPC_EMB_SDA21"),
elf::R_PPC_ADDR32 => Some("R_PPC_ADDR32"),
elf::R_PPC_UADDR32 => Some("R_PPC_UADDR32"),
elf::R_PPC_REL24 => Some("R_PPC_REL24"),
elf::R_PPC_REL14 => Some("R_PPC_REL14"),
_ => None,
},
_ => Cow::Owned(format!("<{flags:?}>")),
}
}
fn guess_data_type(&self, instruction: &ObjIns) -> Option<super::DataType> {
// Always shows the first string of the table. Not ideal, but it's really hard to find
// the actual string being referenced.
if instruction.reloc.as_ref().is_some_and(|r| r.target.name.starts_with("@stringBase")) {
return Some(DataType::String);
}
match Opcode::from(instruction.op as u8) {
Opcode::Lbz | Opcode::Lbzu | Opcode::Lbzux | Opcode::Lbzx => Some(DataType::Int8),
Opcode::Lhz | Opcode::Lhzu | Opcode::Lhzux | Opcode::Lhzx => Some(DataType::Int16),
Opcode::Lha | Opcode::Lhau | Opcode::Lhaux | Opcode::Lhax => Some(DataType::Int16),
Opcode::Lwz | Opcode::Lwzu | Opcode::Lwzux | Opcode::Lwzx => Some(DataType::Int32),
Opcode::Lfs | Opcode::Lfsu | Opcode::Lfsux | Opcode::Lfsx => Some(DataType::Float),
Opcode::Lfd | Opcode::Lfdu | Opcode::Lfdux | Opcode::Lfdx => Some(DataType::Double),
Opcode::Stb | Opcode::Stbu | Opcode::Stbux | Opcode::Stbx => Some(DataType::Int8),
Opcode::Sth | Opcode::Sthu | Opcode::Sthux | Opcode::Sthx => Some(DataType::Int16),
Opcode::Stw | Opcode::Stwu | Opcode::Stwux | Opcode::Stwx => Some(DataType::Int32),
Opcode::Stfs | Opcode::Stfsu | Opcode::Stfsux | Opcode::Stfsx => Some(DataType::Float),
Opcode::Stfd | Opcode::Stfdu | Opcode::Stfdux | Opcode::Stfdx => Some(DataType::Double),
_ => None,
}
}
fn display_data_type(&self, ty: DataType, bytes: &[u8]) -> Option<String> {
ty.display_bytes::<BigEndian>(bytes)
fn data_reloc_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 ppc(&self) -> Option<&ObjArchPpc> { Some(self) }
}
fn extra_symbol_flags(&self, symbol: &object::Symbol) -> SymbolFlagSet {
if self.extab.as_ref().is_some_and(|extab| extab.contains_key(&(symbol.index().0 - 1))) {
SymbolFlag::HasExtra.into()
} else {
SymbolFlag::none()
}
}
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 guess_data_type(&self, resolved: ResolvedInstructionRef) -> Option<DataType> {
if resolved.relocation.is_some_and(|r| r.symbol.name.starts_with("@stringBase")) {
return Some(DataType::String);
}
let opcode = ppc750cl::Opcode::from(resolved.ins_ref.opcode as u8);
guess_data_type_from_load_store_inst_op(opcode)
}
fn symbol_hover(&self, _obj: &Object, symbol_index: usize) -> Vec<HoverItem> {
let mut out = Vec::new();
if let Some(extab) = self.extab_for_symbol(symbol_index) {
out.push(HoverItem::Text {
label: "extab symbol".into(),
value: extab.etb_symbol.name.clone(),
color: HoverItemColor::Special,
});
out.push(HoverItem::Text {
label: "extabindex symbol".into(),
value: extab.eti_symbol.name.clone(),
color: HoverItemColor::Special,
});
}
out
}
fn symbol_context(&self, _obj: &Object, symbol_index: usize) -> Vec<ContextItem> {
let mut out = Vec::new();
if let Some(_extab) = self.extab_for_symbol(symbol_index) {
out.push(ContextItem::Navigate {
label: "Decode exception table".to_string(),
symbol_index,
kind: SymbolNavigationKind::Extab,
});
}
out
}
fn instruction_hover(&self, _obj: &Object, resolved: ResolvedInstructionRef) -> Vec<HoverItem> {
let Ok(ins) = self.parse_ins_ref(resolved) else {
return Vec::new();
};
let orig = ins.basic().to_string();
let simplified = ins.simplified().to_string();
let show_orig = orig != simplified;
let rlwinm_decoded = rlwinmdec::decode(&orig);
let mut out = Vec::with_capacity(2);
if show_orig {
out.push(HoverItem::Text {
label: "Original".into(),
value: orig,
color: HoverItemColor::Normal,
});
}
if let Some(decoded) = rlwinm_decoded {
for line in decoded.lines() {
out.push(HoverItem::Text {
label: Default::default(),
value: line.to_string(),
color: HoverItemColor::Special,
});
}
}
out
}
fn instruction_context(
&self,
_obj: &Object,
resolved: ResolvedInstructionRef,
) -> Vec<ContextItem> {
let Ok(ins) = self.parse_ins_ref(resolved) else {
return Vec::new();
};
let orig = ins.basic().to_string();
let simplified = ins.simplified().to_string();
let show_orig = orig != simplified;
let mut out = Vec::with_capacity(2);
out.push(ContextItem::Copy { value: simplified, label: None });
if show_orig {
out.push(ContextItem::Copy { value: orig, label: Some("original".to_string()) });
}
out
}
}
fn push_reloc(args: &mut Vec<ObjInsArg>, reloc: &ObjReloc) -> Result<()> {
impl ArchPpc {
pub fn extab_for_symbol(&self, symbol_index: usize) -> Option<&ExceptionInfo> {
self.extab.as_ref()?.get(&symbol_index)
}
}
fn zero_reloc(code: u32, reloc: &Relocation) -> u32 {
match reloc.flags {
RelocationFlags::Elf { r_type } => match r_type {
RelocationFlags::Elf(elf::R_PPC_EMB_SDA21) => code & !0x1FFFFF,
RelocationFlags::Elf(elf::R_PPC_REL24) => code & !0x3FFFFFC,
RelocationFlags::Elf(elf::R_PPC_REL14) => code & !0xFFFC,
RelocationFlags::Elf(
elf::R_PPC_ADDR16_HI | elf::R_PPC_ADDR16_HA | elf::R_PPC_ADDR16_LO,
) => code & !0xFFFF,
_ => code,
}
}
fn display_reloc(
resolved: ResolvedRelocation,
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
match resolved.relocation.flags {
RelocationFlags::Elf(r_type) => match r_type {
elf::R_PPC_ADDR16_LO => {
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText("@l".into()));
cb(InstructionPart::reloc())?;
cb(InstructionPart::basic("@l"))?;
}
elf::R_PPC_ADDR16_HI => {
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText("@h".into()));
cb(InstructionPart::reloc())?;
cb(InstructionPart::basic("@h"))?;
}
elf::R_PPC_ADDR16_HA => {
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText("@ha".into()));
cb(InstructionPart::reloc())?;
cb(InstructionPart::basic("@ha"))?;
}
elf::R_PPC_EMB_SDA21 => {
args.push(ObjInsArg::Reloc);
args.push(ObjInsArg::PlainText("@sda21".into()));
cb(InstructionPart::reloc())?;
cb(InstructionPart::basic("@sda21"))?;
}
elf::R_PPC_ADDR32 | elf::R_PPC_UADDR32 | elf::R_PPC_REL24 | elf::R_PPC_REL14 => {
args.push(ObjInsArg::Reloc);
cb(InstructionPart::reloc())?;
}
_ => bail!("Unsupported ELF PPC relocation type {r_type}"),
elf::R_PPC_NONE => {
// Fake pool relocation.
cb(InstructionPart::basic("<"))?;
cb(InstructionPart::reloc())?;
cb(InstructionPart::basic(">"))?;
}
_ => cb(InstructionPart::reloc())?,
},
flags => bail!("Unsupported PPC relocation kind: {flags:?}"),
_ => cb(InstructionPart::reloc())?,
};
Ok(())
}
@@ -268,7 +376,9 @@ pub struct ExceptionInfo {
pub dtors: Vec<ExtabSymbolRef>,
}
fn decode_exception_info(file: &File<'_>) -> Result<Option<BTreeMap<usize, ExceptionInfo>>> {
fn decode_exception_info(
file: &object::File<'_>,
) -> Result<Option<BTreeMap<usize, ExceptionInfo>>> {
let Some(extab_section) = file.section_by_name("extab") else {
return Ok(None);
};
@@ -277,13 +387,14 @@ fn decode_exception_info(file: &File<'_>) -> Result<Option<BTreeMap<usize, Excep
};
let mut result = BTreeMap::new();
let extab_relocations = extab_section.relocations().collect::<BTreeMap<u64, Relocation>>();
let extab_relocations =
extab_section.relocations().collect::<BTreeMap<u64, object::Relocation>>();
let extabindex_relocations =
extabindex_section.relocations().collect::<BTreeMap<u64, Relocation>>();
extabindex_section.relocations().collect::<BTreeMap<u64, object::Relocation>>();
for extabindex in file.symbols().filter(|symbol| {
symbol.section_index() == Some(extabindex_section.index())
&& symbol.kind() == SymbolKind::Data
&& symbol.kind() == object::SymbolKind::Data
}) {
if extabindex.size() != 12 {
log::warn!("Invalid extabindex entry size {}", extabindex.size());
@@ -344,7 +455,7 @@ fn decode_exception_info(file: &File<'_>) -> Result<Option<BTreeMap<usize, Excep
};
//Add the new entry to the list
result.insert(extab_func.index().0, ExceptionInfo {
result.insert(extab_func.index().0 - 1, ExceptionInfo {
eti_symbol: make_symbol_ref(&extabindex)?,
etb_symbol: make_symbol_ref(&extab)?,
data,
@@ -356,16 +467,16 @@ fn decode_exception_info(file: &File<'_>) -> Result<Option<BTreeMap<usize, Excep
}
fn relocation_symbol<'data, 'file>(
file: &'file File<'data>,
relocation: &Relocation,
) -> Result<Option<Symbol<'data, 'file>>> {
file: &'file object::File<'data>,
relocation: &object::Relocation,
) -> Result<Option<object::Symbol<'data, 'file>>> {
let addend = relocation.addend();
match relocation.target() {
RelocationTarget::Symbol(idx) => {
object::RelocationTarget::Symbol(idx) => {
ensure!(addend == 0, "Symbol relocations must have zero addend");
Ok(Some(file.symbol_by_index(idx)?))
}
RelocationTarget::Section(idx) => {
object::RelocationTarget::Section(idx) => {
ensure!(addend >= 0, "Section relocations must have non-negative addend");
let addend = addend as u64;
Ok(file
@@ -376,8 +487,360 @@ fn relocation_symbol<'data, 'file>(
}
}
fn make_symbol_ref(symbol: &Symbol) -> Result<ExtabSymbolRef> {
fn make_symbol_ref(symbol: &object::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 })
Ok(ExtabSymbolRef { original_index: symbol.index().0 - 1, name, demangled_name })
}
fn guess_data_type_from_load_store_inst_op(inst_op: ppc750cl::Opcode) -> Option<DataType> {
use ppc750cl::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,
}
}
struct PoolReference {
addr_src_gpr: ppc750cl::GPR,
addr_offset: i16,
addr_dst_gpr: Option<ppc750cl::GPR>,
}
// Given an instruction, check if it could be accessing pooled data at the address in a register.
// If so, return information pertaining to where the instruction is getting that address from and
// what it's doing with the address (e.g. copying it into another register, adding an offset, etc).
fn get_pool_reference_for_inst(
opcode: ppc750cl::Opcode,
simplified: &ppc750cl::ParsedIns,
) -> Option<PoolReference> {
use ppc750cl::{Argument, Opcode};
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(PoolReference { addr_src_gpr, addr_offset: offset.0, addr_dst_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(PoolReference { addr_src_gpr, addr_offset: 0, addr_dst_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(PoolReference {
addr_src_gpr,
addr_offset: simm.0,
addr_dst_gpr: Some(addr_dst_gpr),
}),
(
// `mr` or `mr.`
Opcode::Or,
Argument::GPR(addr_dst_gpr),
Argument::GPR(addr_src_gpr),
Argument::None,
) => Some(PoolReference {
addr_src_gpr,
addr_offset: 0,
addr_dst_gpr: Some(addr_dst_gpr),
}),
(
Opcode::Add,
Argument::GPR(addr_dst_gpr),
Argument::GPR(addr_src_gpr),
Argument::GPR(_offset_gpr),
) => Some(PoolReference {
addr_src_gpr,
addr_offset: 0,
addr_dst_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: ppc750cl::Ins, gpr_pool_relocs: &mut BTreeMap<u8, Relocation>) {
use ppc750cl::{Argument, Arguments, Opcode};
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: &Relocation,
symbols: &[Symbol],
) -> Option<Relocation> {
let pool_reloc = resolve_relocation(symbols, pool_reloc);
let offset_from_pool = pool_reloc.relocation.addend + offset as i64;
let target_address = pool_reloc.symbol.address.checked_add_signed(offset_from_pool)?;
let target_symbol;
let addend;
if let Some(section_index) = pool_reloc.symbol.section {
// Find the exact data symbol within the pool being accessed here based on the address.
target_symbol = symbols.iter().position(|s| {
s.section == Some(section_index)
&& s.size > 0
&& (s.address..s.address + s.size).contains(&target_address)
})?;
addend = target_address.checked_sub(symbols[target_symbol].address)? as i64;
} else {
// If the target symbol is in a different object (extern), we simply copy the pool
// relocation's target. This is because it's not possible to locate the actual symbol if
// it's extern. 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.
target_symbol = pool_reloc.relocation.target_symbol;
addend = pool_reloc.relocation.addend + offset_from_pool;
}
Some(Relocation {
flags: RelocationFlags::Elf(elf::R_PPC_NONE),
address: cur_addr as u64,
target_symbol,
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 returns a Vec of "fake pool relocations" that simulate what a relocation for that instruction
// 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 does not currently read switch statement jump tables.
// Instead, we 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.
// It should be possible to implement jump tables properly by reading them out of .data. But this
// will require keeping track of what value is loaded into each register so we can retrieve the jump
// table symbol when we encounter a `bctr`.
fn generate_fake_pool_relocations_for_function(
func_address: u64,
code: &[u8],
relocations: &[Relocation],
symbols: &[Symbol],
) -> Vec<Relocation> {
use ppc750cl::{Argument, InsIter, Opcode};
let mut visited_ins_addrs = BTreeSet::new();
let mut pool_reloc_for_addr = BTreeMap::new();
let mut ins_iters_with_gpr_state =
vec![(InsIter::new(code, func_address as u32), BTreeMap::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(pool_ref) = get_pool_reference_for_inst(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(&pool_ref.addr_src_gpr.0) {
if let Some(fake_pool_reloc) =
make_fake_pool_reloc(pool_ref.addr_offset, cur_addr, pool_reloc, symbols)
{
pool_reloc_for_addr.insert(cur_addr, fake_pool_reloc);
}
if let Some(addr_dst_gpr) = pool_ref.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 += pool_ref.addr_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.values().cloned().collect()
}

View File

@@ -1,144 +1,214 @@
use std::{borrow::Cow, collections::BTreeMap};
use alloc::{boxed::Box, string::String, vec::Vec};
use anyhow::{anyhow, bail, ensure, Result};
use anyhow::{Result, anyhow, bail};
use iced_x86::{
Decoder, DecoderOptions, DecoratorKind, Formatter, FormatterOutput, FormatterTextKind,
GasFormatter, Instruction, IntelFormatter, MasmFormatter, NasmFormatter, NumberKind, OpKind,
PrefixKind, Register,
Decoder, DecoderOptions, DecoratorKind, FormatterOutput, FormatterTextKind, GasFormatter,
Instruction, IntelFormatter, MasmFormatter, NasmFormatter, NumberKind, OpKind, Register,
};
use object::{pe, Endian, Endianness, File, Object, Relocation, RelocationFlags};
use object::{Endian as _, Object as _, ObjectSection as _, pe};
use crate::{
arch::{ObjArch, ProcessCodeResult},
diff::{DiffObjConfig, X86Formatter},
obj::{ObjIns, ObjInsArg, ObjInsArgValue, ObjReloc, ObjSection},
arch::Arch,
diff::{DiffObjConfig, X86Formatter, display::InstructionPart},
obj::{InstructionRef, RelocationFlags, ResolvedInstructionRef, ScannedInstruction},
};
pub struct ObjArchX86 {
bits: u32,
endianness: Endianness,
#[derive(Debug)]
pub struct ArchX86 {
arch: Architecture,
endianness: object::Endianness,
}
impl ObjArchX86 {
pub fn new(object: &File) -> Result<Self> {
Ok(Self { bits: if object.is_64() { 64 } else { 32 }, endianness: object.endianness() })
#[derive(Debug)]
enum Architecture {
X86,
X86_64,
}
impl ArchX86 {
pub fn new(object: &object::File) -> Result<Self> {
let arch = match object.architecture() {
object::Architecture::I386 => Architecture::X86,
object::Architecture::X86_64 => Architecture::X86_64,
_ => bail!("Unsupported architecture for ArchX86: {:?}", object.architecture()),
};
Ok(Self { arch, endianness: object.endianness() })
}
}
impl ObjArch for ObjArchX86 {
fn process_code(
&self,
address: u64,
code: &[u8],
_section_index: usize,
relocations: &[ObjReloc],
line_info: &BTreeMap<u64, u32>,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
let mut result = ProcessCodeResult { ops: Vec::new(), insts: Vec::new() };
let mut decoder = Decoder::with_ip(self.bits, code, address, DecoderOptions::NONE);
let mut formatter: Box<dyn Formatter> = match config.x86_formatter {
fn decoder<'a>(&self, code: &'a [u8], address: u64) -> Decoder<'a> {
Decoder::with_ip(
match self.arch {
Architecture::X86 => 32,
Architecture::X86_64 => 64,
},
code,
address,
DecoderOptions::NONE,
)
}
fn formatter(&self, diff_config: &DiffObjConfig) -> Box<dyn iced_x86::Formatter> {
let mut formatter: Box<dyn iced_x86::Formatter> = match diff_config.x86_formatter {
X86Formatter::Intel => Box::new(IntelFormatter::new()),
X86Formatter::Gas => Box::new(GasFormatter::new()),
X86Formatter::Nasm => Box::new(NasmFormatter::new()),
X86Formatter::Masm => Box::new(MasmFormatter::new()),
};
formatter.options_mut().set_space_after_operand_separator(config.space_between_args);
formatter.options_mut().set_space_after_operand_separator(diff_config.space_between_args);
formatter
}
let mut output = InstructionFormatterOutput {
formatted: String::new(),
ins: ObjIns {
address: 0,
size: 0,
op: 0,
mnemonic: Cow::Borrowed("<invalid>"),
args: vec![],
reloc: None,
branch_dest: None,
line: None,
formatted: String::new(),
orig: None,
fn reloc_size(&self, flags: RelocationFlags) -> Option<usize> {
match self.arch {
Architecture::X86 => match flags {
RelocationFlags::Coff(typ) => match typ {
pe::IMAGE_REL_I386_DIR16 | pe::IMAGE_REL_I386_REL16 => Some(2),
pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32 => Some(4),
_ => None,
},
_ => None,
},
error: None,
ins_operands: vec![],
};
Architecture::X86_64 => match flags {
RelocationFlags::Coff(typ) => match typ {
pe::IMAGE_REL_AMD64_ADDR32NB | pe::IMAGE_REL_AMD64_REL32 => Some(4),
pe::IMAGE_REL_AMD64_ADDR64 => Some(8),
_ => None,
},
_ => None,
},
}
}
}
impl Arch for ArchX86 {
fn scan_instructions(
&self,
address: u64,
code: &[u8],
_section_index: usize,
_diff_config: &DiffObjConfig,
) -> Result<Vec<ScannedInstruction>> {
let mut out = Vec::with_capacity(code.len() / 2);
let mut decoder = self.decoder(code, address);
let mut instruction = Instruction::default();
while decoder.can_decode() {
decoder.decode_out(&mut instruction);
let address = instruction.ip();
let op = instruction.mnemonic() as u16;
let reloc = relocations
.iter()
.find(|r| r.address >= address && r.address < address + instruction.len() as u64);
let line = line_info.range(..=address).last().map(|(_, &b)| b);
output.ins = ObjIns {
address,
size: instruction.len() as u8,
op,
mnemonic: Cow::Borrowed("<invalid>"),
args: vec![],
reloc: reloc.cloned(),
branch_dest: None,
line,
formatted: String::new(),
orig: None,
let branch_dest = match instruction.op0_kind() {
OpKind::NearBranch16 => Some(instruction.near_branch16() as u64),
OpKind::NearBranch32 => Some(instruction.near_branch32() as u64),
OpKind::NearBranch64 => Some(instruction.near_branch64()),
_ => None,
};
// Run the formatter, which will populate output.ins
formatter.format(&instruction, &mut output);
if let Some(error) = output.error.take() {
return Err(error);
}
ensure!(output.ins_operands.len() == output.ins.args.len());
output.ins.formatted.clone_from(&output.formatted);
// Make sure we've put the relocation somewhere in the instruction
if reloc.is_some() && !output.ins.args.iter().any(|a| matches!(a, ObjInsArg::Reloc)) {
let mut found = replace_arg(
OpKind::Memory,
ObjInsArg::Reloc,
&mut output.ins.args,
&instruction,
&output.ins_operands,
)?;
if !found {
found = replace_arg(
OpKind::Immediate32,
ObjInsArg::Reloc,
&mut output.ins.args,
&instruction,
&output.ins_operands,
)?;
}
ensure!(found, "x86: Failed to find operand for Absolute relocation");
}
if reloc.is_some() && !output.ins.args.iter().any(|a| matches!(a, ObjInsArg::Reloc)) {
bail!("Failed to find relocation in instruction");
}
result.ops.push(op);
result.insts.push(output.ins.clone());
// Clear for next iteration
output.formatted.clear();
output.ins_operands.clear();
out.push(ScannedInstruction {
ins_ref: InstructionRef {
address: instruction.ip(),
size: instruction.len() as u8,
opcode: instruction.mnemonic() as u16,
},
branch_dest,
});
}
Ok(result)
Ok(out)
}
fn display_instruction(
&self,
resolved: ResolvedInstructionRef,
diff_config: &DiffObjConfig,
cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
) -> Result<()> {
let mut decoder = self.decoder(resolved.code, resolved.ins_ref.address);
let mut formatter = self.formatter(diff_config);
let mut instruction = Instruction::default();
decoder.decode_out(&mut instruction);
// Determine where to insert relocation in instruction output.
// We replace the immediate or displacement with a placeholder value since the formatter
// doesn't provide enough information to know which number is the displacement inside a
// memory operand.
let mut reloc_replace = None;
if let Some(reloc) = resolved.relocation {
const PLACEHOLDER: u64 = 0x7BDE3E7D; // chosen by fair dice roll. guaranteed to be random.
let reloc_offset = reloc.relocation.address - resolved.ins_ref.address;
let reloc_size = self.reloc_size(reloc.relocation.flags).unwrap_or(usize::MAX);
let offsets = decoder.get_constant_offsets(&instruction);
if reloc_offset == offsets.displacement_offset() as u64
&& reloc_size == offsets.displacement_size()
{
instruction.set_memory_displacement64(PLACEHOLDER);
// Formatter always writes the displacement as Int32
reloc_replace = Some((OpKind::Memory, NumberKind::Int32, PLACEHOLDER));
} else if reloc_offset == offsets.immediate_offset() as u64
&& reloc_size == offsets.immediate_size()
{
let is_branch = matches!(
instruction.op0_kind(),
OpKind::NearBranch16 | OpKind::NearBranch32 | OpKind::NearBranch64
);
let op_kind = if is_branch {
instruction.op0_kind()
} else {
match reloc_size {
2 => OpKind::Immediate16,
4 => OpKind::Immediate32,
8 => OpKind::Immediate64,
_ => OpKind::default(),
}
};
let number_kind = match reloc_size {
2 => NumberKind::UInt16,
4 => NumberKind::UInt32,
8 => NumberKind::UInt64,
_ => NumberKind::default(),
};
if is_branch {
instruction.set_near_branch64(PLACEHOLDER);
} else {
instruction.set_immediate32(PLACEHOLDER as u32);
}
reloc_replace = Some((op_kind, number_kind, PLACEHOLDER));
}
}
let mut output =
InstructionFormatterOutput { cb, reloc_replace, error: None, skip_next: false };
formatter.format(&instruction, &mut output);
if let Some(error) = output.error.take() {
return Err(error);
}
Ok(())
}
fn implcit_addend(
&self,
_file: &File<'_>,
section: &ObjSection,
_file: &object::File<'_>,
section: &object::Section,
address: u64,
reloc: &Relocation,
_relocation: &object::Relocation,
flags: RelocationFlags,
) -> Result<i64> {
match reloc.flags() {
RelocationFlags::Coff { typ: pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32 } => {
let data = section.data[address as usize..address as usize + 4].try_into()?;
Ok(self.endianness.read_i32_bytes(data) as i64)
}
flags => bail!("Unsupported x86 implicit relocation {flags:?}"),
match self.arch {
Architecture::X86 => match flags {
RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32) => {
let data =
section.data()?[address as usize..address as usize + 4].try_into()?;
Ok(self.endianness.read_i32_bytes(data) as i64)
}
flags => bail!("Unsupported x86 implicit relocation {flags:?}"),
},
Architecture::X86_64 => match flags {
RelocationFlags::Coff(pe::IMAGE_REL_AMD64_ADDR32NB | pe::IMAGE_REL_AMD64_REL32) => {
let data =
section.data()?[address as usize..address as usize + 4].try_into()?;
Ok(self.endianness.read_i32_bytes(data) as i64)
}
RelocationFlags::Coff(pe::IMAGE_REL_AMD64_ADDR64) => {
let data =
section.data()?[address as usize..address as usize + 8].try_into()?;
Ok(self.endianness.read_i64_bytes(data))
}
flags => bail!("Unsupported x86-64 implicit relocation {flags:?}"),
},
}
}
@@ -152,166 +222,140 @@ impl ObjArch for ObjArchX86 {
}
}
fn display_reloc(&self, flags: RelocationFlags) -> Cow<'static, str> {
match flags {
RelocationFlags::Coff { typ } => match typ {
pe::IMAGE_REL_I386_DIR32 => Cow::Borrowed("IMAGE_REL_I386_DIR32"),
pe::IMAGE_REL_I386_REL32 => Cow::Borrowed("IMAGE_REL_I386_REL32"),
_ => Cow::Owned(format!("<{flags:?}>")),
fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
match self.arch {
Architecture::X86 => match flags {
RelocationFlags::Coff(typ) => match typ {
pe::IMAGE_REL_I386_DIR32 => Some("IMAGE_REL_I386_DIR32"),
pe::IMAGE_REL_I386_REL32 => Some("IMAGE_REL_I386_REL32"),
_ => None,
},
_ => None,
},
Architecture::X86_64 => match flags {
RelocationFlags::Coff(typ) => match typ {
pe::IMAGE_REL_AMD64_ADDR64 => Some("IMAGE_REL_AMD64_ADDR64"),
pe::IMAGE_REL_AMD64_ADDR32NB => Some("IMAGE_REL_AMD64_ADDR32NB"),
pe::IMAGE_REL_AMD64_REL32 => Some("IMAGE_REL_AMD64_REL32"),
_ => None,
},
_ => None,
},
_ => Cow::Owned(format!("<{flags:?}>")),
}
}
fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
self.reloc_size(flags).unwrap_or(1)
}
}
fn replace_arg(
from: OpKind,
to: ObjInsArg,
args: &mut [ObjInsArg],
instruction: &Instruction,
ins_operands: &[Option<u32>],
) -> Result<bool> {
let mut replace = None;
for i in 0..instruction.op_count() {
let op_kind = instruction.op_kind(i);
if op_kind == from {
replace = Some(i);
break;
}
}
if let Some(i) = replace {
for (j, arg) in args.iter_mut().enumerate() {
if ins_operands[j] == Some(i) {
*arg = to;
return Ok(true);
}
}
}
Ok(false)
}
struct InstructionFormatterOutput {
formatted: String,
ins: ObjIns,
struct InstructionFormatterOutput<'a> {
cb: &'a mut dyn FnMut(InstructionPart<'_>) -> Result<()>,
reloc_replace: Option<(OpKind, NumberKind, u64)>,
error: Option<anyhow::Error>,
ins_operands: Vec<Option<u32>>,
skip_next: bool,
}
impl InstructionFormatterOutput {
fn push_signed(&mut self, value: i64) {
// The formatter writes the '-' operator and then gives us a negative value,
// so convert it to a positive value to avoid double negatives
if value < 0
&& matches!(self.ins.args.last(), Some(ObjInsArg::Arg(ObjInsArgValue::Opaque(v))) if v == "-")
{
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(value.wrapping_abs())));
} else {
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Signed(value)));
}
}
}
impl FormatterOutput for InstructionFormatterOutput {
fn write(&mut self, text: &str, kind: FormatterTextKind) {
self.formatted.push_str(text);
// Skip whitespace after the mnemonic
if self.ins.args.is_empty() && kind == FormatterTextKind::Text {
impl InstructionFormatterOutput<'_> {
fn push_signed(&mut self, mut value: i64) {
if self.error.is_some() {
return;
}
self.ins_operands.push(None);
// The formatter writes the '-' operator and then gives us a negative value,
// so convert it to a positive value to avoid double negatives
if value < 0 {
value = value.wrapping_abs();
}
if let Err(e) = (self.cb)(InstructionPart::signed(value)) {
self.error = Some(e);
}
}
}
impl FormatterOutput for InstructionFormatterOutput<'_> {
fn write(&mut self, text: &str, kind: FormatterTextKind) {
if self.error.is_some() {
return;
}
// Skip whitespace after the mnemonic
if self.skip_next {
self.skip_next = false;
if kind == FormatterTextKind::Text && text == " " {
return;
}
}
match kind {
FormatterTextKind::Text | FormatterTextKind::Punctuation => {
self.ins.args.push(ObjInsArg::PlainText(text.to_string().into()));
}
FormatterTextKind::Keyword | FormatterTextKind::Operator => {
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(text.to_string().into())));
}
_ => {
if self.error.is_none() {
self.error = Some(anyhow!("x86: Unsupported FormatterTextKind {:?}", kind));
if let Err(e) = (self.cb)(InstructionPart::basic(text)) {
self.error = Some(e);
}
}
FormatterTextKind::Prefix
| FormatterTextKind::Keyword
| FormatterTextKind::Operator => {
if let Err(e) = (self.cb)(InstructionPart::opaque(text)) {
self.error = Some(e);
}
}
_ => self.error = Some(anyhow!("x86: Unsupported FormatterTextKind {:?}", kind)),
}
}
fn write_prefix(&mut self, _instruction: &Instruction, text: &str, _prefix: PrefixKind) {
self.formatted.push_str(text);
self.ins_operands.push(None);
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(text.to_string().into())));
}
fn write_mnemonic(&mut self, _instruction: &Instruction, text: &str) {
self.formatted.push_str(text);
// TODO: can iced-x86 guarantee 'static here?
self.ins.mnemonic = Cow::Owned(text.to_string());
fn write_mnemonic(&mut self, instruction: &Instruction, text: &str) {
if self.error.is_some() {
return;
}
if let Err(e) = (self.cb)(InstructionPart::opcode(text, instruction.mnemonic() as u16)) {
self.error = Some(e);
}
// Skip whitespace after the mnemonic
self.skip_next = true;
}
fn write_number(
&mut self,
_instruction: &Instruction,
instruction: &Instruction,
_operand: u32,
instruction_operand: Option<u32>,
text: &str,
_text: &str,
value: u64,
number_kind: NumberKind,
kind: FormatterTextKind,
) {
self.formatted.push_str(text);
self.ins_operands.push(instruction_operand);
if self.error.is_some() {
return;
}
// Handle relocations
match kind {
FormatterTextKind::LabelAddress => {
if let Some(reloc) = self.ins.reloc.as_ref() {
if matches!(reloc.flags, RelocationFlags::Coff {
typ: pe::IMAGE_REL_I386_DIR32 | pe::IMAGE_REL_I386_REL32
}) {
self.ins.args.push(ObjInsArg::Reloc);
return;
} else if self.error.is_none() {
self.error = Some(anyhow!(
"x86: Unsupported LabelAddress relocation flags {:?}",
reloc.flags
));
}
if let (Some(operand), Some((target_op_kind, target_number_kind, target_value))) =
(instruction_operand, self.reloc_replace)
{
if instruction.op_kind(operand) == target_op_kind
&& number_kind == target_number_kind
&& value == target_value
{
if let Err(e) = (self.cb)(InstructionPart::reloc()) {
self.error = Some(e);
}
self.ins.args.push(ObjInsArg::BranchDest(value));
self.ins.branch_dest = Some(value);
return;
}
FormatterTextKind::FunctionAddress => {
if let Some(reloc) = self.ins.reloc.as_ref() {
if matches!(reloc.flags, RelocationFlags::Coff {
typ: pe::IMAGE_REL_I386_REL32
}) {
self.ins.args.push(ObjInsArg::Reloc);
return;
} else if self.error.is_none() {
self.error = Some(anyhow!(
"x86: Unsupported FunctionAddress relocation flags {:?}",
reloc.flags
));
}
}
}
if let FormatterTextKind::LabelAddress | FormatterTextKind::FunctionAddress = kind {
if let Err(e) = (self.cb)(InstructionPart::branch_dest(value)) {
self.error = Some(e);
}
_ => {}
return;
}
match number_kind {
NumberKind::Int8 => {
self.push_signed(value as i8 as i64);
}
NumberKind::Int16 => {
self.push_signed(value as i16 as i64);
}
NumberKind::Int32 => {
self.push_signed(value as i32 as i64);
}
NumberKind::Int64 => {
self.push_signed(value as i64);
}
NumberKind::Int8 => self.push_signed(value as i8 as i64),
NumberKind::Int16 => self.push_signed(value as i16 as i64),
NumberKind::Int32 => self.push_signed(value as i32 as i64),
NumberKind::Int64 => self.push_signed(value as i64),
NumberKind::UInt8 | NumberKind::UInt16 | NumberKind::UInt32 | NumberKind::UInt64 => {
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Unsigned(value)));
if let Err(e) = (self.cb)(InstructionPart::unsigned(value)) {
self.error = Some(e);
}
}
}
}
@@ -320,25 +364,211 @@ impl FormatterOutput for InstructionFormatterOutput {
&mut self,
_instruction: &Instruction,
_operand: u32,
instruction_operand: Option<u32>,
_instruction_operand: Option<u32>,
text: &str,
_decorator: DecoratorKind,
) {
self.formatted.push_str(text);
self.ins_operands.push(instruction_operand);
self.ins.args.push(ObjInsArg::PlainText(text.to_string().into()));
if self.error.is_some() {
return;
}
if let Err(e) = (self.cb)(InstructionPart::basic(text)) {
self.error = Some(e);
}
}
fn write_register(
&mut self,
_instruction: &Instruction,
_operand: u32,
instruction_operand: Option<u32>,
_instruction_operand: Option<u32>,
text: &str,
_register: Register,
) {
self.formatted.push_str(text);
self.ins_operands.push(instruction_operand);
self.ins.args.push(ObjInsArg::Arg(ObjInsArgValue::Opaque(text.to_string().into())));
if self.error.is_some() {
return;
}
if let Err(e) = (self.cb)(InstructionPart::opaque(text)) {
self.error = Some(e);
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::obj::{Relocation, ResolvedRelocation};
#[test]
fn test_scan_instructions() {
let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
let code = [
0xc7, 0x85, 0x68, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x8b, 0x04, 0x85, 0x00,
0x00, 0x00, 0x00,
];
let scanned = arch.scan_instructions(0, &code, 0, &DiffObjConfig::default()).unwrap();
assert_eq!(scanned.len(), 2);
assert_eq!(scanned[0].ins_ref.address, 0);
assert_eq!(scanned[0].ins_ref.size, 10);
assert_eq!(scanned[0].ins_ref.opcode, iced_x86::Mnemonic::Mov as u16);
assert_eq!(scanned[0].branch_dest, None);
assert_eq!(scanned[1].ins_ref.address, 10);
assert_eq!(scanned[1].ins_ref.size, 7);
assert_eq!(scanned[1].ins_ref.opcode, iced_x86::Mnemonic::Mov as u16);
assert_eq!(scanned[1].branch_dest, None);
}
#[test]
fn test_process_instruction() {
let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
let code = [0xc7, 0x85, 0x68, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00];
let opcode = iced_x86::Mnemonic::Mov as u16;
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1234, size: 10, opcode },
code: &code,
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
assert_eq!(parts, &[
InstructionPart::opcode("mov", opcode),
InstructionPart::opaque("dword"),
InstructionPart::basic(" "),
InstructionPart::opaque("ptr"),
InstructionPart::basic(" "),
InstructionPart::basic("["),
InstructionPart::opaque("ebp"),
InstructionPart::opaque("-"),
InstructionPart::signed(152i64),
InstructionPart::basic("]"),
InstructionPart::basic(","),
InstructionPart::basic(" "),
InstructionPart::unsigned(0u64),
]);
}
#[test]
fn test_process_instruction_with_reloc_1() {
let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
let code = [0xc7, 0x85, 0x68, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00];
let opcode = iced_x86::Mnemonic::Mov as u16;
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1234, size: 10, opcode },
code: &code,
relocation: Some(ResolvedRelocation {
relocation: &Relocation {
flags: RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32),
address: 0x1234 + 6,
target_symbol: 0,
addend: 0,
},
symbol: &Default::default(),
}),
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
assert_eq!(parts, &[
InstructionPart::opcode("mov", opcode),
InstructionPart::opaque("dword"),
InstructionPart::basic(" "),
InstructionPart::opaque("ptr"),
InstructionPart::basic(" "),
InstructionPart::basic("["),
InstructionPart::opaque("ebp"),
InstructionPart::opaque("-"),
InstructionPart::signed(152i64),
InstructionPart::basic("]"),
InstructionPart::basic(","),
InstructionPart::basic(" "),
InstructionPart::reloc(),
]);
}
#[test]
fn test_process_instruction_with_reloc_2() {
let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
let code = [0x8b, 0x04, 0x85, 0x00, 0x00, 0x00, 0x00];
let opcode = iced_x86::Mnemonic::Mov as u16;
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1234, size: 7, opcode },
code: &code,
relocation: Some(ResolvedRelocation {
relocation: &Relocation {
flags: RelocationFlags::Coff(pe::IMAGE_REL_I386_DIR32),
address: 0x1234 + 3,
target_symbol: 0,
addend: 0,
},
symbol: &Default::default(),
}),
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
assert_eq!(parts, &[
InstructionPart::opcode("mov", opcode),
InstructionPart::opaque("eax"),
InstructionPart::basic(","),
InstructionPart::basic(" "),
InstructionPart::basic("["),
InstructionPart::opaque("eax"),
InstructionPart::opaque("*"),
InstructionPart::signed(4),
InstructionPart::opaque("+"),
InstructionPart::reloc(),
InstructionPart::basic("]"),
]);
}
#[test]
fn test_process_instruction_with_reloc_3() {
let arch = ArchX86 { arch: Architecture::X86, endianness: object::Endianness::Little };
let code = [0xe8, 0x00, 0x00, 0x00, 0x00];
let opcode = iced_x86::Mnemonic::Call as u16;
let mut parts = Vec::new();
arch.display_instruction(
ResolvedInstructionRef {
ins_ref: InstructionRef { address: 0x1234, size: 5, opcode },
code: &code,
relocation: Some(ResolvedRelocation {
relocation: &Relocation {
flags: RelocationFlags::Coff(pe::IMAGE_REL_I386_REL32),
address: 0x1234 + 1,
target_symbol: 0,
addend: 0,
},
symbol: &Default::default(),
}),
..Default::default()
},
&DiffObjConfig::default(),
&mut |part| {
parts.push(part.into_static());
Ok(())
},
)
.unwrap();
assert_eq!(parts, &[InstructionPart::opcode("call", opcode), InstructionPart::reloc()]);
}
}

View File

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

View File

@@ -1,12 +1,18 @@
#![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 serde_json::error::Category;
// Protobuf report types
include!(concat!(env!("OUT_DIR"), "/objdiff.report.rs"));
#[cfg(feature = "serde")]
include!(concat!(env!("OUT_DIR"), "/objdiff.report.serde.rs"));
pub const REPORT_VERSION: u32 = 2;
@@ -15,23 +21,30 @@ impl Report {
/// Attempts to parse the report as binary protobuf or JSON.
pub fn parse(data: &[u8]) -> Result<Self> {
if data.is_empty() {
bail!(std::io::Error::from(std::io::ErrorKind::UnexpectedEof));
bail!("Empty data");
}
let report = if data[0] == b'{' {
// 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 {
// Load as binary protobuf
Self::decode(data)?
Self::decode(data).map_err(|e| anyhow::Error::msg(e.to_string()))?
};
Ok(report)
}
#[cfg(feature = "serde")]
/// 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> {
match serde_json::from_slice::<Self>(bytes) {
Ok(report) => Ok(report),
Err(e) => {
use serde_json::error::Category;
match e.classify() {
Category::Io | Category::Eof | Category::Syntax => Err(e),
Category::Data => {
@@ -304,7 +317,8 @@ impl FromIterator<Measures> for Measures {
}
// 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 {
fuzzy_match_percent: f32,
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 {
name: String,
fuzzy_match_percent: f32,
@@ -351,11 +366,11 @@ struct LegacyReportUnit {
matched_data: u64,
total_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>,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
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>,
sections: 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 {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
demangled_name: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_hex",
deserialize_with = "deserialize_hex"
#[cfg_attr(
feature = "serde",
serde(
default,
skip_serializing_if = "Option::is_none",
serialize_with = "serialize_hex",
deserialize_with = "deserialize_hex"
)
)]
address: Option<u64>,
size: u64,
@@ -419,15 +438,13 @@ impl From<LegacyReportItem> for ReportItem {
}
}
#[cfg(feature = "serde")]
fn serialize_hex<S>(x: &Option<u64>, s: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
if let Some(x) = x {
s.serialize_str(&format!("{:#x}", x))
} else {
s.serialize_none()
}
if let Some(x) = x { 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>
where D: serde::Deserializer<'de> {
use serde::Deserialize;

View File

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

View File

@@ -0,0 +1,104 @@
pub mod watcher;
use std::process::Command;
use typed_path::{Utf8PlatformPathBuf, Utf8UnixPath};
pub struct BuildStatus {
pub success: bool,
pub cmdline: String,
pub stdout: String,
pub stderr: String,
}
impl Default for BuildStatus {
fn default() -> Self {
BuildStatus {
success: true,
cmdline: String::new(),
stdout: String::new(),
stderr: String::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct BuildConfig {
pub project_dir: Option<Utf8PlatformPathBuf>,
pub custom_make: Option<String>,
pub custom_args: Option<Vec<String>>,
#[allow(unused)]
pub selected_wsl_distro: Option<String>,
}
pub fn run_make(config: &BuildConfig, arg: &Utf8UnixPath) -> BuildStatus {
let Some(cwd) = &config.project_dir else {
return BuildStatus {
success: false,
stderr: "Missing project dir".to_string(),
..Default::default()
};
};
let make = config.custom_make.as_deref().unwrap_or("make");
let make_args = config.custom_args.as_deref().unwrap_or(&[]);
#[cfg(not(windows))]
let mut command = {
let mut command = Command::new(make);
command.current_dir(cwd).args(make_args).arg(arg);
command
};
#[cfg(windows)]
let mut command = {
use std::os::windows::process::CommandExt;
let mut command = if config.selected_wsl_distro.is_some() {
Command::new("wsl")
} else {
Command::new(make)
};
if let Some(distro) = &config.selected_wsl_distro {
// Strip distro root prefix \\wsl.localhost\{distro}
let wsl_path_prefix = format!("\\\\wsl.localhost\\{}", distro);
let cwd = match cwd.strip_prefix(wsl_path_prefix) {
Ok(new_cwd) => Utf8UnixPath::new("/").join(new_cwd.with_unix_encoding()),
Err(_) => cwd.with_unix_encoding(),
};
command
.arg("--cd")
.arg(cwd.as_str())
.arg("-d")
.arg(distro)
.arg("--")
.arg(make)
.args(make_args)
.arg(arg.as_str());
} else {
command.current_dir(cwd).args(make_args).arg(arg.as_str());
}
command.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW);
command
};
let mut cmdline = shell_escape::escape(command.get_program().to_string_lossy()).into_owned();
for arg in command.get_args() {
cmdline.push(' ');
cmdline.push_str(shell_escape::escape(arg.to_string_lossy()).as_ref());
}
let output = match command.output() {
Ok(output) => output,
Err(e) => {
return BuildStatus {
success: false,
cmdline,
stdout: Default::default(),
stderr: e.to_string(),
};
}
};
// Try from_utf8 first to avoid copying the buffer if it's valid, then fall back to from_utf8_lossy
let stdout = String::from_utf8(output.stdout)
.unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned());
let stderr = String::from_utf8(output.stderr)
.unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned());
BuildStatus { success: output.status.success(), cmdline, stdout, stderr }
}

View File

@@ -0,0 +1,75 @@
use std::{
fs,
path::{Path, PathBuf},
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
task::Waker,
time::Duration,
};
use globset::GlobSet;
use notify::RecursiveMode;
use notify_debouncer_full::{DebounceEventResult, new_debouncer_opt};
pub type Watcher = notify_debouncer_full::Debouncer<
notify::RecommendedWatcher,
notify_debouncer_full::RecommendedCache,
>;
pub struct WatcherState {
pub config_path: Option<PathBuf>,
pub left_obj_path: Option<PathBuf>,
pub right_obj_path: Option<PathBuf>,
pub patterns: GlobSet,
}
pub fn create_watcher(
modified: Arc<AtomicBool>,
project_dir: &Path,
patterns: GlobSet,
waker: Waker,
) -> notify::Result<Watcher> {
let base_dir = fs::canonicalize(project_dir)?;
let base_dir_clone = base_dir.clone();
let timeout = Duration::from_millis(200);
let config = notify::Config::default().with_poll_interval(Duration::from_secs(2));
let mut debouncer = new_debouncer_opt(
timeout,
None,
move |result: DebounceEventResult| match result {
Ok(events) => {
let mut any_match = false;
for event in events.iter() {
if !matches!(
event.kind,
notify::EventKind::Modify(..)
| notify::EventKind::Create(..)
| notify::EventKind::Remove(..)
) {
continue;
}
for path in &event.paths {
let Ok(path) = path.strip_prefix(&base_dir_clone) else {
continue;
};
if patterns.is_match(path) {
// log::info!("File modified: {}", path.display());
any_match = true;
}
}
}
if any_match {
modified.store(true, Ordering::Relaxed);
waker.wake_by_ref();
}
}
Err(errors) => errors.iter().for_each(|e| log::error!("Watch error: {e:?}")),
},
notify_debouncer_full::RecommendedCache::new(),
config,
)?;
debouncer.watch(base_dir, RecursiveMode::Recursive)?;
Ok(debouncer)
}

View File

@@ -1,36 +1,47 @@
use std::{
fs,
fs::File,
io::{BufReader, BufWriter, Read},
path::{Path, PathBuf},
pub mod path;
use alloc::{
collections::BTreeMap,
string::{String, ToString},
vec::Vec,
};
use anyhow::{anyhow, Context, Result};
use bimap::BiBTreeMap;
use filetime::FileTime;
use anyhow::{Context, Result, anyhow};
use globset::{Glob, GlobSet, GlobSetBuilder};
use path::unix_path_serde_option;
use typed_path::Utf8UnixPathBuf;
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
#[derive(Default, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
pub struct ProjectConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
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>,
#[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>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target_dir: Option<PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub base_dir: Option<PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(
feature = "serde",
serde(with = "unix_path_serde_option", 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>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub build_target: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub watch_patterns: Option<Vec<Glob>>,
#[serde(default, alias = "objects", skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub watch_patterns: Option<Vec<String>>,
#[cfg_attr(
feature = "serde",
serde(alias = "objects", skip_serializing_if = "Option::is_none")
)]
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>>,
}
@@ -38,11 +49,6 @@ impl ProjectConfig {
#[inline]
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]
pub fn progress_categories(&self) -> &[ProjectProgressCategory] {
self.progress_categories.as_deref().unwrap_or_default()
@@ -52,53 +58,75 @@ impl ProjectConfig {
pub fn progress_categories_mut(&mut self) -> &mut Vec<ProjectProgressCategory> {
self.progress_categories.get_or_insert_with(Vec::new)
}
pub fn build_watch_patterns(&self) -> Result<Vec<Glob>, globset::Error> {
Ok(if let Some(watch_patterns) = &self.watch_patterns {
watch_patterns
.iter()
.map(|s| Glob::new(s))
.collect::<Result<Vec<Glob>, globset::Error>>()?
} else {
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
})
}
}
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
#[derive(Default, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
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>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path: Option<PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub target_path: Option<PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub base_path: Option<PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(
feature = "serde",
serde(with = "unix_path_serde_option", skip_serializing_if = "Option::is_none")
)]
pub path: Option<Utf8UnixPathBuf>,
#[cfg_attr(
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")]
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")]
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>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub metadata: Option<ProjectObjectMetadata>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub symbol_mappings: Option<SymbolMappings>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub symbol_mappings: Option<BTreeMap<String, String>>,
}
pub type SymbolMappings = BiBTreeMap<String, String>;
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
#[derive(Default, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
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>,
#[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>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(
feature = "serde",
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>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub auto_generated: Option<bool>,
}
#[derive(Default, Clone, serde::Serialize, serde::Deserialize)]
#[derive(Default, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
pub struct ProjectProgressCategory {
#[serde(default)]
pub id: String,
#[serde(default)]
pub name: String,
}
@@ -107,33 +135,12 @@ impl ProjectObject {
if let Some(name) = &self.name {
name
} else if let Some(path) = &self.path {
path.to_str().unwrap_or("[invalid path]")
path.as_str()
} else {
"[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> {
#[expect(deprecated)]
self.metadata.as_ref().and_then(|m| m.complete).or(self.complete)
@@ -148,24 +155,36 @@ impl ProjectObject {
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())
}
pub fn progress_categories(&self) -> &[String] {
self.metadata.as_ref().and_then(|m| m.progress_categories.as_deref()).unwrap_or_default()
}
pub fn auto_generated(&self) -> Option<bool> {
self.metadata.as_ref().and_then(|m| m.auto_generated)
}
}
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[derive(Default, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize), serde(default))]
pub struct ScratchConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
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>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub c_flags: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ctx_path: Option<PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(
feature = "serde",
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>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub preset_id: Option<u32>,
}
@@ -176,16 +195,24 @@ pub const DEFAULT_WATCH_PATTERNS: &[&str] = &[
"*.inc", "*.py", "*.yml", "*.txt", "*.json",
];
#[derive(Clone, Eq, PartialEq)]
pub struct ProjectConfigInfo {
pub path: PathBuf,
pub timestamp: Option<FileTime>,
pub fn default_watch_patterns() -> Vec<Glob> {
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
}
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() {
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;
};
let metadata = file.metadata();
@@ -193,12 +220,9 @@ pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectC
if !metadata.is_file() {
continue;
}
let ts = FileTime::from_last_modification_time(&metadata);
let mut reader = BufReader::new(file);
let mut result = match filename.contains("json") {
true => read_json_config(&mut reader),
false => read_yml_config(&mut reader),
};
let ts = filetime::FileTime::from_last_modification_time(&metadata);
let mut reader = std::io::BufReader::new(file);
let mut result = read_json_config(&mut reader);
if let Ok(config) = &result {
// Validate min_version if present
if let Err(e) = validate_min_version(config) {
@@ -211,40 +235,42 @@ pub fn try_project_config(dir: &Path) -> Option<(Result<ProjectConfig>, ProjectC
None
}
#[cfg(feature = "std")]
pub fn save_project_config(
config: &ProjectConfig,
info: &ProjectConfigInfo,
) -> Result<ProjectConfigInfo> {
if let Some(last_ts) = info.timestamp {
// Check if the file has changed since we last read it
if let Ok(metadata) = fs::metadata(&info.path) {
let ts = FileTime::from_last_modification_time(&metadata);
if let Ok(metadata) = std::fs::metadata(&info.path) {
let ts = filetime::FileTime::from_last_modification_time(&metadata);
if ts != last_ts {
return Err(anyhow!("Config file has changed since last read"));
}
}
}
let mut writer =
BufWriter::new(File::create(&info.path).context("Failed to create config file")?);
let mut writer = std::io::BufWriter::new(
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");
match ext {
"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}")),
}?;
let file = writer.into_inner().context("Failed to flush file")?;
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) })
}
fn validate_min_version(config: &ProjectConfig) -> Result<()> {
let Some(min_version) = &config.min_version else { return Ok(()) };
let version = semver::Version::parse(env!("CARGO_PKG_VERSION"))
.map_err(|e| anyhow::Error::msg(e.to_string()))
.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 {
Ok(())
} else {
@@ -252,15 +278,12 @@ fn validate_min_version(config: &ProjectConfig) -> Result<()> {
}
}
fn read_yml_config<R: Read>(reader: &mut R) -> Result<ProjectConfig> {
Ok(serde_yaml::from_reader(reader)?)
}
fn read_json_config<R: Read>(reader: &mut R) -> Result<ProjectConfig> {
#[cfg(feature = "std")]
fn read_json_config<R: std::io::Read>(reader: &mut R) -> Result<ProjectConfig> {
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();
for glob in vec {
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,371 +1,504 @@
use std::{cmp::max, collections::BTreeMap};
use anyhow::{anyhow, Result};
use similar::{capture_diff_slices_deadline, Algorithm};
use crate::{
arch::ProcessCodeResult,
diff::{
DiffObjConfig, ObjInsArgDiff, ObjInsBranchFrom, ObjInsBranchTo, ObjInsDiff, ObjInsDiffKind,
ObjSymbolDiff,
},
obj::{ObjInfo, ObjInsArg, ObjReloc, ObjSymbolFlags, SymbolRef},
use alloc::{
collections::{BTreeMap, btree_map},
string::{String, ToString},
vec,
vec::Vec,
};
pub fn process_code_symbol(
obj: &ObjInfo,
symbol_ref: SymbolRef,
config: &DiffObjConfig,
) -> Result<ProcessCodeResult> {
let (section, symbol) = obj.section_symbol(symbol_ref);
let section = section.ok_or_else(|| anyhow!("Code symbol section not found"))?;
let code = &section.data
[symbol.section_address as usize..(symbol.section_address + symbol.size) as usize];
obj.arch.process_code(
symbol.address,
code,
section.orig_index,
&section.relocations,
&section.line_info,
config,
)
use anyhow::{Context, Result, anyhow, ensure};
use super::{
DiffObjConfig, FunctionRelocDiffs, InstructionArgDiffIndex, InstructionBranchFrom,
InstructionBranchTo, InstructionDiffKind, InstructionDiffRow, SymbolDiff,
display::display_ins_data_literals,
};
use crate::obj::{
InstructionArg, InstructionArgValue, InstructionRef, Object, ResolvedInstructionRef,
ResolvedRelocation, ScannedInstruction, SymbolFlag, SymbolKind,
};
pub fn no_diff_code(
obj: &Object,
symbol_idx: usize,
diff_config: &DiffObjConfig,
) -> Result<SymbolDiff> {
let symbol = &obj.symbols[symbol_idx];
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(symbol.address, data, section_index, diff_config)?;
let mut instruction_rows = Vec::<InstructionDiffRow>::new();
for i in &ops {
instruction_rows
.push(InstructionDiffRow { ins_ref: Some(i.ins_ref), ..Default::default() });
}
resolve_branches(obj, section_index, &ops, &mut instruction_rows);
Ok(SymbolDiff { target_symbol: None, match_percent: None, diff_score: None, instruction_rows })
}
pub fn no_diff_code(out: &ProcessCodeResult, symbol_ref: SymbolRef) -> Result<ObjSymbolDiff> {
let mut diff = Vec::<ObjInsDiff>::new();
for i in &out.insts {
diff.push(ObjInsDiff {
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 })
}
const PENALTY_IMM_DIFF: u64 = 1;
const PENALTY_REG_DIFF: u64 = 5;
const PENALTY_REPLACE: u64 = 60;
const PENALTY_INSERT_DELETE: u64 = 100;
pub fn diff_code(
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left_out: &ProcessCodeResult,
right_out: &ProcessCodeResult,
left_symbol_ref: SymbolRef,
right_symbol_ref: SymbolRef,
config: &DiffObjConfig,
) -> Result<(ObjSymbolDiff, ObjSymbolDiff)> {
let mut left_diff = Vec::<ObjInsDiff>::new();
let mut right_diff = Vec::<ObjInsDiff>::new();
diff_instructions(&mut left_diff, &mut right_diff, left_out, right_out)?;
left_obj: &Object,
right_obj: &Object,
left_symbol_idx: usize,
right_symbol_idx: usize,
diff_config: &DiffObjConfig,
) -> Result<(SymbolDiff, SymbolDiff)> {
let left_symbol = &left_obj.symbols[left_symbol_idx];
let right_symbol = &right_obj.symbols[right_symbol_idx];
let left_section = left_symbol
.section
.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);
resolve_branches(&mut right_diff);
let left_section_idx = left_symbol.section.unwrap();
let right_section_idx = right_symbol.section.unwrap();
let left_ops = left_obj.arch.scan_instructions(
left_symbol.address,
left_data,
left_section_idx,
diff_config,
)?;
let right_ops = right_obj.arch.scan_instructions(
right_symbol.address,
right_data,
right_section_idx,
diff_config,
)?;
let (mut left_rows, mut right_rows) = diff_instructions(&left_ops, &right_ops)?;
resolve_branches(left_obj, left_section_idx, &left_ops, &mut left_rows);
resolve_branches(right_obj, right_section_idx, &right_ops, &mut right_rows);
let mut diff_state = InsDiffState::default();
for (left, right) in left_diff.iter_mut().zip(right_diff.iter_mut()) {
let result = compare_ins(config, left_obj, right_obj, left, right, &mut diff_state)?;
left.kind = result.kind;
right.kind = result.kind;
left.arg_diff = result.left_args_diff;
right.arg_diff = result.right_args_diff;
let mut diff_state = InstructionDiffState::default();
for (left_row, right_row) in left_rows.iter_mut().zip(right_rows.iter_mut()) {
let result = diff_instruction(
left_obj,
right_obj,
left_symbol_idx,
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 percent = if diff_state.diff_count >= total {
0.0
let max_score = left_ops.len() as u64 * PENALTY_INSERT_DELETE;
let diff_score = diff_state.diff_score.min(max_score);
let match_percent = if max_score == 0 {
100.0
} 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((
ObjSymbolDiff {
symbol_ref: left_symbol_ref,
target_symbol: Some(right_symbol_ref),
instructions: left_diff,
match_percent: Some(percent),
SymbolDiff {
target_symbol: Some(right_symbol_idx),
match_percent: Some(match_percent),
diff_score: Some((diff_score, max_score)),
instruction_rows: left_rows,
},
ObjSymbolDiff {
symbol_ref: right_symbol_ref,
target_symbol: Some(left_symbol_ref),
instructions: right_diff,
match_percent: Some(percent),
SymbolDiff {
target_symbol: Some(left_symbol_idx),
match_percent: Some(match_percent),
diff_score: Some((diff_score, max_score)),
instruction_rows: right_rows,
},
))
}
fn diff_instructions(
left_diff: &mut Vec<ObjInsDiff>,
right_diff: &mut Vec<ObjInsDiff>,
left_code: &ProcessCodeResult,
right_code: &ProcessCodeResult,
) -> Result<()> {
let ops =
capture_diff_slices_deadline(Algorithm::Patience, &left_code.ops, &right_code.ops, None);
left_insts: &[ScannedInstruction],
right_insts: &[ScannedInstruction],
) -> Result<(Vec<InstructionDiffRow>, Vec<InstructionDiffRow>)> {
let left_ops = left_insts.iter().map(|i| i.ins_ref.opcode).collect::<Vec<_>>();
let right_ops = right_insts.iter().map(|i| i.ins_ref.opcode).collect::<Vec<_>>();
let ops = similar::capture_diff_slices(similar::Algorithm::Patience, &left_ops, &right_ops);
if ops.is_empty() {
left_diff.extend(
left_code
.insts
.iter()
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }),
);
right_diff.extend(
right_code
.insts
.iter()
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }),
);
return Ok(());
ensure!(left_insts.len() == right_insts.len());
let left_diff = left_insts
.iter()
.map(|i| InstructionDiffRow { ins_ref: Some(i.ins_ref), ..Default::default() })
.collect();
let right_diff = right_insts
.iter()
.map(|i| InstructionDiffRow { ins_ref: Some(i.ins_ref), ..Default::default() })
.collect();
return Ok((left_diff, right_diff));
}
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 {
let (_tag, left_range, right_range) = op.as_tag_tuple();
let len = max(left_range.len(), right_range.len());
left_diff.extend(
left_code.insts[left_range.clone()]
.iter()
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }),
);
right_diff.extend(
right_code.insts[right_range.clone()]
.iter()
.map(|i| ObjInsDiff { ins: Some(i.clone()), ..Default::default() }),
);
let len = left_range.len().max(right_range.len());
left_diff.extend(left_range.clone().map(|i| InstructionDiffRow {
ins_ref: Some(left_insts[i].ins_ref),
..Default::default()
}));
right_diff.extend(right_range.clone().map(|i| InstructionDiffRow {
ins_ref: Some(right_insts[i].ins_ref),
..Default::default()
}));
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 {
right_diff.extend((right_range.len()..len).map(|_| ObjInsDiff::default()));
right_diff.extend((right_range.len()..len).map(|_| InstructionDiffRow::default()));
}
}
Ok(())
Ok((left_diff, right_diff))
}
fn resolve_branches(vec: &mut [ObjInsDiff]) {
let mut branch_idx = 0usize;
fn arg_to_string(arg: &InstructionArg, reloc: Option<ResolvedRelocation>) -> String {
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(
obj: &Object,
section_index: usize,
ops: &[ScannedInstruction],
rows: &mut [InstructionDiffRow],
) {
let section = &obj.sections[section_index];
let mut branch_idx = 0u32;
// Map addresses to indices
let mut addr_map = BTreeMap::<u64, usize>::new();
for (i, ins_diff) in vec.iter().enumerate() {
if let Some(ins) = &ins_diff.ins {
addr_map.insert(ins.address, i);
let mut addr_map = BTreeMap::<u64, u32>::new();
for (i, ins_diff) in rows.iter().enumerate() {
if let Some(ins) = ins_diff.ins_ref {
addr_map.insert(ins.address, i as u32);
}
}
// Generate branches
let mut branches = BTreeMap::<usize, ObjInsBranchFrom>::new();
for (i, ins_diff) in vec.iter_mut().enumerate() {
if let Some(ins) = &ins_diff.ins {
if let Some(ins_idx) = ins.branch_dest.and_then(|a| addr_map.get(&a)) {
if let Some(branch) = branches.get_mut(ins_idx) {
ins_diff.branch_to =
Some(ObjInsBranchTo { ins_idx: *ins_idx, branch_idx: branch.branch_idx });
branch.ins_idx.push(i);
} else {
ins_diff.branch_to = Some(ObjInsBranchTo { ins_idx: *ins_idx, branch_idx });
branches.insert(*ins_idx, ObjInsBranchFrom { ins_idx: vec![i], branch_idx });
let mut branches = BTreeMap::<u32, InstructionBranchFrom>::new();
for ((i, ins_diff), ins) in
rows.iter_mut().enumerate().filter(|(_, row)| row.ins_ref.is_some()).zip(ops)
{
let branch_dest = if let Some(resolved) = section.relocation_at(obj, ins.ins_ref) {
if resolved.symbol.section == Some(section_index) {
// If the relocation target is in the same section, use it as the branch destination
resolved.symbol.address.checked_add_signed(resolved.relocation.addend)
} else {
None
}
} else {
ins.branch_dest
};
if let Some(ins_idx) = branch_dest.and_then(|a| addr_map.get(&a).copied()) {
match branches.entry(ins_idx) {
btree_map::Entry::Vacant(e) => {
ins_diff.branch_to = Some(InstructionBranchTo { ins_idx, branch_idx });
e.insert(InstructionBranchFrom { ins_idx: vec![i as u32], branch_idx });
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
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 {
left.target.address as i64 + left.addend == right.target.address as i64 + right.addend
pub(crate) fn address_eq(left: ResolvedRelocation, right: ResolvedRelocation) -> bool {
if right.symbol.size == 0 && left.symbol.size != 0 {
// The base relocation is against a pool but the target relocation isn't.
// This can happen in rare cases where the compiler will generate a pool+addend relocation
// in the base's data, but the one detected in the target is direct with no addend.
// Just check that the final address is the same so these count as a match.
left.symbol.address as i64 + left.relocation.addend
== right.symbol.address as i64 + right.relocation.addend
} else {
// But otherwise, if the compiler isn't using a pool, we're more strict and check that the
// target symbol address and relocation addend both match exactly.
left.symbol.address == right.symbol.address
&& left.relocation.addend == right.relocation.addend
}
}
fn section_name_eq(
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left_orig_section_index: usize,
right_orig_section_index: usize,
pub(crate) fn section_name_eq(
left_obj: &Object,
right_obj: &Object,
left_section_index: usize,
right_section_index: usize,
) -> bool {
let Some(left_section) =
left_obj.sections.iter().find(|s| s.orig_index == left_orig_section_index)
else {
return false;
};
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
left_obj.sections.get(left_section_index).is_some_and(|left_section| {
right_obj
.sections
.get(right_section_index)
.is_some_and(|right_section| left_section.name == right_section.name)
})
}
fn reloc_eq(
config: &DiffObjConfig,
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left_reloc: Option<&ObjReloc>,
right_reloc: Option<&ObjReloc>,
left_obj: &Object,
right_obj: &Object,
left_ins: ResolvedInstructionRef,
right_ins: ResolvedInstructionRef,
diff_config: &DiffObjConfig,
) -> bool {
let (Some(left), Some(right)) = (left_reloc, right_reloc) else {
return false;
let relax_reloc_diffs = diff_config.function_reloc_diffs == FunctionRelocDiffs::None;
let (left_reloc, right_reloc) = match (left_ins.relocation, right_ins.relocation) {
(Some(left_reloc), Some(right_reloc)) => (left_reloc, right_reloc),
// If relocations are relaxed, match if left is missing a reloc
(None, Some(_)) => return relax_reloc_diffs,
(None, None) => return true,
_ => return false,
};
if left.flags != right.flags {
if left_reloc.relocation.flags != right_reloc.relocation.flags {
return false;
}
if config.relax_reloc_diffs {
if relax_reloc_diffs {
return true;
}
let symbol_name_matches = left.target.name == right.target.name;
match (&left.target.orig_section_index, &right.target.orig_section_index) {
let symbol_name_addend_matches = left_reloc.symbol.name == right_reloc.symbol.name
&& left_reloc.relocation.addend == right_reloc.relocation.addend;
match (&left_reloc.symbol.section, &right_reloc.symbol.section) {
(Some(sl), Some(sr)) => {
// Match if section and name or address match
section_name_eq(left_obj, right_obj, *sl, *sr)
&& (symbol_name_matches || address_eq(left, right))
&& (diff_config.function_reloc_diffs == FunctionRelocDiffs::DataValue
|| symbol_name_addend_matches
|| address_eq(left_reloc, right_reloc))
&& (diff_config.function_reloc_diffs == FunctionRelocDiffs::NameAddress
|| left_reloc.symbol.kind != SymbolKind::Object
|| display_ins_data_literals(left_obj, left_ins)
== display_ins_data_literals(right_obj, right_ins))
}
(Some(_), None) => false,
(None, Some(_)) => {
// 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,
(None, None) => symbol_name_addend_matches,
}
}
fn arg_eq(
config: &DiffObjConfig,
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left: &ObjInsArg,
right: &ObjInsArg,
left_diff: &ObjInsDiff,
right_diff: &ObjInsDiff,
left_obj: &Object,
right_obj: &Object,
left_row: &InstructionDiffRow,
right_row: &InstructionDiffRow,
left_arg: &InstructionArg,
right_arg: &InstructionArg,
left_ins: ResolvedInstructionRef,
right_ins: ResolvedInstructionRef,
diff_config: &DiffObjConfig,
) -> bool {
match left {
ObjInsArg::PlainText(l) => match right {
ObjInsArg::PlainText(r) => l == r,
_ => false,
},
ObjInsArg::Arg(l) => match right {
ObjInsArg::Arg(r) => l == r,
match left_arg {
InstructionArg::Value(l) => match right_arg {
InstructionArg::Value(r) => l.loose_eq(r),
// 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
ObjInsArg::Reloc => config.relax_reloc_diffs,
InstructionArg::Reloc => diff_config.function_reloc_diffs == FunctionRelocDiffs::None,
_ => false,
},
ObjInsArg::Reloc => {
matches!(right, ObjInsArg::Reloc)
&& reloc_eq(
config,
left_obj,
right_obj,
left_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
right_diff.ins.as_ref().and_then(|i| i.reloc.as_ref()),
)
InstructionArg::Reloc => {
matches!(right_arg, InstructionArg::Reloc)
&& reloc_eq(left_obj, right_obj, left_ins, right_ins, diff_config)
}
ObjInsArg::BranchDest(_) => match right {
InstructionArg::BranchDest(_) => match right_arg {
// Compare dest instruction idx after diffing
ObjInsArg::BranchDest(_) => {
left_diff.branch_to.as_ref().map(|b| b.ins_idx)
== right_diff.branch_to.as_ref().map(|b| b.ins_idx)
InstructionArg::BranchDest(_) => {
left_row.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
// Useful for instances where the target object is created without relocations
ObjInsArg::Reloc => config.relax_reloc_diffs,
InstructionArg::Reloc => diff_config.function_reloc_diffs == FunctionRelocDiffs::None,
_ => false,
},
}
}
#[derive(Default)]
struct InsDiffState {
diff_count: usize,
left_arg_idx: usize,
right_arg_idx: usize,
left_args_idx: BTreeMap<String, usize>,
right_args_idx: BTreeMap<String, usize>,
struct InstructionDiffState {
diff_score: u64,
left_arg_idx: u32,
right_arg_idx: u32,
left_args_idx: BTreeMap<String, u32>,
right_args_idx: BTreeMap<String, u32>,
}
#[derive(Default)]
struct InsDiffResult {
kind: ObjInsDiffKind,
left_args_diff: Vec<Option<ObjInsArgDiff>>,
right_args_diff: Vec<Option<ObjInsArgDiff>>,
struct InstructionDiffResult {
kind: InstructionDiffKind,
left_args_diff: Vec<InstructionArgDiffIndex>,
right_args_diff: Vec<InstructionArgDiffIndex>,
}
fn compare_ins(
config: &DiffObjConfig,
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left: &ObjInsDiff,
right: &ObjInsDiff,
state: &mut InsDiffState,
) -> Result<InsDiffResult> {
let mut result = InsDiffResult::default();
if let (Some(left_ins), Some(right_ins)) = (&left.ins, &right.ins) {
// Count only non-PlainText args
let left_args_count = left_ins.iter_args().count();
let right_args_count = right_ins.iter_args().count();
if left_args_count != right_args_count || left_ins.op != right_ins.op {
// Totally different op
result.kind = ObjInsDiffKind::Replace;
state.diff_count += 1;
return Ok(result);
impl InstructionDiffResult {
#[inline]
const fn new(kind: InstructionDiffKind) -> Self {
Self { kind, left_args_diff: Vec::new(), right_args_diff: Vec::new() }
}
}
fn diff_instruction(
left_obj: &Object,
right_obj: &Object,
left_symbol_idx: usize,
right_symbol_idx: usize,
l: Option<InstructionRef>,
r: Option<InstructionRef>,
left_row: &InstructionDiffRow,
right_row: &InstructionDiffRow,
diff_config: &DiffObjConfig,
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 {
// Same op but different mnemonic, still cmp args
result.kind = ObjInsDiffKind::OpMismatch;
state.diff_count += 1;
state.diff_score += PENALTY_REG_DIFF;
result.kind = InstructionDiffKind::OpMismatch;
}
for (a, b) in left_ins.iter_args().zip(right_ins.iter_args()) {
if arg_eq(config, left_obj, right_obj, a, b, left, right) {
result.left_args_diff.push(None);
result.right_args_diff.push(None);
for (a, b) in left_ins.args.iter().zip(right_ins.args.iter()) {
if arg_eq(
left_obj,
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 {
if result.kind == ObjInsDiffKind::None {
result.kind = ObjInsDiffKind::ArgMismatch;
state.diff_count += 1;
state.diff_score += if let InstructionArg::Value(
InstructionArgValue::Signed(_) | InstructionArgValue::Unsigned(_),
) = a
{
PENALTY_IMM_DIFF
} else {
PENALTY_REG_DIFF
};
if result.kind == InstructionDiffKind::None {
result.kind = InstructionDiffKind::ArgMismatch;
}
let a_str = match a {
ObjInsArg::PlainText(arg) => arg.to_string(),
ObjInsArg::Arg(arg) => arg.to_string(),
ObjInsArg::Reloc => left_ins
.reloc
.as_ref()
.map_or_else(|| "<unknown>".to_string(), |r| r.target.name.clone()),
ObjInsArg::BranchDest(arg) => arg.to_string(),
let a_str = arg_to_string(a, left_resolved.relocation);
let a_diff = match state.left_args_idx.entry(a_str) {
btree_map::Entry::Vacant(e) => {
let idx = state.left_arg_idx;
state.left_arg_idx = idx + 1;
e.insert(idx);
idx
}
btree_map::Entry::Occupied(e) => *e.get(),
};
let a_diff = if let Some(idx) = state.left_args_idx.get(&a_str) {
ObjInsArgDiff { idx: *idx }
} else {
let idx = state.left_arg_idx;
state.left_args_idx.insert(a_str, idx);
state.left_arg_idx += 1;
ObjInsArgDiff { idx }
let b_str = arg_to_string(b, right_resolved.relocation);
let b_diff = match state.right_args_idx.entry(b_str) {
btree_map::Entry::Vacant(e) => {
let idx = state.right_arg_idx;
state.right_arg_idx = idx + 1;
e.insert(idx);
idx
}
btree_map::Entry::Occupied(e) => *e.get(),
};
let b_str = match b {
ObjInsArg::PlainText(arg) => arg.to_string(),
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));
result.left_args_diff.push(InstructionArgDiffIndex::new(a_diff));
result.right_args_diff.push(InstructionArgDiffIndex::new(b_diff));
}
}
} else if left.ins.is_some() {
result.kind = ObjInsDiffKind::Delete;
state.diff_count += 1;
} else {
result.kind = ObjInsDiffKind::Insert;
state.diff_count += 1;
return Ok(result);
}
Ok(result)
Ok(InstructionDiffResult::new(InstructionDiffKind::None))
}

View File

@@ -1,117 +1,232 @@
use std::cmp::{max, min, Ordering};
use alloc::{vec, vec::Vec};
use core::{cmp::Ordering, ops::Range};
use anyhow::{anyhow, Result};
use similar::{capture_diff_slices_deadline, get_diff_ratio, Algorithm};
use anyhow::{Result, anyhow};
use similar::{Algorithm, capture_diff_slices, get_diff_ratio};
use crate::{
diff::{ObjDataDiff, ObjDataDiffKind, ObjSectionDiff, ObjSymbolDiff},
obj::{ObjInfo, ObjSection, SymbolRef},
use super::{
DataDiff, DataDiffKind, DataRelocationDiff, ObjectDiff, SectionDiff, SymbolDiff,
code::{address_eq, section_name_eq},
};
use crate::obj::{Object, Relocation, ResolvedRelocation, Symbol, SymbolFlag, SymbolKind};
pub fn diff_bss_symbol(
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left_symbol_ref: SymbolRef,
right_symbol_ref: SymbolRef,
) -> Result<(ObjSymbolDiff, ObjSymbolDiff)> {
let (_, left_symbol) = left_obj.section_symbol(left_symbol_ref);
let (_, right_symbol) = right_obj.section_symbol(right_symbol_ref);
left_obj: &Object,
right_obj: &Object,
left_symbol_ref: usize,
right_symbol_ref: usize,
) -> Result<(SymbolDiff, SymbolDiff)> {
let left_symbol = &left_obj.symbols[left_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 };
Ok((
ObjSymbolDiff {
symbol_ref: left_symbol_ref,
SymbolDiff {
target_symbol: Some(right_symbol_ref),
instructions: vec![],
match_percent: Some(percent),
diff_score: None,
instruction_rows: vec![],
},
ObjSymbolDiff {
symbol_ref: right_symbol_ref,
SymbolDiff {
target_symbol: Some(left_symbol_ref),
instructions: vec![],
match_percent: Some(percent),
diff_score: None,
instruction_rows: vec![],
},
))
}
pub fn no_diff_symbol(_obj: &ObjInfo, symbol_ref: SymbolRef) -> ObjSymbolDiff {
ObjSymbolDiff { symbol_ref, target_symbol: None, instructions: vec![], match_percent: None }
fn reloc_eq(
left_obj: &Object,
right_obj: &Object,
left: ResolvedRelocation,
right: ResolvedRelocation,
) -> bool {
if left.relocation.flags != right.relocation.flags {
return false;
}
let symbol_name_addend_matches =
left.symbol.name == right.symbol.name && left.relocation.addend == right.relocation.addend;
match (left.symbol.section, right.symbol.section) {
(Some(sl), Some(sr)) => {
// Match if section and name+addend or address match
section_name_eq(left_obj, right_obj, sl, sr)
&& (symbol_name_addend_matches || address_eq(left, right))
}
(Some(_), None) => false,
(None, Some(_)) => {
// Match if possibly stripped weak symbol
symbol_name_addend_matches && right.symbol.flags.contains(SymbolFlag::Weak)
}
(None, None) => symbol_name_addend_matches,
}
}
#[inline]
pub fn resolve_relocation<'obj>(
symbols: &'obj [Symbol],
reloc: &'obj Relocation,
) -> ResolvedRelocation<'obj> {
let symbol = &symbols[reloc.target_symbol];
ResolvedRelocation { relocation: reloc, symbol }
}
/// Compares relocations contained with a certain data range.
fn diff_data_relocs_for_range<'left, 'right>(
left_obj: &'left Object,
right_obj: &'right Object,
left_section_idx: usize,
right_section_idx: usize,
left_range: Range<usize>,
right_range: Range<usize>,
) -> Vec<(DataDiffKind, Option<ResolvedRelocation<'left>>, Option<ResolvedRelocation<'right>>)> {
let left_section = &left_obj.sections[left_section_idx];
let right_section = &right_obj.sections[right_section_idx];
let mut diffs = Vec::new();
for left_reloc in left_section.relocations.iter() {
if !left_range.contains(&(left_reloc.address as usize)) {
continue;
}
let left_offset = left_reloc.address as usize - left_range.start;
let left_reloc = resolve_relocation(&left_obj.symbols, left_reloc);
let Some(right_reloc) = right_section.relocations.iter().find(|r| {
if !right_range.contains(&(r.address as usize)) {
return false;
}
let right_offset = r.address as usize - right_range.start;
right_offset == left_offset
}) else {
diffs.push((DataDiffKind::Delete, Some(left_reloc), None));
continue;
};
let right_reloc = resolve_relocation(&right_obj.symbols, right_reloc);
if reloc_eq(left_obj, right_obj, left_reloc, right_reloc) {
diffs.push((DataDiffKind::None, Some(left_reloc), Some(right_reloc)));
} else {
diffs.push((DataDiffKind::Replace, Some(left_reloc), Some(right_reloc)));
}
}
for right_reloc in right_section.relocations.iter() {
if !right_range.contains(&(right_reloc.address as usize)) {
continue;
}
let right_offset = right_reloc.address as usize - right_range.start;
let right_reloc = resolve_relocation(&right_obj.symbols, right_reloc);
let Some(_) = left_section.relocations.iter().find(|r| {
if !left_range.contains(&(r.address as usize)) {
return false;
}
let left_offset = r.address as usize - left_range.start;
left_offset == right_offset
}) else {
diffs.push((DataDiffKind::Insert, None, Some(right_reloc)));
continue;
};
// No need to check the cases for relocations being deleted or matching again.
// They were already handled in the loop over the left relocs.
}
diffs
}
/// Compare the data sections of two object files.
pub fn diff_data_section(
left: &ObjSection,
right: &ObjSection,
left_section_diff: &ObjSectionDiff,
right_section_diff: &ObjSectionDiff,
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
let left_max =
left.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0).min(left.size);
let right_max =
right.symbols.iter().map(|s| s.section_address + s.size).max().unwrap_or(0).min(right.size);
let left_data = &left.data[..left_max as usize];
let right_data = &right.data[..right_max as usize];
let ops = capture_diff_slices_deadline(Algorithm::Patience, left_data, right_data, None);
left_obj: &Object,
right_obj: &Object,
left_diff: &ObjectDiff,
right_diff: &ObjectDiff,
left_section_idx: usize,
right_section_idx: usize,
) -> Result<(SectionDiff, SectionDiff)> {
let left_section = &left_obj.sections[left_section_idx];
let right_section = &right_obj.sections[right_section_idx];
let left_max = left_obj
.symbols
.iter()
.filter_map(|s| {
if s.section != Some(left_section_idx) || s.kind == SymbolKind::Section {
return None;
}
s.address.checked_sub(left_section.address).map(|a| a + s.size)
})
.max()
.unwrap_or(0)
.min(left_section.size);
let right_max = right_obj
.symbols
.iter()
.filter_map(|s| {
if s.section != Some(right_section_idx) || s.kind == SymbolKind::Section {
return None;
}
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 mut left_diff = Vec::<ObjDataDiff>::new();
let mut right_diff = Vec::<ObjDataDiff>::new();
let mut left_data_diff = Vec::<DataDiff>::new();
let mut right_data_diff = Vec::<DataDiff>::new();
for op in ops {
let (tag, left_range, right_range) = op.as_tag_tuple();
let left_len = left_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 {
similar::DiffTag::Equal => ObjDataDiffKind::None,
similar::DiffTag::Delete => ObjDataDiffKind::Delete,
similar::DiffTag::Insert => ObjDataDiffKind::Insert,
similar::DiffTag::Equal => DataDiffKind::None,
similar::DiffTag::Delete => DataDiffKind::Delete,
similar::DiffTag::Insert => DataDiffKind::Insert,
similar::DiffTag::Replace => {
// Ensure replacements are equal length
len = min(left_len, right_len);
ObjDataDiffKind::Replace
len = left_len.min(right_len);
DataDiffKind::Replace
}
};
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(),
let left_data = &left_section.data[left_range];
let right_data = &right_section.data[right_range];
left_data_diff.push(DataDiff {
data: left_data[..len.min(left_data.len())].to_vec(),
kind,
len,
..Default::default()
});
right_diff.push(ObjDataDiff {
data: right_data[..min(len, right_data.len())].to_vec(),
right_data_diff.push(DataDiff {
data: right_data[..len.min(right_data.len())].to_vec(),
kind,
len,
..Default::default()
});
if kind == ObjDataDiffKind::Replace {
if kind == DataDiffKind::Replace {
match left_len.cmp(&right_len) {
Ordering::Less => {
let len = right_len - left_len;
left_diff.push(ObjDataDiff {
left_data_diff.push(DataDiff {
data: vec![],
kind: ObjDataDiffKind::Insert,
kind: DataDiffKind::Insert,
len,
..Default::default()
});
right_diff.push(ObjDataDiff {
right_data_diff.push(DataDiff {
data: right_data[left_len..right_len].to_vec(),
kind: ObjDataDiffKind::Insert,
kind: DataDiffKind::Insert,
len,
..Default::default()
});
}
Ordering::Greater => {
let len = left_len - right_len;
left_diff.push(ObjDataDiff {
left_data_diff.push(DataDiff {
data: left_data[right_len..left_len].to_vec(),
kind: ObjDataDiffKind::Delete,
kind: DataDiffKind::Delete,
len,
..Default::default()
});
right_diff.push(ObjDataDiff {
right_data_diff.push(DataDiff {
data: vec![],
kind: ObjDataDiffKind::Delete,
kind: DataDiffKind::Delete,
len,
..Default::default()
});
@@ -121,52 +236,168 @@ pub fn diff_data_section(
}
}
let (mut left_section_diff, mut right_section_diff) =
diff_generic_section(left, right, left_section_diff, right_section_diff)?;
left_section_diff.data_diff = left_diff;
right_section_diff.data_diff = right_diff;
// Use the highest match percent between two options:
// - Left symbols matching right symbols by name
// - Diff of the data itself
if left_section_diff.match_percent.unwrap_or(-1.0) < match_percent {
left_section_diff.match_percent = Some(match_percent);
right_section_diff.match_percent = Some(match_percent);
let mut left_reloc_diffs = Vec::new();
let mut right_reloc_diffs = Vec::new();
for (diff_kind, left_reloc, right_reloc) in diff_data_relocs_for_range(
left_obj,
right_obj,
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 {
// Use the highest match percent between two options:
// - Left symbols matching right symbols by name
// - Diff of the data itself
// We only do this when all relocations on the left side match.
if left_section_diff.match_percent.unwrap_or(-1.0) < match_percent {
left_section_diff.match_percent = Some(match_percent);
right_section_diff.match_percent = Some(match_percent);
}
}
Ok((left_section_diff, right_section_diff))
}
pub fn diff_data_symbol(
left_obj: &ObjInfo,
right_obj: &ObjInfo,
left_symbol_ref: SymbolRef,
right_symbol_ref: SymbolRef,
) -> Result<(ObjSymbolDiff, ObjSymbolDiff)> {
let (left_section, left_symbol) = left_obj.section_symbol(left_symbol_ref);
let (right_section, right_symbol) = right_obj.section_symbol(right_symbol_ref);
left_obj: &Object,
right_obj: &Object,
left_symbol_idx: usize,
right_symbol_idx: usize,
) -> Result<(SymbolDiff, SymbolDiff)> {
let left_symbol = &left_obj.symbols[left_symbol_idx];
let right_symbol = &right_obj.symbols[right_symbol_idx];
let left_section = left_section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
let right_section = right_section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
let left_section_idx =
left_symbol.section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
let right_section_idx =
right_symbol.section.ok_or_else(|| anyhow!("Data symbol section not found"))?;
let left_data = &left_section.data[left_symbol.section_address as usize
..(left_symbol.section_address + left_symbol.size) as usize];
let right_data = &right_section.data[right_symbol.section_address as usize
..(right_symbol.section_address + right_symbol.size) as usize];
let left_section = &left_obj.sections[left_section_idx];
let right_section = &right_obj.sections[right_section_idx];
let ops = capture_diff_slices_deadline(Algorithm::Patience, left_data, right_data, None);
let match_percent = get_diff_ratio(&ops, left_data.len(), right_data.len()) * 100.0;
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 right_data = &right_section.data[right_range.clone()];
let reloc_diffs = diff_data_relocs_for_range(
left_obj,
right_obj,
left_section_idx,
right_section_idx,
left_range,
right_range,
);
let ops = capture_diff_slices(Algorithm::Patience, left_data, right_data);
let bytes_match_ratio = get_diff_ratio(&ops, left_data.len(), right_data.len());
let mut match_ratio = bytes_match_ratio;
if !reloc_diffs.is_empty() {
let mut total_reloc_bytes = 0;
let mut matching_reloc_bytes = 0;
for (diff_kind, left_reloc, right_reloc) in reloc_diffs {
let reloc_diff_len = match (left_reloc, right_reloc) {
(None, None) => unreachable!(),
(None, Some(right_reloc)) => {
right_obj.arch.data_reloc_size(right_reloc.relocation.flags)
}
(Some(left_reloc), _) => left_obj.arch.data_reloc_size(left_reloc.relocation.flags),
};
total_reloc_bytes += reloc_diff_len;
if diff_kind == DataDiffKind::None {
matching_reloc_bytes += reloc_diff_len;
}
}
if total_reloc_bytes > 0 {
let relocs_match_ratio = matching_reloc_bytes as f32 / total_reloc_bytes as f32;
// Adjust the overall match ratio to include relocation differences.
// We calculate it so that bytes that contain a relocation are counted twice: once for the
// byte's raw value, and once for its relocation.
// e.g. An 8 byte symbol that has 8 matching raw bytes and a single 4 byte relocation that
// doesn't match would show as 66% (weighted average of 100% and 0%).
match_ratio = ((bytes_match_ratio * (left_data.len() as f32))
+ (relocs_match_ratio * total_reloc_bytes as f32))
/ (left_data.len() + total_reloc_bytes) as f32;
}
}
let match_percent = match_ratio * 100.0;
Ok((
ObjSymbolDiff {
symbol_ref: left_symbol_ref,
target_symbol: Some(right_symbol_ref),
instructions: vec![],
SymbolDiff {
target_symbol: Some(right_symbol_idx),
match_percent: Some(match_percent),
diff_score: None,
instruction_rows: vec![],
},
ObjSymbolDiff {
symbol_ref: right_symbol_ref,
target_symbol: Some(left_symbol_ref),
instructions: vec![],
SymbolDiff {
target_symbol: Some(left_symbol_idx),
match_percent: Some(match_percent),
diff_score: None,
instruction_rows: vec![],
},
))
}
@@ -174,49 +405,85 @@ pub fn diff_data_symbol(
/// Compares a section of two object files.
/// This essentially adds up the match percentage of each symbol in the section.
pub fn diff_generic_section(
left: &ObjSection,
_right: &ObjSection,
left_diff: &ObjSectionDiff,
_right_diff: &ObjSectionDiff,
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
let match_percent = if left_diff.symbols.iter().all(|d| d.match_percent == Some(100.0)) {
left_obj: &Object,
_right_obj: &Object,
left_diff: &ObjectDiff,
_right_diff: &ObjectDiff,
left_section_idx: usize,
_right_section_idx: usize,
) -> Result<(SectionDiff, SectionDiff)> {
let match_percent = if left_obj
.symbols
.iter()
.enumerate()
.filter(|(_, s)| s.section == Some(left_section_idx) && s.kind != SymbolKind::Section)
.map(|(i, _)| &left_diff.symbols[i])
.all(|d| d.match_percent == Some(100.0))
{
100.0 // Avoid fp precision issues
} else {
left.symbols
let (matched, total) = left_obj
.symbols
.iter()
.zip(left_diff.symbols.iter())
.map(|(s, d)| d.match_percent.unwrap_or(0.0) * s.size as f32)
.sum::<f32>()
/ left.size as f32
.enumerate()
.filter(|(_, s)| s.section == Some(left_section_idx) && s.kind != SymbolKind::Section)
.map(|(i, s)| (s, &left_diff.symbols[i]))
.fold((0.0, 0.0), |(matched, total), (s, d)| {
(matched + d.match_percent.unwrap_or(0.0) * s.size as f32, total + s.size as f32)
});
if total == 0.0 { 100.0 } else { matched / total }
};
Ok((
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
SectionDiff { match_percent: Some(match_percent), data_diff: vec![], reloc_diff: vec![] },
SectionDiff { match_percent: Some(match_percent), data_diff: vec![], reloc_diff: vec![] },
))
}
/// Compare the addresses and sizes of each symbol in the BSS sections.
pub fn diff_bss_section(
left: &ObjSection,
right: &ObjSection,
left_diff: &ObjSectionDiff,
right_diff: &ObjSectionDiff,
) -> Result<(ObjSectionDiff, ObjSectionDiff)> {
let left_sizes = left.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
let right_sizes = right.symbols.iter().map(|s| (s.section_address, s.size)).collect::<Vec<_>>();
let ops = capture_diff_slices_deadline(Algorithm::Patience, &left_sizes, &right_sizes, None);
left_obj: &Object,
right_obj: &Object,
left_diff: &ObjectDiff,
right_diff: &ObjectDiff,
left_section_idx: usize,
right_section_idx: usize,
) -> Result<(SectionDiff, SectionDiff)> {
let left_section = &left_obj.sections[left_section_idx];
let left_sizes = left_obj
.symbols
.iter()
.enumerate()
.filter(|(_, s)| s.section == Some(left_section_idx) && s.kind != SymbolKind::Section)
.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 = right_obj
.symbols
.iter()
.enumerate()
.filter(|(_, s)| s.section == Some(right_section_idx) && s.kind != SymbolKind::Section)
.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;
// Use the highest match percent between two options:
// - Left symbols matching right symbols by name
// - 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 {
match_percent = generic_diff.match_percent.unwrap();
}
Ok((
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
ObjSectionDiff { symbols: vec![], data_diff: vec![], match_percent: Some(match_percent) },
SectionDiff { match_percent: Some(match_percent), data_diff: vec![], reloc_diff: vec![] },
SectionDiff { match_percent: Some(match_percent), data_diff: vec![], reloc_diff: vec![] },
))
}

View File

@@ -1,16 +1,28 @@
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::{
diff::{ObjInsArgDiff, ObjInsDiff},
obj::{ObjInsArg, ObjInsArgValue, ObjReloc, ObjSymbol},
diff::{DiffObjConfig, InstructionDiffKind, InstructionDiffRow, ObjectDiff, SymbolDiff},
obj::{
InstructionArg, InstructionArgValue, Object, ParsedInstruction, ResolvedInstructionRef,
ResolvedRelocation, SectionFlag, SectionKind, Symbol, SymbolFlag, SymbolKind,
},
};
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Clone)]
pub enum DiffText<'a> {
/// Basic text
Basic(&'a str),
/// Colored text
BasicColor(&'a str, usize),
/// Line number
Line(u32),
/// Instruction address
@@ -18,103 +30,267 @@ pub enum DiffText<'a> {
/// Instruction mnemonic
Opcode(&'a str, u16),
/// Instruction argument
Argument(&'a ObjInsArgValue, Option<&'a ObjInsArgDiff>),
Argument(InstructionArgValue<'a>),
/// Branch destination
BranchDest(u64, Option<&'a ObjInsArgDiff>),
BranchDest(u64),
/// Symbol name
Symbol(&'a ObjSymbol, Option<&'a ObjInsArgDiff>),
Symbol(&'a Symbol),
/// Relocation addend
Addend(i64),
/// Number of spaces
Spacing(usize),
Spacing(u8),
/// End of line
Eol,
}
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Hash)]
pub enum DiffTextColor {
#[default]
Normal, // Grey
Dim, // Dark grey
Bright, // White
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, PartialEq, Eq)]
pub enum HighlightKind {
#[default]
None,
Opcode(u16),
Arg(ObjInsArgValue),
Argument(InstructionArgValue<'static>),
Symbol(String),
Address(u64),
}
pub fn display_diff<E>(
ins_diff: &ObjInsDiff,
base_addr: u64,
mut cb: impl FnMut(DiffText) -> Result<(), E>,
) -> Result<(), E> {
let Some(ins) = &ins_diff.ins else {
cb(DiffText::Eol)?;
return Ok(());
};
if let Some(line) = ins.line {
cb(DiffText::Line(line))?;
}
cb(DiffText::Address(ins.address - base_addr))?;
if let Some(branch) = &ins_diff.branch_from {
cb(DiffText::BasicColor(" ~> ", branch.branch_idx))?;
} else {
cb(DiffText::Spacing(4))?;
}
cb(DiffText::Opcode(&ins.mnemonic, ins.op))?;
let mut arg_diff_idx = 0; // non-PlainText index
for (i, arg) in ins.args.iter().enumerate() {
if i == 0 {
cb(DiffText::Spacing(1))?;
}
let diff = ins_diff.arg_diff.get(arg_diff_idx).and_then(|o| o.as_ref());
match arg {
ObjInsArg::PlainText(s) => {
cb(DiffText::Basic(s))?;
}
ObjInsArg::Arg(v) => {
cb(DiffText::Argument(v, diff))?;
arg_diff_idx += 1;
}
ObjInsArg::Reloc => {
display_reloc_name(ins.reloc.as_ref().unwrap(), &mut cb, diff)?;
arg_diff_idx += 1;
}
ObjInsArg::BranchDest(dest) => {
if let Some(dest) = dest.checked_sub(base_addr) {
cb(DiffText::BranchDest(dest, diff))?;
} else {
cb(DiffText::Basic("<unknown>"))?;
}
arg_diff_idx += 1;
}
}
}
if let Some(branch) = &ins_diff.branch_to {
cb(DiffText::BasicColor(" ~>", branch.branch_idx))?;
}
cb(DiffText::Eol)?;
Ok(())
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InstructionPart<'a> {
Basic(Cow<'a, str>),
Opcode(Cow<'a, str>, u16),
Arg(InstructionArg<'a>),
Separator,
}
fn display_reloc_name<E>(
reloc: &ObjReloc,
mut cb: impl FnMut(DiffText) -> Result<(), E>,
diff: Option<&ObjInsArgDiff>,
) -> Result<(), E> {
cb(DiffText::Symbol(&reloc.target, diff))?;
match reloc.addend.cmp(&0i64) {
Ordering::Greater => cb(DiffText::Basic(&format!("+{:#x}", reloc.addend))),
Ordering::Less => cb(DiffText::Basic(&format!("-{:#x}", -reloc.addend))),
_ => Ok(()),
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(());
};
let Some(resolved) = obj.resolve_instruction_ref(symbol_index, ins_ref) else {
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(DiffTextSegment {
text: DiffText::Address(ins_ref.address.saturating_sub(resolved.symbol.address)),
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 {
cb(DiffTextSegment::spacing(4))?;
}
let mut arg_idx = 0;
let mut displayed_relocation = false;
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))
}
}
InstructionPart::Opcode(mnemonic, opcode) => cb(DiffTextSegment {
text: DiffText::Opcode(mnemonic.as_ref(), opcode),
color: match ins_row.kind {
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;
match arg {
InstructionArg::Value(value) => cb(DiffTextSegment {
text: DiffText::Argument(value),
color: diff_index
.get()
.map_or(base_color, |i| DiffTextColor::Rotating(i as u8)),
pad_to: 0,
}),
InstructionArg::Reloc => {
displayed_relocation = true;
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 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,
})
}
}
}
}
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_row.branch_to {
cb(DiffTextSegment::basic(" ~>", DiffTextColor::Rotating(branch.branch_idx as u8)))?;
}
cb(EOL_SEGMENT)?;
Ok(())
}
impl PartialEq<DiffText<'_>> for HighlightKind {
fn eq(&self, other: &DiffText) -> bool {
match (self, other) {
(HighlightKind::Opcode(a), DiffText::Opcode(_, b)) => a == b,
(HighlightKind::Arg(a), DiffText::Argument(b, _)) => a.loose_eq(b),
(HighlightKind::Symbol(a), DiffText::Symbol(b, _)) => a == &b.name,
(HighlightKind::Address(a), DiffText::Address(b) | DiffText::BranchDest(b, _)) => {
a == b
}
(HighlightKind::Argument(a), DiffText::Argument(b)) => a.loose_eq(b),
(HighlightKind::Symbol(a), DiffText::Symbol(b)) => a == &b.name,
(HighlightKind::Address(a), DiffText::Address(b) | DiffText::BranchDest(b)) => a == b,
_ => false,
}
}
@@ -124,14 +300,460 @@ impl PartialEq<HighlightKind> for DiffText<'_> {
fn eq(&self, other: &HighlightKind) -> bool { other.eq(self) }
}
impl From<DiffText<'_>> for HighlightKind {
fn from(value: DiffText<'_>) -> Self {
impl From<&DiffText<'_>> for HighlightKind {
fn from(value: &DiffText<'_>) -> Self {
match value {
DiffText::Opcode(_, op) => HighlightKind::Opcode(op),
DiffText::Argument(arg, _) => HighlightKind::Arg(arg.clone()),
DiffText::Symbol(sym, _) => HighlightKind::Symbol(sym.name.to_string()),
DiffText::Address(addr) | DiffText::BranchDest(addr, _) => HighlightKind::Address(addr),
DiffText::Opcode(_, op) => HighlightKind::Opcode(*op),
DiffText::Argument(arg) => HighlightKind::Argument(arg.to_static()),
DiffText::Symbol(sym) => HighlightKind::Symbol(sym.name.to_string()),
DiffText::Address(addr) | DiffText::BranchDest(addr) => HighlightKind::Address(*addr),
_ => 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,
}
pub enum HoverItemColor {
Normal, // Gray
Emphasized, // White
Special, // Blue
}
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() {
if let Some(address) = symbol.virtual_address {
out.push(ContextItem::Copy {
value: format!("{:x}", address),
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) -> Vec<HoverItem> {
let symbol = &obj.symbols[symbol_index];
let addend_str = match addend.cmp(&0i64) {
Ordering::Greater => format!("+{:x}", addend),
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: HoverItemColor::Normal,
});
if let Some(demangled_name) = &symbol.demangled_name {
out.push(HoverItem::Text {
label: "Demangled".into(),
value: demangled_name.into(),
color: HoverItemColor::Normal,
});
}
if let Some(section) = symbol.section {
out.push(HoverItem::Text {
label: "Section".into(),
value: obj.sections[section].name.clone(),
color: HoverItemColor::Normal,
});
out.push(HoverItem::Text {
label: "Address".into(),
value: format!("{:x}{}", symbol.address, addend_str),
color: HoverItemColor::Normal,
});
if symbol.flags.contains(SymbolFlag::SizeInferred) {
out.push(HoverItem::Text {
label: "Size".into(),
value: format!("{:x} (inferred)", symbol.size),
color: HoverItemColor::Normal,
});
} else {
out.push(HoverItem::Text {
label: "Size".into(),
value: format!("{:x}", symbol.size),
color: HoverItemColor::Normal,
});
}
if let Some(align) = symbol.align {
out.push(HoverItem::Text {
label: "Alignment".into(),
value: align.get().to_string(),
color: HoverItemColor::Normal,
});
}
if let Some(address) = symbol.virtual_address {
out.push(HoverItem::Text {
label: "Virtual address".into(),
value: format!("{:x}", address),
color: 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 in literals {
out.push(ContextItem::Copy { value: literal, label: None });
}
}
}
out
}
pub fn relocation_hover(obj: &Object, reloc: ResolvedRelocation) -> 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: HoverItemColor::Normal,
});
} else {
out.push(HoverItem::Text {
label: "Relocation".into(),
value: format!("<{:?}>", reloc.relocation.flags),
color: HoverItemColor::Normal,
});
}
out.append(&mut symbol_hover(obj, reloc.relocation.target_symbol, reloc.relocation.addend));
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!("{:02x}", byte));
}
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));
if let Some(ty) = obj.arch.guess_data_type(resolved) {
let literals = display_ins_data_literals(obj, resolved);
if !literals.is_empty() {
out.push(HoverItem::Separator);
for literal in literals {
out.push(HoverItem::Text {
label: format!("{}", ty),
value: literal,
color: HoverItemColor::Normal,
});
}
}
}
}
out
}
#[derive(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)) {
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 || section.flags.contains(SectionFlag::Hidden) {
// Skip unknown and hidden sections
continue;
}
let section_diff = &diff.sections[section_idx];
if section.kind == SectionKind::Code && reverse_fn_order {
symbols.sort_by(|a, b| {
let a_symbol = &obj.symbols[a.symbol];
let b_symbol = &obj.symbols[b.symbol];
symbol_sort_reverse(a_symbol, b_symbol)
});
} else {
symbols.sort_by(|a, b| {
let a_symbol = &obj.symbols[a.symbol];
let b_symbol = &obj.symbols[b.symbol];
symbol_sort(a_symbol, b_symbol)
});
}
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.id.cmp(&b.id));
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
}
fn symbol_sort(a: &Symbol, b: &Symbol) -> Ordering {
section_symbol_sort(a, b).then(a.address.cmp(&b.address)).then(a.size.cmp(&b.size))
}
fn symbol_sort_reverse(a: &Symbol, b: &Symbol) -> Ordering {
section_symbol_sort(a, b).then(b.address.cmp(&a.address)).then(b.size.cmp(&a.size))
}
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)
.map(|ty| ty.display_labels(obj.endianness, bytes))
.unwrap_or_default()
}
pub fn display_ins_data_literals(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)
.map(|ty| ty.display_literals(obj.endianness, bytes))
.unwrap_or_default()
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,50 @@
use std::{sync::mpsc::Receiver, task::Waker};
use anyhow::{Context, Result};
use self_update::{
cargo_crate_version,
update::{Release, ReleaseUpdate},
};
use crate::jobs::{Job, JobContext, JobResult, JobState, start_job, update_status};
pub struct CheckUpdateConfig {
pub build_updater: fn() -> Result<Box<dyn ReleaseUpdate>>,
pub bin_names: Vec<String>,
}
pub struct CheckUpdateResult {
pub update_available: bool,
pub latest_release: Release,
pub found_binary: Option<String>,
}
fn run_check_update(
context: &JobContext,
cancel: Receiver<()>,
config: CheckUpdateConfig,
) -> Result<Box<CheckUpdateResult>> {
update_status(context, "Fetching latest release".to_string(), 0, 1, &cancel)?;
let updater = (config.build_updater)().context("Failed to create release updater")?;
let latest_release = updater.get_latest_release()?;
let update_available =
self_update::version::bump_is_greater(cargo_crate_version!(), &latest_release.version)?;
// Find the binary name in the release assets
let mut found_binary = None;
for bin_name in &config.bin_names {
if latest_release.assets.iter().any(|a| &a.name == bin_name) {
found_binary = Some(bin_name.clone());
break;
}
}
update_status(context, "Complete".to_string(), 1, 1, &cancel)?;
Ok(Box::new(CheckUpdateResult { update_available, latest_release, found_binary }))
}
pub fn start_check_update(waker: Waker, config: CheckUpdateConfig) -> JobState {
start_job(waker, "Check for updates", Job::CheckUpdate, move |context, cancel| {
run_check_update(&context, cancel, config)
.map(|result| JobResult::CheckUpdate(Some(result)))
})
}

View File

@@ -1,20 +1,17 @@
use std::{fs, path::PathBuf, sync::mpsc::Receiver};
use std::{fs, sync::mpsc::Receiver, task::Waker};
use anyhow::{anyhow, bail, Context, Result};
use const_format::formatcp;
use anyhow::{Context, Result, anyhow, bail};
use typed_path::{Utf8PlatformPathBuf, Utf8UnixPathBuf};
use crate::{
app::AppConfig,
jobs::{
objdiff::{run_make, BuildConfig, BuildStatus},
start_job, update_status, Job, JobContext, JobResult, JobState,
},
build::{BuildConfig, BuildStatus, run_make},
jobs::{Job, JobContext, JobResult, JobState, start_job, update_status},
};
#[derive(Debug, Clone)]
pub struct CreateScratchConfig {
pub build_config: BuildConfig,
pub context_path: Option<PathBuf>,
pub context_path: Option<Utf8UnixPathBuf>,
pub build_context: bool,
// Scratch fields
@@ -22,42 +19,10 @@ pub struct CreateScratchConfig {
pub platform: String,
pub compiler_flags: String,
pub function_name: String,
pub target_obj: PathBuf,
pub target_obj: Utf8PlatformPathBuf,
pub preset_id: Option<u32>,
}
impl CreateScratchConfig {
pub(crate) fn from_config(config: &AppConfig, function_name: String) -> Result<Self> {
let Some(selected_obj) = &config.selected_obj else {
bail!("No object selected");
};
let Some(target_path) = &selected_obj.target_path else {
bail!("No target path for {}", selected_obj.name);
};
let Some(scratch_config) = &selected_obj.scratch else {
bail!("No scratch configuration for {}", selected_obj.name);
};
Ok(Self {
build_config: BuildConfig::from_config(config),
context_path: scratch_config.ctx_path.clone(),
build_context: scratch_config.build_ctx.unwrap_or(false),
compiler: scratch_config.compiler.clone().unwrap_or_default(),
platform: scratch_config.platform.clone().unwrap_or_default(),
compiler_flags: scratch_config.c_flags.clone().unwrap_or_default(),
function_name,
target_obj: target_path.to_path_buf(),
preset_id: scratch_config.preset_id,
})
}
pub fn is_available(config: &AppConfig) -> bool {
let Some(selected_obj) = &config.selected_obj else {
return false;
};
selected_obj.target_path.is_some() && selected_obj.scratch.is_some()
}
}
#[derive(Default, Debug, Clone)]
pub struct CreateScratchResult {
pub scratch_url: String,
@@ -83,26 +48,25 @@ fn run_create_scratch(
if let Some(context_path) = &config.context_path {
if config.build_context {
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: false, stdout, 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(
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)?;
let diff_flags = [format!("--disassemble={}", config.function_name)];
let diff_flags = serde_json::to_string(&diff_flags).unwrap();
let obj_path = project_dir.join(&config.target_obj);
let file = reqwest::blocking::multipart::Part::file(&obj_path)
.with_context(|| format!("Failed to open {}", obj_path.display()))?;
let diff_flags = serde_json::to_string(&diff_flags)?;
let file = reqwest::blocking::multipart::Part::file(&config.target_obj)
.with_context(|| format!("Failed to open {}", config.target_obj))?;
let mut form = reqwest::blocking::multipart::Form::new()
.text("compiler", config.compiler.clone())
.text("platform", config.platform.clone())
@@ -117,7 +81,7 @@ fn run_create_scratch(
form = form.part("target_obj", file);
let client = reqwest::blocking::Client::new();
let response = client
.post(formatcp!("{API_HOST}/api/scratch"))
.post(format!("{API_HOST}/api/scratch"))
.multipart(form)
.send()
.map_err(|e| anyhow!("Failed to send request: {}", e))?;
@@ -131,8 +95,8 @@ fn run_create_scratch(
Ok(Box::from(CreateScratchResult { scratch_url }))
}
pub fn start_create_scratch(ctx: &egui::Context, config: CreateScratchConfig) -> JobState {
start_job(ctx, "Create scratch", Job::CreateScratch, move |context, cancel| {
pub fn start_create_scratch(waker: Waker, config: CreateScratchConfig) -> JobState {
start_job(waker, "Create scratch", Job::CreateScratch, move |context, cancel| {
run_create_scratch(&context, cancel, config)
.map(|result| JobResult::CreateScratch(Some(result)))
})

View File

@@ -1,9 +1,10 @@
use std::{
sync::{
Arc, RwLock,
atomic::{AtomicUsize, Ordering},
mpsc::{Receiver, Sender, TryRecvError},
Arc, RwLock,
},
task::Waker,
thread::JoinHandle,
};
@@ -53,7 +54,6 @@ impl JobQueue {
}
/// Returns whether any job is running.
#[expect(dead_code)]
pub fn any_running(&self) -> bool {
self.jobs.iter().any(|job| {
if let Some(handle) = &job.handle {
@@ -96,12 +96,53 @@ impl JobQueue {
/// Removes a job from the queue given its ID.
pub fn remove(&mut self, id: usize) { self.jobs.retain(|job| job.id != id); }
/// Collects the results of all finished jobs and handles any errors.
pub fn collect_results(&mut self) {
let mut results = vec![];
for (job, result) in self.iter_finished() {
match result {
Ok(result) => {
match result {
JobResult::None => {
// Job context contains the error
}
_ => results.push(result),
}
}
Err(err) => {
let err = if let Some(msg) = err.downcast_ref::<&'static str>() {
anyhow::Error::msg(*msg)
} else if let Some(msg) = err.downcast_ref::<String>() {
anyhow::Error::msg(msg.clone())
} else {
anyhow::Error::msg("Thread panicked")
};
let result = job.context.status.write();
if let Ok(mut guard) = result {
guard.error = Some(err);
} else {
drop(result);
job.context.status = Arc::new(RwLock::new(JobStatus {
title: "Error".to_string(),
progress_percent: 0.0,
progress_items: None,
status: String::new(),
error: Some(err),
}));
}
}
}
}
self.results.append(&mut results);
self.clear_finished();
}
}
#[derive(Clone)]
pub struct JobContext {
pub status: Arc<RwLock<JobStatus>>,
pub egui: egui::Context,
pub waker: Waker,
}
pub struct JobState {
@@ -137,7 +178,7 @@ fn should_cancel(rx: &Receiver<()>) -> bool {
}
fn start_job(
ctx: &egui::Context,
waker: Waker,
title: &str,
kind: Job,
run: impl FnOnce(JobContext, Receiver<()>) -> Result<JobResult> + Send + 'static,
@@ -149,8 +190,8 @@ fn start_job(
status: String::new(),
error: None,
}));
let context = JobContext { status: status.clone(), egui: ctx.clone() };
let context_inner = JobContext { status: status.clone(), egui: ctx.clone() };
let context = JobContext { status: status.clone(), waker: waker.clone() };
let context_inner = JobContext { status: status.clone(), waker };
let (tx, rx) = std::sync::mpsc::channel();
let handle = std::thread::spawn(move || match run(context_inner, rx) {
Ok(state) => state,
@@ -162,7 +203,7 @@ fn start_job(
}
});
let id = JOB_ID.fetch_add(1, Ordering::Relaxed);
log::info!("Started job {}", id);
// log::info!("Started job {}", id); TODO
JobState { id, kind, handle: Some(handle), context, cancel: tx }
}
@@ -184,6 +225,6 @@ fn update_status(
w.status = str;
}
drop(w);
context.egui.request_repaint();
context.waker.wake_by_ref();
Ok(())
}

View File

@@ -0,0 +1,194 @@
use std::{sync::mpsc::Receiver, task::Waker};
use anyhow::{Error, Result, bail};
use time::OffsetDateTime;
use typed_path::Utf8PlatformPathBuf;
use crate::{
build::{BuildConfig, BuildStatus, run_make},
diff::{DiffObjConfig, MappingConfig, ObjectDiff, diff_objs},
jobs::{Job, JobContext, JobResult, JobState, start_job, update_status},
obj::{Object, read},
};
pub struct ObjDiffConfig {
pub build_config: BuildConfig,
pub build_base: bool,
pub build_target: bool,
pub target_path: Option<Utf8PlatformPathBuf>,
pub base_path: Option<Utf8PlatformPathBuf>,
pub diff_obj_config: DiffObjConfig,
pub mapping_config: MappingConfig,
}
pub struct ObjDiffResult {
pub first_status: BuildStatus,
pub second_status: BuildStatus,
pub first_obj: Option<(Object, ObjectDiff)>,
pub second_obj: Option<(Object, ObjectDiff)>,
pub time: OffsetDateTime,
}
fn run_build(
context: &JobContext,
cancel: Receiver<()>,
config: ObjDiffConfig,
) -> Result<Box<ObjDiffResult>> {
let mut target_path_rel = None;
let mut base_path_rel = None;
if config.build_target || config.build_base {
let project_dir = config
.build_config
.project_dir
.as_ref()
.ok_or_else(|| Error::msg("Missing project dir"))?;
if let Some(target_path) = &config.target_path {
target_path_rel = match target_path.strip_prefix(project_dir) {
Ok(p) => Some(p.with_unix_encoding()),
Err(_) => {
bail!("Target path '{}' doesn't begin with '{}'", target_path, project_dir);
}
};
}
if let Some(base_path) = &config.base_path {
base_path_rel = match base_path.strip_prefix(project_dir) {
Ok(p) => Some(p.with_unix_encoding()),
Err(_) => {
bail!("Base path '{}' doesn't begin with '{}'", base_path, project_dir);
}
};
};
}
let mut total = 1;
if config.build_target && target_path_rel.is_some() {
total += 1;
}
if config.build_base && base_path_rel.is_some() {
total += 1;
}
if config.target_path.is_some() {
total += 1;
}
if config.base_path.is_some() {
total += 1;
}
let mut step_idx = 0;
let mut first_status = match target_path_rel {
Some(target_path_rel) if config.build_target => {
update_status(
context,
format!("Building target {}", target_path_rel),
step_idx,
total,
&cancel,
)?;
step_idx += 1;
run_make(&config.build_config, target_path_rel.as_ref())
}
_ => BuildStatus::default(),
};
let mut second_status = match base_path_rel {
Some(base_path_rel) if config.build_base => {
update_status(
context,
format!("Building base {}", base_path_rel),
step_idx,
total,
&cancel,
)?;
step_idx += 1;
run_make(&config.build_config, base_path_rel.as_ref())
}
_ => BuildStatus::default(),
};
let time = OffsetDateTime::now_utc();
let first_obj = match &config.target_path {
Some(target_path) if first_status.success => {
update_status(
context,
format!("Loading target {}", target_path),
step_idx,
total,
&cancel,
)?;
step_idx += 1;
match read::read(target_path.as_ref(), &config.diff_obj_config) {
Ok(obj) => Some(obj),
Err(e) => {
first_status = BuildStatus {
success: false,
stdout: format!("Loading object '{}'", target_path),
stderr: format!("{:#}", e),
..Default::default()
};
None
}
}
}
Some(_) => {
step_idx += 1;
None
}
_ => None,
};
let second_obj = match &config.base_path {
Some(base_path) if second_status.success => {
update_status(
context,
format!("Loading base {}", base_path),
step_idx,
total,
&cancel,
)?;
step_idx += 1;
match read::read(base_path.as_ref(), &config.diff_obj_config) {
Ok(obj) => Some(obj),
Err(e) => {
second_status = BuildStatus {
success: false,
stdout: format!("Loading object '{}'", base_path),
stderr: format!("{:#}", e),
..Default::default()
};
None
}
}
}
Some(_) => {
step_idx += 1;
None
}
_ => None,
};
update_status(context, "Performing diff".to_string(), step_idx, total, &cancel)?;
step_idx += 1;
let result = diff_objs(
first_obj.as_ref(),
second_obj.as_ref(),
None,
&config.diff_obj_config,
&config.mapping_config,
)?;
update_status(context, "Complete".to_string(), step_idx, total, &cancel)?;
Ok(Box::new(ObjDiffResult {
first_status,
second_status,
first_obj: first_obj.and_then(|o| result.left.map(|d| (o, d))),
second_obj: second_obj.and_then(|o| result.right.map(|d| (o, d))),
time,
}))
}
pub fn start_build(waker: Waker, config: ObjDiffConfig) -> JobState {
start_job(waker, "Build", Job::ObjDiff, move |context, cancel| {
run_build(&context, cancel, config).map(|result| JobResult::ObjDiff(Some(result)))
})
}

View File

@@ -3,14 +3,19 @@ use std::{
fs::File,
path::PathBuf,
sync::mpsc::Receiver,
task::Waker,
};
use anyhow::{Context, Result};
pub use self_update; // Re-export self_update crate
use self_update::update::ReleaseUpdate;
use crate::{
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState},
update::build_updater,
};
use crate::jobs::{Job, JobContext, JobResult, JobState, start_job, update_status};
pub struct UpdateConfig {
pub build_updater: fn() -> Result<Box<dyn ReleaseUpdate>>,
pub bin_name: String,
}
pub struct UpdateResult {
pub exe_path: PathBuf,
@@ -19,16 +24,15 @@ pub struct UpdateResult {
fn run_update(
status: &JobContext,
cancel: Receiver<()>,
bin_name: String,
config: UpdateConfig,
) -> Result<Box<UpdateResult>> {
update_status(status, "Fetching latest release".to_string(), 0, 3, &cancel)?;
let updater = build_updater().context("Failed to create release updater")?;
let updater = (config.build_updater)().context("Failed to create release updater")?;
let latest_release = updater.get_latest_release()?;
let asset = latest_release
.assets
.iter()
.find(|a| a.name == bin_name)
.ok_or_else(|| anyhow::Error::msg(format!("No release asset for {bin_name}")))?;
let asset =
latest_release.assets.iter().find(|a| a.name == config.bin_name).ok_or_else(|| {
anyhow::Error::msg(format!("No release asset for {}", config.bin_name))
})?;
update_status(status, "Downloading release".to_string(), 1, 3, &cancel)?;
let tmp_dir = tempfile::Builder::new().prefix("update").tempdir_in(current_dir()?)?;
@@ -47,9 +51,7 @@ fn run_update(
#[cfg(unix)]
{
use std::{fs, os::unix::fs::PermissionsExt};
let mut perms = fs::metadata(&target_file)?.permissions();
perms.set_mode(0o755);
fs::set_permissions(&target_file, perms)?;
fs::set_permissions(&target_file, fs::Permissions::from_mode(0o755))?;
}
tmp_dir.close()?;
@@ -57,8 +59,8 @@ fn run_update(
Ok(Box::from(UpdateResult { exe_path: target_file }))
}
pub fn start_update(ctx: &egui::Context, bin_name: String) -> JobState {
start_job(ctx, "Update app", Job::Update, move |context, cancel| {
run_update(&context, cancel, bin_name).map(JobResult::Update)
pub fn start_update(waker: Waker, config: UpdateConfig) -> JobState {
start_job(waker, "Update app", Job::Update, move |context, cancel| {
run_update(&context, cancel, config).map(JobResult::Update)
})
}

View File

@@ -1,11 +1,19 @@
#![allow(clippy::too_many_arguments)]
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
#[cfg(feature = "any-arch")]
pub mod arch;
#[cfg(feature = "bindings")]
pub mod bindings;
#[cfg(feature = "build")]
pub mod build;
#[cfg(feature = "config")]
pub mod config;
#[cfg(feature = "any-arch")]
pub mod diff;
#[cfg(feature = "build")]
pub mod jobs;
#[cfg(feature = "any-arch")]
pub mod obj;
#[cfg(feature = "any-arch")]

View File

@@ -1,23 +1,37 @@
pub mod read;
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};
use filetime::FileTime;
use flagset::{flags, FlagSet};
use object::RelocationFlags;
use split_meta::SplitMeta;
use flagset::{FlagSet, flags};
use crate::{arch::ObjArch, util::ReallySigned};
use crate::{
arch::{Arch, ArchDummy},
obj::split_meta::SplitMeta,
util::ReallySigned,
};
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum ObjSectionKind {
#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
pub enum SectionKind {
#[default]
Unknown = -1,
Code,
Data,
Bss,
Common,
}
flags! {
pub enum ObjSymbolFlags: u8 {
#[derive(Hash)]
pub enum SymbolFlag: u8 {
Global,
Local,
Weak,
@@ -26,105 +40,192 @@ flags! {
/// Has extra data associated with the symbol
/// (e.g. exception table entry)
HasExtra,
/// Symbol size was missing and was inferred
SizeInferred,
}
}
#[derive(Debug, Copy, Clone, Default)]
pub struct ObjSymbolFlagSet(pub FlagSet<ObjSymbolFlags>);
#[derive(Debug, Clone)]
pub struct ObjSection {
pub type SymbolFlagSet = FlagSet<SymbolFlag>;
flags! {
#[derive(Hash)]
pub enum SectionFlag: u8 {
/// Section combined from multiple input sections
Combined,
Hidden,
}
}
pub type SectionFlagSet = FlagSet<SectionFlag>;
#[derive(Debug, Clone, Default)]
pub struct Section {
/// Unique section ID
pub id: String,
pub name: String,
pub kind: ObjSectionKind,
pub address: u64,
pub size: u64,
pub data: Vec<u8>,
pub orig_index: usize,
pub symbols: Vec<ObjSymbol>,
pub relocations: Vec<ObjReloc>,
pub virtual_address: Option<u64>,
pub kind: SectionKind,
pub data: SectionData,
pub flags: SectionFlagSet,
pub relocations: Vec<Relocation>,
/// Line number info (.line or .debug_line section)
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 start = self.address;
let end = start + self.size;
if address >= start && address + size as u64 <= end {
let offset = (address - start) as usize;
Some(&self.data[offset..offset + size])
} else {
None
}
}
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(i) => self.relocations.get(i),
Err(i) => self
.relocations
.get(i)
.take_if(|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)]
pub enum ObjInsArgValue {
pub enum InstructionArgValue<'a> {
Signed(i64),
Unsigned(u64),
Opaque(Cow<'static, str>),
Opaque(Cow<'a, str>),
}
impl ObjInsArgValue {
pub fn loose_eq(&self, other: &ObjInsArgValue) -> bool {
impl InstructionArgValue<'_> {
pub fn loose_eq(&self, other: &InstructionArgValue) -> bool {
match (self, other) {
(ObjInsArgValue::Signed(a), ObjInsArgValue::Signed(b)) => a == b,
(ObjInsArgValue::Unsigned(a), ObjInsArgValue::Unsigned(b)) => a == b,
(ObjInsArgValue::Signed(a), ObjInsArgValue::Unsigned(b))
| (ObjInsArgValue::Unsigned(b), ObjInsArgValue::Signed(a)) => *a as u64 == *b,
(ObjInsArgValue::Opaque(a), ObjInsArgValue::Opaque(b)) => a == b,
(InstructionArgValue::Signed(a), InstructionArgValue::Signed(b)) => a == b,
(InstructionArgValue::Unsigned(a), InstructionArgValue::Unsigned(b)) => a == b,
(InstructionArgValue::Signed(a), InstructionArgValue::Unsigned(b))
| (InstructionArgValue::Unsigned(b), InstructionArgValue::Signed(a)) => *a as u64 == *b,
(InstructionArgValue::Opaque(a), InstructionArgValue::Opaque(b)) => a == b,
_ => 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 {
match self {
ObjInsArgValue::Signed(v) => write!(f, "{:#x}", ReallySigned(*v)),
ObjInsArgValue::Unsigned(v) => write!(f, "{:#x}", v),
ObjInsArgValue::Opaque(v) => write!(f, "{}", v),
InstructionArgValue::Signed(v) => write!(f, "{:#x}", ReallySigned(*v)),
InstructionArgValue::Unsigned(v) => write!(f, "{:#x}", v),
InstructionArgValue::Opaque(v) => write!(f, "{}", v),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum ObjInsArg {
PlainText(Cow<'static, str>),
Arg(ObjInsArgValue),
pub enum InstructionArg<'a> {
Value(InstructionArgValue<'a>),
Reloc,
BranchDest(u64),
}
impl ObjInsArg {
#[inline]
pub fn is_plain_text(&self) -> bool { matches!(self, ObjInsArg::PlainText(_)) }
pub fn loose_eq(&self, other: &ObjInsArg) -> bool {
impl InstructionArg<'_> {
pub fn loose_eq(&self, other: &InstructionArg) -> bool {
match (self, other) {
(ObjInsArg::Arg(a), ObjInsArg::Arg(b)) => a.loose_eq(b),
(ObjInsArg::Reloc, ObjInsArg::Reloc) => true,
(ObjInsArg::BranchDest(a), ObjInsArg::BranchDest(b)) => a == b,
(InstructionArg::Value(a), InstructionArg::Value(b)) => a.loose_eq(b),
(InstructionArg::Reloc, InstructionArg::Reloc) => true,
(InstructionArg::BranchDest(a), InstructionArg::BranchDest(b)) => a == b,
_ => 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)]
pub struct ObjIns {
#[derive(Copy, Clone, Debug, Default)]
pub struct InstructionRef {
pub address: u64,
pub size: u8,
pub op: u16,
pub mnemonic: Cow<'static, str>,
pub args: Vec<ObjInsArg>,
pub reloc: Option<ObjReloc>,
pub branch_dest: Option<u64>,
/// Line number
pub line: Option<u32>,
/// Formatted instruction
pub formatted: String,
/// Original (unsimplified) instruction
pub orig: Option<String>,
pub opcode: u16,
}
impl ObjIns {
/// Iterate over non-PlainText arguments.
#[inline]
pub fn iter_args(&self) -> impl DoubleEndedIterator<Item = &ObjInsArg> {
self.args.iter().filter(|a| !a.is_plain_text())
}
#[derive(Copy, Clone, Debug)]
pub struct ScannedInstruction {
pub ins_ref: InstructionRef,
pub branch_dest: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct ParsedInstruction {
pub ins_ref: InstructionRef,
pub mnemonic: Cow<'static, str>,
pub args: Vec<InstructionArg<'static>>,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
pub enum ObjSymbolKind {
pub enum SymbolKind {
#[default]
Unknown,
Function,
@@ -132,59 +233,149 @@ pub enum ObjSymbolKind {
Section,
}
#[derive(Debug, Clone)]
pub struct ObjSymbol {
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
pub struct Symbol {
pub name: String,
pub demangled_name: Option<String>,
pub address: u64,
pub section_address: u64,
pub size: u64,
pub size_known: bool,
pub kind: ObjSymbolKind,
pub flags: ObjSymbolFlagSet,
pub orig_section_index: Option<usize>,
pub kind: SymbolKind,
pub section: Option<usize>,
pub flags: SymbolFlagSet,
/// Alignment (from Metrowerks .comment section)
pub align: Option<NonZeroU32>,
/// Original virtual address (from .note.split section)
pub virtual_address: Option<u64>,
/// Original index in object symbol table
pub original_index: Option<usize>,
pub bytes: Vec<u8>,
}
pub struct ObjInfo {
pub arch: Box<dyn ObjArch>,
pub path: Option<PathBuf>,
pub timestamp: Option<FileTime>,
pub sections: Vec<ObjSection>,
/// Common BSS symbols
pub common: Vec<ObjSymbol>,
#[derive(Debug)]
pub struct Object {
pub arch: Box<dyn Arch>,
pub endianness: object::Endianness,
pub symbols: Vec<Symbol>,
pub sections: Vec<Section>,
/// Split object metadata (.note.split section)
pub split_meta: Option<SplitMeta>,
#[cfg(feature = "std")]
pub path: Option<std::path::PathBuf>,
#[cfg(feature = "std")]
pub timestamp: Option<filetime::FileTime>,
}
#[derive(Debug, Clone)]
pub struct ObjReloc {
impl Default for Object {
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,
}
}
}
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)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Relocation {
pub flags: RelocationFlags,
pub address: u64,
pub target: ObjSymbol,
pub target_symbol: usize,
pub addend: i64,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SymbolRef {
pub section_idx: usize,
pub symbol_idx: usize,
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum RelocationFlags {
Elf(u32),
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 {
pub fn section_symbol(&self, symbol_ref: SymbolRef) -> (Option<&ObjSection>, &ObjSymbol) {
if symbol_ref.section_idx == SECTION_COMMON {
let symbol = &self.common[symbol_ref.symbol_idx];
return (None, symbol);
#[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(),
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,161 @@
---
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(),
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),
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: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".data-2",
name: ".data",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
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,7 @@
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};
pub const SPLITMETA_SECTION: &str = ".note.split";
pub const SHT_SPLITMETA: u32 = SHT_NOTE;
@@ -27,10 +28,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");
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 {
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)?;
while let Some(note) = iter.next(e)? {
if note.name != ELF_NOTE_SPLIT {
@@ -38,20 +39,19 @@ impl SplitMeta {
}
match note.n_type {
NT_SPLIT_GENERATOR => {
let string = String::from_utf8(note.desc.to_vec())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
let string =
String::from_utf8(note.desc.to_vec()).map_err(anyhow::Error::new)?;
result.generator = Some(string);
}
NT_SPLIT_MODULE_NAME => {
let string = String::from_utf8(note.desc.to_vec())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
let string =
String::from_utf8(note.desc.to_vec()).map_err(anyhow::Error::new)?;
result.module_name = Some(string);
}
NT_SPLIT_MODULE_ID => {
result.module_id =
Some(e.read_u32_bytes(note.desc.try_into().map_err(|_| {
io::Error::new(io::ErrorKind::InvalidData, "Invalid module ID size")
})?));
result.module_id = Some(e.read_u32_bytes(
note.desc.try_into().map_err(|_| anyhow!("Invalid module ID size"))?,
));
}
NT_SPLIT_VIRTUAL_ADDRESSES => {
let vec = if is_64 {
@@ -79,10 +79,11 @@ impl SplitMeta {
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
E: Endian,
W: Write + ?Sized,
W: std::io::Write + ?Sized,
{
if let Some(generator) = &self.generator {
write_note_header(writer, e, NT_SPLIT_GENERATOR, generator.len())?;
@@ -137,10 +138,9 @@ impl SplitMeta {
}
}
/// Convert an object::read::Error to an io::Error.
fn object_io_error(err: object::read::Error) -> io::Error {
io::Error::new(io::ErrorKind::InvalidData, err)
}
/// Convert an object::read::Error to a String.
#[inline]
fn object_error(err: object::read::Error) -> anyhow::Error { anyhow::Error::new(err) }
/// An ELF note entry.
struct Note<'data> {
@@ -161,27 +161,27 @@ where E: Endian
impl<'data, E> NoteIterator<'data, E>
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 {
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 {
NoteIterator::B32(
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 {
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),
name: note.name(),
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),
name: note.name(),
desc: note.desc(),
@@ -192,7 +192,8 @@ 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<()> {
#[cfg(feature = "std")]
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 % 4 != 0 {
writer.write_all(&ALIGN_BYTES[..4 - len % 4])?;
@@ -208,10 +209,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
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
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(desc_len as u32))?; // Desc Size

View File

@@ -1,18 +1,15 @@
use std::{
fmt::{LowerHex, UpperHex},
io::Read,
};
use alloc::format;
use core::fmt;
use anyhow::Result;
use byteorder::{NativeEndian, ReadBytesExt};
use anyhow::{Result, ensure};
use num_traits::PrimInt;
use object::{Endian, Object};
// 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> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
impl<N: PrimInt> fmt::LowerHex for ReallySigned<N> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let num = self.0.to_i64().unwrap();
let prefix = if f.alternate() { "0x" } else { "" };
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> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
impl<N: PrimInt> fmt::UpperHex for ReallySigned<N> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let num = self.0.to_i64().unwrap();
let prefix = if f.alternate() { "0x" } else { "" };
let bare_hex = format!("{:X}", num.abs());
@@ -29,10 +26,16 @@ impl<N: PrimInt> UpperHex for ReallySigned<N> {
}
}
pub fn read_u32<R: Read>(obj_file: &object::File, reader: &mut R) -> Result<u32> {
Ok(obj_file.endianness().read_u32(reader.read_u32::<NativeEndian>()?))
pub fn read_u32(obj_file: &object::File, reader: &mut &[u8]) -> Result<u32> {
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> {
Ok(obj_file.endianness().read_u16(reader.read_u16::<NativeEndian>()?))
pub fn read_u16(obj_file: &object::File, reader: &mut &[u8]) -> Result<u16> {
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))
}

View File

@@ -0,0 +1,17 @@
use objdiff_core::{diff, obj};
mod common;
#[test]
#[cfg(feature = "arm")]
fn read_arm() {
let diff_config = diff::DiffObjConfig { mips_register_prefix: true, ..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);
}

View File

@@ -0,0 +1,39 @@
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);
}

View File

@@ -0,0 +1,87 @@
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));
}

View File

@@ -0,0 +1,42 @@
use objdiff_core::{diff, 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);
}

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!(u32, $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.

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), (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), (Symbol(Symbol { name: "_ZN13LinkStateItem15GetEquipBombchuEv", demangled_name: Some("LinkStateItem::GetEquipBombchu()"), address: 472, size: 16, kind: Function, section: Some(0), flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Addend(-8), Bright, 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), (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), (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), (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("andeq", 32771), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(" <"), Normal, 0), (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), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
[(Address(424), Normal, 5), (Basic(" ~> "), Rotating(8), 0), (Opcode("andeq", 32771), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(" <"), Normal, 0), (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), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
[(Address(428), Normal, 5), (Spacing(4), Normal, 0), (Opcode("andeq", 32771), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(" <"), Normal, 0), (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), (Basic(">"), Normal, 0), (Eol, Normal, 0)]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,640 @@
---
source: objdiff-core/tests/arch_mips.rs
expression: diff.instruction_rows
---
[
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 0,
size: 4,
opcode: 12,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 4,
size: 4,
opcode: 44,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 8,
size: 4,
opcode: 44,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 12,
size: 4,
opcode: 44,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 16,
size: 4,
opcode: 44,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 20,
size: 4,
opcode: 2,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 24,
size: 4,
opcode: 113,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 28,
size: 4,
opcode: 26,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 32,
size: 4,
opcode: 20,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 36,
size: 4,
opcode: 97,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 40,
size: 4,
opcode: 2,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 44,
size: 4,
opcode: 12,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 48,
size: 4,
opcode: 20,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 52,
size: 4,
opcode: 26,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 56,
size: 4,
opcode: 2,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 60,
size: 4,
opcode: 12,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 64,
size: 4,
opcode: 26,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 68,
size: 4,
opcode: 97,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 72,
size: 4,
opcode: 2,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 76,
size: 4,
opcode: 97,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 80,
size: 4,
opcode: 2,
},
),
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,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 88,
size: 4,
opcode: 56,
},
),
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,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 96,
size: 4,
opcode: 2,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 100,
size: 4,
opcode: 20,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 104,
size: 4,
opcode: 2,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 108,
size: 4,
opcode: 12,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 112,
size: 4,
opcode: 2,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 116,
size: 4,
opcode: 16,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 120,
size: 4,
opcode: 20,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 124,
size: 4,
opcode: 12,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 128,
size: 4,
opcode: 2,
},
),
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,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 136,
size: 4,
opcode: 2,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 140,
size: 4,
opcode: 113,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 144,
size: 4,
opcode: 55,
},
),
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,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 152,
size: 4,
opcode: 3,
},
),
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,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 160,
size: 4,
opcode: 2,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 164,
size: 4,
opcode: 60,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 168,
size: 4,
opcode: 77,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 172,
size: 4,
opcode: 113,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 176,
size: 4,
opcode: 54,
},
),
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,
},
),
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,
},
]

View File

@@ -0,0 +1,521 @@
---
source: objdiff-core/tests/arch_ppc.rs
expression: obj
---
Object {
arch: ArchPpc {
extab: Some(
{
10: ExceptionInfo {
eti_symbol: ExtabSymbolRef {
original_index: 5,
name: "@31",
demangled_name: None,
},
etb_symbol: ExtabSymbolRef {
original_index: 4,
name: "@30",
demangled_name: None,
},
data: ExceptionTableData {
flag_val: 8200,
has_elf_vector: false,
large_frame: true,
has_frame_pointer: false,
saved_cr: false,
fpr_save_range: 0,
gpr_save_range: 4,
et_field: 0,
pc_actions: [],
exception_actions: [],
relocations: [],
},
dtors: [],
},
11: ExceptionInfo {
eti_symbol: ExtabSymbolRef {
original_index: 7,
name: "@52",
demangled_name: None,
},
etb_symbol: ExtabSymbolRef {
original_index: 6,
name: "@51",
demangled_name: None,
},
data: ExceptionTableData {
flag_val: 8200,
has_elf_vector: false,
large_frame: true,
has_frame_pointer: false,
saved_cr: false,
fpr_save_range: 0,
gpr_save_range: 4,
et_field: 0,
pc_actions: [
PCAction {
start_pc: 96,
end_pc: 96,
action_offset: 16,
},
],
exception_actions: [
ExceptionAction {
action_offset: 16,
action_type: DestroyLocal,
action_param: 0,
has_end_bit: true,
bytes: [
0,
8,
0,
0,
0,
0,
],
},
],
relocations: [
Relocation {
offset: 20,
address: 0,
},
],
},
dtors: [
ExtabSymbolRef {
original_index: 12,
name: "__dt__26__partial_array_destructorFv",
demangled_name: Some(
"__partial_array_destructor::~__partial_array_destructor()",
),
},
],
},
12: ExceptionInfo {
eti_symbol: ExtabSymbolRef {
original_index: 9,
name: "@60",
demangled_name: None,
},
etb_symbol: ExtabSymbolRef {
original_index: 8,
name: "@59",
demangled_name: None,
},
data: ExceptionTableData {
flag_val: 6152,
has_elf_vector: false,
large_frame: true,
has_frame_pointer: false,
saved_cr: false,
fpr_save_range: 0,
gpr_save_range: 3,
et_field: 0,
pc_actions: [],
exception_actions: [],
relocations: [],
},
dtors: [],
},
},
),
},
endianness: Big,
symbols: [
Symbol {
name: "NMWException.cpp",
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: "[extab]",
demangled_name: None,
address: 0,
size: 0,
kind: Section,
section: Some(
1,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[extabindex]",
demangled_name: None,
address: 0,
size: 0,
kind: Section,
section: Some(
2,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "@30",
demangled_name: None,
address: 0,
size: 8,
kind: Object,
section: Some(
1,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "@31",
demangled_name: None,
address: 0,
size: 12,
kind: Object,
section: Some(
2,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "@51",
demangled_name: None,
address: 8,
size: 24,
kind: Object,
section: Some(
1,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "@52",
demangled_name: None,
address: 12,
size: 12,
kind: Object,
section: Some(
2,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "@59",
demangled_name: None,
address: 32,
size: 8,
kind: Object,
section: Some(
1,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "@60",
demangled_name: None,
address: 24,
size: 12,
kind: Object,
section: Some(
2,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "__destroy_arr",
demangled_name: None,
address: 0,
size: 120,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Global | HasExtra),
align: None,
virtual_address: None,
},
Symbol {
name: "__construct_array",
demangled_name: None,
address: 120,
size: 248,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Global | HasExtra),
align: None,
virtual_address: None,
},
Symbol {
name: "__dt__26__partial_array_destructorFv",
demangled_name: Some(
"__partial_array_destructor::~__partial_array_destructor()",
),
address: 368,
size: 184,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Global | Weak | HasExtra),
align: None,
virtual_address: None,
},
Symbol {
name: "__dl__FPv",
demangled_name: Some(
"operator delete(void*)",
),
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Global),
align: None,
virtual_address: None,
},
],
sections: [
Section {
id: ".text-0",
name: ".text",
address: 0,
size: 552,
kind: Code,
data: SectionData(
552,
),
flags: FlagSet(),
relocations: [
Relocation {
flags: Elf(
10,
),
address: 516,
target_symbol: 13,
addend: 0,
},
],
line_info: {},
virtual_address: None,
},
Section {
id: "extab-0",
name: "extab",
address: 0,
size: 40,
kind: Data,
data: SectionData(
40,
),
flags: FlagSet(),
relocations: [
Relocation {
flags: Elf(
1,
),
address: 28,
target_symbol: 12,
addend: 0,
},
],
line_info: {},
virtual_address: None,
},
Section {
id: "extabindex-0",
name: "extabindex",
address: 0,
size: 36,
kind: Data,
data: SectionData(
36,
),
flags: FlagSet(),
relocations: [
Relocation {
flags: Elf(
1,
),
address: 0,
target_symbol: 10,
addend: 0,
},
Relocation {
flags: Elf(
1,
),
address: 8,
target_symbol: 4,
addend: 0,
},
Relocation {
flags: Elf(
1,
),
address: 12,
target_symbol: 11,
addend: 0,
},
Relocation {
flags: Elf(
1,
),
address: 20,
target_symbol: 6,
addend: 0,
},
Relocation {
flags: Elf(
1,
),
address: 24,
target_symbol: 12,
addend: 0,
},
Relocation {
flags: Elf(
1,
),
address: 32,
target_symbol: 8,
addend: 0,
},
],
line_info: {},
virtual_address: None,
},
Section {
id: ".rela.text-0",
name: ".rela.text",
address: 0,
size: 12,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".relaextab-0",
name: ".relaextab",
address: 0,
size: 12,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".relaextabindex-0",
name: ".relaextabindex",
address: 0,
size: 72,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".symtab-0",
name: ".symtab",
address: 0,
size: 240,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".strtab-0",
name: ".strtab",
address: 0,
size: 121,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".shstrtab-0",
name: ".shstrtab",
address: 0,
size: 97,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".comment-0",
name: ".comment",
address: 0,
size: 164,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
],
split_meta: None,
path: None,
timestamp: None,
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,71 @@
---
source: objdiff-core/tests/arch_ppc.rs
assertion_line: 20
expression: output
---
[(Address(0), Normal, 5), (Spacing(4), Normal, 0), (Opcode("srwi", 60), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("24")), Normal, 0), (Eol, Normal, 0)]
[(Address(4), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmpwi", 38), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(-1)), Normal, 0), (Eol, Normal, 0)]
[(Address(8), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 43), Normal, 10), (BranchDest(20), Normal, 0), (Basic(" ~>"), Rotating(0), 0), (Eol, Normal, 0)]
[(Address(12), Normal, 5), (Spacing(4), Normal, 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(-1)), Normal, 0), (Eol, Normal, 0)]
[(Address(16), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 45), Normal, 10), (BranchDest(32), Normal, 0), (Basic(" ~>"), Rotating(1), 0), (Eol, Normal, 0)]
[(Address(20), Normal, 5), (Basic(" ~> "), Rotating(0), 0), (Opcode("lis", 42), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@ha"), Normal, 0), (Eol, Normal, 0)]
[(Address(24), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addi", 41), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@l"), Normal, 0), (Eol, Normal, 0)]
[(Address(28), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lbzx", 94), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r0")), Normal, 0), (Basic(" <"), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
[(Address(32), Normal, 5), (Basic(" ~> "), Rotating(1), 0), (Opcode("extrwi", 60), Normal, 10), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("8")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("8")), Normal, 0), (Eol, Normal, 0)]
[(Address(36), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stb", 166), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "text$52", demangled_name: None, address: 8, size: 5, kind: Object, section: Some(2), flags: FlagSet(Local), align: None, virtual_address: Some(2153420056) }), Bright, 0), (Basic("@sda21"), Normal, 0), (Eol, Normal, 0)]
[(Address(40), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmpwi", 38), Normal, 10), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(-1)), Normal, 0), (Eol, Normal, 0)]
[(Address(44), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 43), Normal, 10), (BranchDest(56), Normal, 0), (Basic(" ~>"), Rotating(2), 0), (Eol, Normal, 0)]
[(Address(48), Normal, 5), (Spacing(4), Normal, 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(-1)), Normal, 0), (Eol, Normal, 0)]
[(Address(52), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 45), Normal, 10), (BranchDest(68), Normal, 0), (Basic(" ~>"), Rotating(3), 0), (Eol, Normal, 0)]
[(Address(56), Normal, 5), (Basic(" ~> "), Rotating(2), 0), (Opcode("lis", 42), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@ha"), Normal, 0), (Eol, Normal, 0)]
[(Address(60), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addi", 41), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@l"), Normal, 0), (Eol, Normal, 0)]
[(Address(64), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lbzx", 94), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(" <"), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
[(Address(68), Normal, 5), (Basic(" ~> "), Rotating(3), 0), (Opcode("extrwi", 60), Normal, 10), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("8")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("16")), Normal, 0), (Eol, Normal, 0)]
[(Address(72), Normal, 5), (Spacing(4), Normal, 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "text$52", demangled_name: None, address: 8, size: 5, kind: Object, section: Some(2), flags: FlagSet(Local), align: None, virtual_address: Some(2153420056) }), Bright, 0), (Basic("@sda21"), Normal, 0), (Eol, Normal, 0)]
[(Address(76), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmpwi", 38), Normal, 10), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(-1)), Normal, 0), (Eol, Normal, 0)]
[(Address(80), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stb", 166), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(1)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(84), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 43), Normal, 10), (BranchDest(96), Normal, 0), (Basic(" ~>"), Rotating(4), 0), (Eol, Normal, 0)]
[(Address(88), Normal, 5), (Spacing(4), Normal, 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(-1)), Normal, 0), (Eol, Normal, 0)]
[(Address(92), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 45), Normal, 10), (BranchDest(108), Normal, 0), (Basic(" ~>"), Rotating(5), 0), (Eol, Normal, 0)]
[(Address(96), Normal, 5), (Basic(" ~> "), Rotating(4), 0), (Opcode("lis", 42), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@ha"), Normal, 0), (Eol, Normal, 0)]
[(Address(100), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addi", 41), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@l"), Normal, 0), (Eol, Normal, 0)]
[(Address(104), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lbzx", 94), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(" <"), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
[(Address(108), Normal, 5), (Basic(" ~> "), Rotating(5), 0), (Opcode("clrlwi", 60), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("24")), Normal, 0), (Eol, Normal, 0)]
[(Address(112), Normal, 5), (Spacing(4), Normal, 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "text$52", demangled_name: None, address: 8, size: 5, kind: Object, section: Some(2), flags: FlagSet(Local), align: None, virtual_address: Some(2153420056) }), Bright, 0), (Basic("@sda21"), Normal, 0), (Eol, Normal, 0)]
[(Address(116), Normal, 5), (Spacing(4), Normal, 0), (Opcode("cmpwi", 38), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(-1)), Normal, 0), (Eol, Normal, 0)]
[(Address(120), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stb", 166), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(2)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(124), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 43), Normal, 10), (BranchDest(136), Normal, 0), (Basic(" ~>"), Rotating(6), 0), (Eol, Normal, 0)]
[(Address(128), Normal, 5), (Spacing(4), Normal, 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(-1)), Normal, 0), (Eol, Normal, 0)]
[(Address(132), Normal, 5), (Spacing(4), Normal, 0), (Opcode("b", 45), Normal, 10), (BranchDest(148), Normal, 0), (Basic(" ~>"), Rotating(7), 0), (Eol, Normal, 0)]
[(Address(136), Normal, 5), (Basic(" ~> "), Rotating(6), 0), (Opcode("lis", 42), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@ha"), Normal, 0), (Eol, Normal, 0)]
[(Address(140), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addi", 41), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@l"), Normal, 0), (Eol, Normal, 0)]
[(Address(144), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lbzx", 94), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(" <"), Normal, 0), (Symbol(Symbol { name: "__upper_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
[(Address(148), Normal, 5), (Basic(" ~> "), Rotating(7), 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r5")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "text$52", demangled_name: None, address: 8, size: 5, kind: Object, section: Some(2), flags: FlagSet(Local), align: None, virtual_address: Some(2153420056) }), Bright, 0), (Basic("@sda21"), Normal, 0), (Eol, Normal, 0)]
[(Address(152), Normal, 5), (Spacing(4), Normal, 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(0)), Normal, 0), (Eol, Normal, 0)]
[(Address(156), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stb", 166), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(3)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(160), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lis", 42), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__ctype_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@ha"), Normal, 0), (Eol, Normal, 0)]
[(Address(164), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addi", 41), Normal, 10), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "__ctype_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic("@l"), Normal, 0), (Eol, Normal, 0)]
[(Address(168), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stb", 166), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(4)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(172), Normal, 5), (Spacing(4), Normal, 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(45)), Normal, 0), (Eol, Normal, 0)]
[(Address(176), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lbz", 162), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "text$52", demangled_name: None, address: 8, size: 5, kind: Object, section: Some(2), flags: FlagSet(Local), align: None, virtual_address: Some(2153420056) }), Bright, 0), (Basic("@sda21"), Normal, 0), (Eol, Normal, 0)]
[(Address(180), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lbzx", 94), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(" <"), Normal, 0), (Symbol(Symbol { name: "__ctype_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
[(Address(184), Normal, 5), (Spacing(4), Normal, 0), (Opcode("andi.", 66), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Unsigned(220)), Normal, 0), (Eol, Normal, 0)]
[(Address(188), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 43), Normal, 10), (BranchDest(196), Normal, 0), (Basic(" ~>"), Rotating(8), 0), (Eol, Normal, 0)]
[(Address(192), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stb", 166), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(196), Normal, 5), (Basic(" ~> "), Rotating(8), 0), (Opcode("lbzu", 163), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(1)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(200), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lbzx", 94), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(" <"), Normal, 0), (Symbol(Symbol { name: "__ctype_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
[(Address(204), Normal, 5), (Spacing(4), Normal, 0), (Opcode("andi.", 66), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Unsigned(220)), Normal, 0), (Eol, Normal, 0)]
[(Address(208), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 43), Normal, 10), (BranchDest(216), Normal, 0), (Basic(" ~>"), Rotating(9), 0), (Eol, Normal, 0)]
[(Address(212), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stb", 166), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(216), Normal, 5), (Basic(" ~> "), Rotating(9), 0), (Opcode("lbzu", 163), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(1)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(220), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lbzx", 94), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(" <"), Normal, 0), (Symbol(Symbol { name: "__ctype_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
[(Address(224), Normal, 5), (Spacing(4), Normal, 0), (Opcode("andi.", 66), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Unsigned(220)), Normal, 0), (Eol, Normal, 0)]
[(Address(228), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 43), Normal, 10), (BranchDest(236), Normal, 0), (Basic(" ~>"), Rotating(10), 0), (Eol, Normal, 0)]
[(Address(232), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stb", 166), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(236), Normal, 5), (Basic(" ~> "), Rotating(10), 0), (Opcode("lbzu", 163), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(1)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(240), Normal, 5), (Spacing(4), Normal, 0), (Opcode("lbzx", 94), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r4")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(" <"), Normal, 0), (Symbol(Symbol { name: "__ctype_map", demangled_name: None, address: 0, size: 0, kind: Unknown, section: None, flags: FlagSet(Global), align: None, virtual_address: Some(0) }), Bright, 0), (Basic(">"), Normal, 0), (Eol, Normal, 0)]
[(Address(244), Normal, 5), (Spacing(4), Normal, 0), (Opcode("andi.", 66), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Unsigned(220)), Normal, 0), (Eol, Normal, 0)]
[(Address(248), Normal, 5), (Spacing(4), Normal, 0), (Opcode("bne", 43), Normal, 10), (BranchDest(256), Normal, 0), (Basic(" ~>"), Rotating(11), 0), (Eol, Normal, 0)]
[(Address(252), Normal, 5), (Spacing(4), Normal, 0), (Opcode("stb", 166), Normal, 10), (Argument(Opaque("r0")), Normal, 0), (Basic(", "), Normal, 0), (Argument(Signed(0)), Normal, 0), (Basic("("), Normal, 0), (Argument(Opaque("r5")), Normal, 0), (Basic(")"), Normal, 0), (Eol, Normal, 0)]
[(Address(256), Normal, 5), (Basic(" ~> "), Rotating(11), 0), (Opcode("li", 41), Normal, 10), (Argument(Opaque("r3")), Normal, 0), (Basic(", "), Normal, 0), (Symbol(Symbol { name: "text$52", demangled_name: None, address: 8, size: 5, kind: Object, section: Some(2), flags: FlagSet(Local), align: None, virtual_address: Some(2153420056) }), Bright, 0), (Basic("@sda21"), Normal, 0), (Eol, Normal, 0)]
[(Address(260), Normal, 5), (Spacing(4), Normal, 0), (Opcode("blr", 47), Normal, 10), (Eol, Normal, 0)]

View File

@@ -0,0 +1,555 @@
---
source: objdiff-core/tests/arch_ppc.rs
assertion_line: 14
expression: obj
---
Object {
arch: ArchPpc {
extab: None,
},
endianness: Big,
symbols: [
Symbol {
name: "IObj.cpp",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Local),
align: None,
virtual_address: Some(
0,
),
},
Symbol {
name: "[.text]",
demangled_name: None,
address: 0,
size: 0,
kind: Section,
section: Some(
0,
),
flags: FlagSet(Local),
align: None,
virtual_address: Some(
2150895620,
),
},
Symbol {
name: "[.ctors]",
demangled_name: None,
address: 0,
size: 4,
kind: Section,
section: Some(
1,
),
flags: FlagSet(Local),
align: None,
virtual_address: Some(
2151461704,
),
},
Symbol {
name: "[.sbss]",
demangled_name: None,
address: 0,
size: 0,
kind: Section,
section: Some(
2,
),
flags: FlagSet(Local),
align: None,
virtual_address: Some(
2153420048,
),
},
Symbol {
name: "__sinit_IObj_cpp",
demangled_name: None,
address: 264,
size: 20,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Local),
align: None,
virtual_address: Some(
2150895884,
),
},
Symbol {
name: "text$52",
demangled_name: None,
address: 8,
size: 5,
kind: Object,
section: Some(
2,
),
flags: FlagSet(Local),
align: None,
virtual_address: Some(
2153420056,
),
},
Symbol {
name: "Type2Text__10SObjectTagFUi",
demangled_name: Some(
"SObjectTag::Type2Text(unsigned int)",
),
address: 0,
size: 264,
kind: Function,
section: Some(
0,
),
flags: FlagSet(Global),
align: None,
virtual_address: Some(
2150895620,
),
},
Symbol {
name: "gkInvalidObjectTag",
demangled_name: None,
address: 0,
size: 8,
kind: Object,
section: Some(
2,
),
flags: FlagSet(Global),
align: None,
virtual_address: Some(
2153420048,
),
},
Symbol {
name: "__upper_map",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Global),
align: None,
virtual_address: Some(
0,
),
},
Symbol {
name: "__ctype_map",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Global),
align: None,
virtual_address: Some(
0,
),
},
],
sections: [
Section {
id: ".text-0",
name: ".text",
address: 0,
size: 284,
kind: Code,
data: SectionData(
284,
),
flags: FlagSet(),
relocations: [
Relocation {
flags: Elf(
6,
),
address: 22,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
4,
),
address: 26,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
0,
),
address: 28,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
109,
),
address: 36,
target_symbol: 5,
addend: 0,
},
Relocation {
flags: Elf(
6,
),
address: 58,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
4,
),
address: 62,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
0,
),
address: 64,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
109,
),
address: 72,
target_symbol: 5,
addend: 0,
},
Relocation {
flags: Elf(
6,
),
address: 98,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
4,
),
address: 102,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
0,
),
address: 104,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
109,
),
address: 112,
target_symbol: 5,
addend: 0,
},
Relocation {
flags: Elf(
6,
),
address: 138,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
4,
),
address: 142,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
0,
),
address: 144,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Elf(
109,
),
address: 148,
target_symbol: 5,
addend: 0,
},
Relocation {
flags: Elf(
6,
),
address: 162,
target_symbol: 9,
addend: 0,
},
Relocation {
flags: Elf(
4,
),
address: 166,
target_symbol: 9,
addend: 0,
},
Relocation {
flags: Elf(
109,
),
address: 176,
target_symbol: 5,
addend: 0,
},
Relocation {
flags: Elf(
0,
),
address: 180,
target_symbol: 9,
addend: 0,
},
Relocation {
flags: Elf(
0,
),
address: 200,
target_symbol: 9,
addend: 0,
},
Relocation {
flags: Elf(
0,
),
address: 220,
target_symbol: 9,
addend: 0,
},
Relocation {
flags: Elf(
0,
),
address: 240,
target_symbol: 9,
addend: 0,
},
Relocation {
flags: Elf(
109,
),
address: 256,
target_symbol: 5,
addend: 0,
},
Relocation {
flags: Elf(
109,
),
address: 268,
target_symbol: 7,
addend: 0,
},
Relocation {
flags: Elf(
109,
),
address: 272,
target_symbol: 7,
addend: 0,
},
],
line_info: {},
virtual_address: Some(
2150895620,
),
},
Section {
id: ".ctors-0",
name: ".ctors",
address: 0,
size: 4,
kind: Data,
data: SectionData(
4,
),
flags: FlagSet(),
relocations: [
Relocation {
flags: Elf(
1,
),
address: 0,
target_symbol: 4,
addend: 0,
},
],
line_info: {},
virtual_address: Some(
2151461704,
),
},
Section {
id: ".sbss-0",
name: ".sbss",
address: 0,
size: 16,
kind: Bss,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: Some(
2153420048,
),
},
Section {
id: ".rela.text-0",
name: ".rela.text",
address: 0,
size: 216,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".rela.ctors-0",
name: ".rela.ctors",
address: 0,
size: 12,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".symtab-0",
name: ".symtab",
address: 0,
size: 176,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".strtab-0",
name: ".strtab",
address: 0,
size: 105,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".shstrtab-0",
name: ".shstrtab",
address: 0,
size: 77,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".comment-0",
name: ".comment",
address: 0,
size: 132,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".note.split-0",
name: ".note.split",
address: 0,
size: 152,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
],
split_meta: Some(
SplitMeta {
generator: Some(
"decomp-toolkit 1.4.0",
),
module_name: Some(
"main",
),
module_id: Some(
0,
),
virtual_addresses: Some(
[
0,
0,
2150895620,
2151461704,
2153420048,
2150895884,
2153420056,
2150895620,
2153420048,
0,
0,
],
),
},
),
path: None,
timestamp: None,
}

View File

@@ -0,0 +1,97 @@
---
source: objdiff-core/tests/arch_x86.rs
expression: diff.instruction_rows
---
[
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 0,
size: 1,
opcode: 640,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 1,
size: 2,
opcode: 414,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 3,
size: 5,
opcode: 640,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 8,
size: 5,
opcode: 59,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 13,
size: 3,
opcode: 7,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 16,
size: 1,
opcode: 590,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 17,
size: 1,
opcode: 662,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
]

View File

@@ -0,0 +1,11 @@
---
source: objdiff-core/tests/arch_x86.rs
expression: output
---
[(Address(0), Normal, 5), (Spacing(4), Normal, 0), (Opcode("push", 640), Normal, 10), (Argument(Opaque("ebp")), Normal, 0), (Eol, Normal, 0)]
[(Address(1), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 414), Normal, 10), (Argument(Opaque("ebp")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Argument(Opaque("esp")), Normal, 0), (Eol, Normal, 0)]
[(Address(3), Normal, 5), (Spacing(4), Normal, 0), (Opcode("push", 640), Normal, 10), (Symbol(Symbol { name: "$SG526", demangled_name: None, address: 4, size: 6, kind: Object, section: Some(1), flags: FlagSet(Local | SizeInferred), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
[(Address(8), Normal, 5), (Spacing(4), Normal, 0), (Opcode("call", 59), Normal, 10), (Symbol(Symbol { name: "_printf", demangled_name: None, address: 0, size: 0, kind: Function, section: None, flags: FlagSet(Global), align: None, virtual_address: None }), Bright, 0), (Eol, Normal, 0)]
[(Address(13), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 7), Normal, 10), (Argument(Opaque("esp")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Argument(Unsigned(4)), Normal, 0), (Eol, Normal, 0)]
[(Address(16), Normal, 5), (Spacing(4), Normal, 0), (Opcode("pop", 590), Normal, 10), (Argument(Opaque("ebp")), Normal, 0), (Eol, Normal, 0)]
[(Address(17), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ret", 662), Normal, 10), (Eol, Normal, 0)]

View File

@@ -0,0 +1,201 @@
---
source: objdiff-core/tests/arch_x86.rs
expression: obj
---
Object {
arch: ArchX86 {
arch: X86,
endianness: Little,
},
endianness: Little,
symbols: [
Symbol {
name: "objdiffstaticdebug.cpp",
demangled_name: None,
address: 0,
size: 0,
kind: Unknown,
section: None,
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "@comp.id",
demangled_name: None,
address: 0,
size: 0,
kind: Object,
section: None,
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "[.drectve]",
demangled_name: None,
address: 0,
size: 38,
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(
1,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "?a@@3PAXA",
demangled_name: Some(
"void *a",
),
address: 0,
size: 4,
kind: Object,
section: Some(
1,
),
flags: FlagSet(Global | SizeInferred),
align: None,
virtual_address: None,
},
Symbol {
name: "[.text]",
demangled_name: None,
address: 0,
size: 0,
kind: Section,
section: Some(
2,
),
flags: FlagSet(Local),
align: None,
virtual_address: None,
},
Symbol {
name: "?PrintThing@@YAXXZ",
demangled_name: Some(
"void __cdecl PrintThing(void)",
),
address: 0,
size: 18,
kind: Function,
section: Some(
2,
),
flags: FlagSet(Local | SizeInferred),
align: None,
virtual_address: None,
},
Symbol {
name: "_printf",
demangled_name: None,
address: 0,
size: 0,
kind: Function,
section: None,
flags: FlagSet(Global),
align: None,
virtual_address: None,
},
Symbol {
name: "$SG526",
demangled_name: None,
address: 4,
size: 6,
kind: Object,
section: Some(
1,
),
flags: FlagSet(Local | SizeInferred),
align: None,
virtual_address: None,
},
],
sections: [
Section {
id: ".drectve-0",
name: ".drectve",
address: 0,
size: 38,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".data-0",
name: ".data",
address: 0,
size: 10,
kind: Data,
data: SectionData(
10,
),
flags: FlagSet(),
relocations: [
Relocation {
flags: Coff(
6,
),
address: 0,
target_symbol: 6,
addend: 0,
},
],
line_info: {},
virtual_address: None,
},
Section {
id: ".text-0",
name: ".text",
address: 0,
size: 18,
kind: Code,
data: SectionData(
18,
),
flags: FlagSet(),
relocations: [
Relocation {
flags: Coff(
6,
),
address: 4,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Coff(
20,
),
address: 9,
target_symbol: 7,
addend: 0,
},
],
line_info: {},
virtual_address: None,
},
],
split_meta: None,
path: None,
timestamp: None,
}

View File

@@ -0,0 +1,279 @@
---
source: objdiff-core/tests/arch_x86.rs
expression: diff.instruction_rows
---
[
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 0,
size: 5,
opcode: 414,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 5,
size: 5,
opcode: 414,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 10,
size: 1,
opcode: 640,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 11,
size: 4,
opcode: 740,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 15,
size: 5,
opcode: 414,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 20,
size: 5,
opcode: 414,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 25,
size: 4,
opcode: 448,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 29,
size: 4,
opcode: 460,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 33,
size: 5,
opcode: 414,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 38,
size: 5,
opcode: 414,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 43,
size: 5,
opcode: 448,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 48,
size: 5,
opcode: 460,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 53,
size: 4,
opcode: 11,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 57,
size: 5,
opcode: 414,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 62,
size: 5,
opcode: 414,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 67,
size: 5,
opcode: 448,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 72,
size: 5,
opcode: 460,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 77,
size: 4,
opcode: 11,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 81,
size: 4,
opcode: 7,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 85,
size: 1,
opcode: 590,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 86,
size: 1,
opcode: 662,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
]

View File

@@ -0,0 +1,25 @@
---
source: objdiff-core/tests/arch_x86.rs
expression: output
---
[(Address(0), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 414), Normal, 10), (Basic("["), Normal, 0), (Argument(Opaque("rsp")), Normal, 0), (Argument(Opaque("+")), Normal, 0), (Argument(Signed(16)), Normal, 0), (Basic("]"), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Argument(Opaque("rdx")), Normal, 0), (Eol, Normal, 0)]
[(Address(5), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 414), Normal, 10), (Basic("["), Normal, 0), (Argument(Opaque("rsp")), Normal, 0), (Argument(Opaque("+")), Normal, 0), (Argument(Signed(8)), Normal, 0), (Basic("]"), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Argument(Opaque("rcx")), Normal, 0), (Eol, Normal, 0)]
[(Address(10), Normal, 5), (Spacing(4), Normal, 0), (Opcode("push", 640), Normal, 10), (Argument(Opaque("rdi")), Normal, 0), (Eol, Normal, 0)]
[(Address(11), Normal, 5), (Spacing(4), Normal, 0), (Opcode("sub", 740), Normal, 10), (Argument(Opaque("rsp")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Argument(Unsigned(16)), Normal, 0), (Eol, Normal, 0)]
[(Address(15), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 414), Normal, 10), (Argument(Opaque("rax")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("rsp")), Normal, 0), (Argument(Opaque("+")), Normal, 0), (Argument(Signed(32)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(20), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 414), Normal, 10), (Argument(Opaque("rcx")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("rsp")), Normal, 0), (Argument(Opaque("+")), Normal, 0), (Argument(Signed(40)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(25), Normal, 5), (Spacing(4), Normal, 0), (Opcode("movss", 448), Normal, 10), (Argument(Opaque("xmm0")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("rax")), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(29), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mulss", 460), Normal, 10), (Argument(Opaque("xmm0")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("rcx")), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(33), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 414), Normal, 10), (Argument(Opaque("rax")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("rsp")), Normal, 0), (Argument(Opaque("+")), Normal, 0), (Argument(Signed(32)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(38), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 414), Normal, 10), (Argument(Opaque("rcx")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("rsp")), Normal, 0), (Argument(Opaque("+")), Normal, 0), (Argument(Signed(40)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(43), Normal, 5), (Spacing(4), Normal, 0), (Opcode("movss", 448), Normal, 10), (Argument(Opaque("xmm1")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("rax")), Normal, 0), (Argument(Opaque("+")), Normal, 0), (Argument(Signed(4)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(48), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mulss", 460), Normal, 10), (Argument(Opaque("xmm1")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("rcx")), Normal, 0), (Argument(Opaque("+")), Normal, 0), (Argument(Signed(4)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(53), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addss", 11), Normal, 10), (Argument(Opaque("xmm0")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Argument(Opaque("xmm1")), Normal, 0), (Eol, Normal, 0)]
[(Address(57), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 414), Normal, 10), (Argument(Opaque("rax")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("rsp")), Normal, 0), (Argument(Opaque("+")), Normal, 0), (Argument(Signed(32)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(62), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mov", 414), Normal, 10), (Argument(Opaque("rcx")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("rsp")), Normal, 0), (Argument(Opaque("+")), Normal, 0), (Argument(Signed(40)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(67), Normal, 5), (Spacing(4), Normal, 0), (Opcode("movss", 448), Normal, 10), (Argument(Opaque("xmm1")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("rax")), Normal, 0), (Argument(Opaque("+")), Normal, 0), (Argument(Signed(8)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(72), Normal, 5), (Spacing(4), Normal, 0), (Opcode("mulss", 460), Normal, 10), (Argument(Opaque("xmm1")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Basic("["), Normal, 0), (Argument(Opaque("rcx")), Normal, 0), (Argument(Opaque("+")), Normal, 0), (Argument(Signed(8)), Normal, 0), (Basic("]"), Normal, 0), (Eol, Normal, 0)]
[(Address(77), Normal, 5), (Spacing(4), Normal, 0), (Opcode("addss", 11), Normal, 10), (Argument(Opaque("xmm0")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Argument(Opaque("xmm1")), Normal, 0), (Eol, Normal, 0)]
[(Address(81), Normal, 5), (Spacing(4), Normal, 0), (Opcode("add", 7), Normal, 10), (Argument(Opaque("rsp")), Normal, 0), (Basic(","), Normal, 0), (Spacing(1), Normal, 0), (Argument(Unsigned(16)), Normal, 0), (Eol, Normal, 0)]
[(Address(85), Normal, 5), (Spacing(4), Normal, 0), (Opcode("pop", 590), Normal, 10), (Argument(Opaque("rdi")), Normal, 0), (Eol, Normal, 0)]
[(Address(86), Normal, 5), (Spacing(4), Normal, 0), (Opcode("ret", 662), Normal, 10), (Eol, Normal, 0)]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,940 @@
---
source: objdiff-core/tests/arch_x86.rs
expression: obj.sections
---
[
Section {
id: ".drectve-0",
name: ".drectve",
address: 0,
size: 47,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".debug$S-0",
name: ".debug$S",
address: 0,
size: 100,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".rdata-0",
name: ".rdata",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".rdata-1",
name: ".rdata",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".text$mn-0",
name: ".text$mn",
address: 0,
size: 0,
kind: Code,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".data-combined",
name: ".data",
address: 0,
size: 56,
kind: Data,
data: SectionData(
56,
),
flags: FlagSet(Combined),
relocations: [
Relocation {
flags: Coff(
6,
),
address: 0,
target_symbol: 44,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 16,
target_symbol: 44,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 32,
target_symbol: 44,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 48,
target_symbol: 6,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 52,
target_symbol: 8,
addend: 0,
},
],
line_info: {},
virtual_address: None,
},
Section {
id: ".rdata-combined",
name: ".rdata",
address: 0,
size: 295,
kind: Data,
data: SectionData(
295,
),
flags: FlagSet(Combined),
relocations: [
Relocation {
flags: Coff(
6,
),
address: 12,
target_symbol: 17,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 16,
target_symbol: 19,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 21,
target_symbol: 13,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 45,
target_symbol: 15,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 61,
target_symbol: 25,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 65,
target_symbol: 27,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 70,
target_symbol: 21,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 94,
target_symbol: 23,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 110,
target_symbol: 31,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 114,
target_symbol: 33,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 130,
target_symbol: 35,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 134,
target_symbol: 37,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 138,
target_symbol: 19,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 142,
target_symbol: 39,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 147,
target_symbol: 31,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 171,
target_symbol: 33,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 175,
target_symbol: 21,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 199,
target_symbol: 23,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 215,
target_symbol: 31,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 219,
target_symbol: 33,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 235,
target_symbol: 13,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 239,
target_symbol: 15,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 255,
target_symbol: 21,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 259,
target_symbol: 23,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 263,
target_symbol: 29,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 267,
target_symbol: 11,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 271,
target_symbol: 43,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 275,
target_symbol: 41,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 279,
target_symbol: 70,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 283,
target_symbol: 56,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 287,
target_symbol: 72,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 291,
target_symbol: 59,
addend: 0,
},
],
line_info: {},
virtual_address: None,
},
Section {
id: ".rdata$r-1",
name: ".rdata$r",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".rdata$r-2",
name: ".rdata$r",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".data$rs-1",
name: ".data$rs",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".rdata$r-3",
name: ".rdata$r",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".rdata$r-4",
name: ".rdata$r",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".rdata$r-5",
name: ".rdata$r",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".rdata$r-6",
name: ".rdata$r",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".data$rs-2",
name: ".data$rs",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".rdata$r-7",
name: ".rdata$r",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".rdata$r-8",
name: ".rdata$r",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".rdata$r-9",
name: ".rdata$r",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".rdata$r-10",
name: ".rdata$r",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".text$mn-1",
name: ".text$mn",
address: 0,
size: 0,
kind: Code,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".rdata$r-11",
name: ".rdata$r",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".text$mn-2",
name: ".text$mn",
address: 0,
size: 0,
kind: Code,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".text$mn-3",
name: ".text$mn",
address: 0,
size: 0,
kind: Code,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".text$mn-4",
name: ".text$mn",
address: 0,
size: 0,
kind: Code,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".text$mn-5",
name: ".text$mn",
address: 0,
size: 0,
kind: Code,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".text$mn-6",
name: ".text$mn",
address: 0,
size: 0,
kind: Code,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".text-combined",
name: ".text",
address: 0,
size: 268,
kind: Code,
data: SectionData(
268,
),
flags: FlagSet(Combined),
relocations: [
Relocation {
flags: Coff(
6,
),
address: 4,
target_symbol: 62,
addend: 0,
},
Relocation {
flags: Coff(
20,
),
address: 9,
target_symbol: 53,
addend: 0,
},
Relocation {
flags: Coff(
20,
),
address: 29,
target_symbol: 60,
addend: 0,
},
Relocation {
flags: Coff(
20,
),
address: 48,
target_symbol: 52,
addend: 0,
},
Relocation {
flags: Coff(
20,
),
address: 68,
target_symbol: 11,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 84,
target_symbol: 64,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 104,
target_symbol: 66,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 124,
target_symbol: 6,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 134,
target_symbol: 8,
addend: 0,
},
Relocation {
flags: Coff(
20,
),
address: 145,
target_symbol: 57,
addend: 0,
},
Relocation {
flags: Coff(
20,
),
address: 153,
target_symbol: 54,
addend: 0,
},
Relocation {
flags: Coff(
20,
),
address: 172,
target_symbol: 54,
addend: 0,
},
Relocation {
flags: Coff(
20,
),
address: 191,
target_symbol: 52,
addend: 0,
},
Relocation {
flags: Coff(
20,
),
address: 218,
target_symbol: 57,
addend: 0,
},
Relocation {
flags: Coff(
20,
),
address: 237,
target_symbol: 52,
addend: 0,
},
Relocation {
flags: Coff(
6,
),
address: 257,
target_symbol: 68,
addend: 0,
},
Relocation {
flags: Coff(
20,
),
address: 262,
target_symbol: 60,
addend: 0,
},
],
line_info: {},
virtual_address: None,
},
Section {
id: ".text$yd-0",
name: ".text$yd",
address: 0,
size: 0,
kind: Code,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".rdata-2",
name: ".rdata",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".rdata-3",
name: ".rdata",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".data-0",
name: ".data",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".rdata$r-12",
name: ".rdata$r",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".rdata$r-13",
name: ".rdata$r",
address: 0,
size: 0,
kind: Data,
data: SectionData(
0,
),
flags: FlagSet(Hidden),
relocations: [],
line_info: {},
virtual_address: None,
},
Section {
id: ".CRT$XCU-0",
name: ".CRT$XCU",
address: 0,
size: 4,
kind: Data,
data: SectionData(
4,
),
flags: FlagSet(),
relocations: [
Relocation {
flags: Coff(
6,
),
address: 0,
target_symbol: 61,
addend: 0,
},
],
line_info: {},
virtual_address: None,
},
Section {
id: ".chks64-0",
name: ".chks64",
address: 0,
size: 280,
kind: Unknown,
data: SectionData(
0,
),
flags: FlagSet(),
relocations: [],
line_info: {},
virtual_address: None,
},
]

View File

@@ -25,38 +25,34 @@ wsl = []
[dependencies]
anyhow = "1.0"
bytes = "1.9"
cfg-if = "1.0"
const_format = "0.2"
cwdemangle = "1.0"
cwextab = "1.0.2"
dirs = "5.0"
egui = "0.29"
egui_extras = "0.29"
dirs = "6.0"
egui = "0.31"
egui_extras = "0.31"
filetime = "0.2"
float-ord = "0.3"
font-kit = "0.14"
globset = { version = "0.4", features = ["serde1"] }
log = "0.4"
notify = { git = "https://github.com/notify-rs/notify", rev = "128bf6230c03d39dbb7f301ff7b20e594e34c3a2" }
objdiff-core = { path = "../objdiff-core", features = ["all"] }
open = "5.3"
png = "0.17"
pollster = "0.4"
regex = "1.11"
rfd = { version = "0.15" } #, default-features = false, features = ['xdg-portal']
rlwinmdec = "1.0"
rlwinmdec = { version = "1.0", git = "https://github.com/CelestialAmber/rlwinmdec.git" }
ron = "0.8"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
shell-escape = "0.1"
strum = { version = "0.26", features = ["derive"] }
tempfile = "3.14"
time = { version = "0.3", features = ["formatting", "local-offset"] }
typed-path = "0.10"
winit = { version = "0.30", features = ["wayland-csd-adwaita"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Keep version in sync with egui
[dependencies.eframe]
version = "0.29"
version = "0.31"
features = [
"default_fonts",
"persistence",
@@ -67,7 +63,7 @@ default-features = false
# Keep version in sync with eframe
[dependencies.wgpu]
version = "22.1"
version = "24.0"
features = [
"dx12",
"metal",
@@ -76,34 +72,14 @@ features = [
optional = true
default-features = false
# For Linux static binaries, use rustls
[target.'cfg(target_os = "linux")'.dependencies]
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "rustls-tls"] }
self_update = { version = "0.41", default-features = false, features = ["rustls"] }
# For all other platforms, use native TLS
[target.'cfg(not(target_os = "linux"))'.dependencies]
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "multipart", "default-tls"] }
self_update = "0.41"
[target.'cfg(windows)'.dependencies]
path-slash = "0.2"
winapi = "0.3"
[target.'cfg(unix)'.dependencies]
exec = "0.3"
# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# web:
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1"
tracing-wasm = "0.2"
[build-dependencies]
anyhow = "1.0"
[target.'cfg(windows)'.build-dependencies]
tauri-winres = "0.1"
tauri-winres = "0.3"

View File

@@ -1,50 +1,49 @@
use std::{
collections::BTreeMap,
default::Default,
fs,
path::{Path, PathBuf},
rc::Rc,
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex, RwLock,
atomic::{AtomicBool, Ordering},
},
time::Instant,
};
use filetime::FileTime;
use globset::{Glob, GlobSet};
use notify::{RecursiveMode, Watcher};
use globset::Glob;
use objdiff_core::{
build::watcher::{Watcher, create_watcher},
config::{
build_globset, save_project_config, ProjectConfig, ProjectConfigInfo, ProjectObject,
ScratchConfig, SymbolMappings, DEFAULT_WATCH_PATTERNS,
DEFAULT_WATCH_PATTERNS, ProjectConfig, ProjectConfigInfo, ProjectObject, ScratchConfig,
build_globset, default_watch_patterns, path::platform_path_serde_option,
save_project_config,
},
diff::DiffObjConfig,
jobs::{Job, JobQueue, JobResult},
};
use time::UtcOffset;
use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
use crate::{
app_config::{deserialize_config, AppConfigVersion},
config::{load_project_config, ProjectObjectNode},
hotkeys,
jobs::{
objdiff::{start_build, ObjDiffConfig},
Job, JobQueue, JobResult, JobStatus,
},
app_config::{AppConfigVersion, deserialize_config},
config::{ProjectObjectNode, load_project_config},
jobs::{create_objdiff_config, egui_waker, start_build},
views::{
appearance::{appearance_window, Appearance},
appearance::{Appearance, appearance_window},
config::{
arch_config_window, config_ui, project_window, ConfigViewState, CONFIG_DISABLED_TEXT,
CONFIG_DISABLED_TEXT, ConfigViewState, arch_config_window, config_ui,
general_config_ui, project_window,
},
data_diff::data_diff_ui,
debug::debug_window,
demangle::{demangle_window, DemangleViewState},
extab_diff::extab_diff_ui,
demangle::{DemangleViewState, demangle_window},
diff::diff_view_ui,
frame_history::FrameHistory,
function_diff::function_diff_ui,
graphics::{graphics_window, GraphicsConfig, GraphicsViewState},
graphics::{GraphicsConfig, GraphicsViewState, graphics_window},
jobs::{jobs_menu_ui, jobs_window},
rlwinm::{rlwinm_decode_window, RlwinmDecodeViewState},
symbol_diff::{symbol_diff_ui, DiffViewAction, DiffViewNavigation, DiffViewState, View},
rlwinm::{RlwinmDecodeViewState, rlwinm_decode_window},
symbol_diff::{DiffViewAction, DiffViewState, ResolvedNavigation, View},
},
};
@@ -94,26 +93,53 @@ impl Default for ViewState {
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct ObjectConfig {
pub name: String,
pub target_path: Option<PathBuf>,
pub base_path: Option<PathBuf>,
#[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 reverse_fn_order: Option<bool>,
pub complete: Option<bool>,
pub scratch: Option<ScratchConfig>,
pub source_path: Option<String>,
#[serde(default)]
pub symbol_mappings: SymbolMappings,
pub hidden: bool,
pub scratch: Option<ScratchConfig>,
#[serde(default, with = "platform_path_serde_option")]
pub source_path: Option<Utf8PlatformPathBuf>,
#[serde(default)]
pub symbol_mappings: BTreeMap<String, String>,
}
impl From<&ProjectObject> for ObjectConfig {
fn from(object: &ProjectObject) -> Self {
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()))
};
let source_path =
object.source_path().map(|s| project_dir.join(s.with_platform_encoding()));
Self {
name: object.name().to_string(),
target_path: object.target_path.clone(),
base_path: object.base_path.clone(),
target_path,
base_path,
reverse_fn_order: object.reverse_fn_order(),
complete: object.complete(),
hidden: object.hidden(),
scratch: object.scratch.clone(),
source_path: object.source_path().cloned(),
source_path,
symbol_mappings: object.symbol_mappings.clone().unwrap_or_default(),
}
}
@@ -122,14 +148,9 @@ impl From<&ProjectObject> for ObjectConfig {
#[inline]
fn bool_true() -> bool { true }
#[inline]
fn default_watch_patterns() -> Vec<Glob> {
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
}
pub struct AppState {
pub config: AppConfig,
pub objects: Vec<ProjectObject>,
pub objects: Vec<ObjectConfig>,
pub object_nodes: Vec<ProjectObjectNode>,
pub watcher_change: bool,
pub config_change: bool,
@@ -179,12 +200,12 @@ pub struct AppConfig {
pub custom_args: Option<Vec<String>>,
#[serde(default)]
pub selected_wsl_distro: Option<String>,
#[serde(default)]
pub project_dir: Option<PathBuf>,
#[serde(default)]
pub target_obj_dir: Option<PathBuf>,
#[serde(default)]
pub base_obj_dir: Option<PathBuf>,
#[serde(default, with = "platform_path_serde_option")]
pub project_dir: Option<Utf8PlatformPathBuf>,
#[serde(default, with = "platform_path_serde_option")]
pub target_obj_dir: Option<Utf8PlatformPathBuf>,
#[serde(default, with = "platform_path_serde_option")]
pub base_obj_dir: Option<Utf8PlatformPathBuf>,
#[serde(default)]
pub selected_obj: Option<ObjectConfig>,
#[serde(default = "bool_true")]
@@ -198,7 +219,7 @@ pub struct AppConfig {
#[serde(default = "default_watch_patterns")]
pub watch_patterns: Vec<Glob>,
#[serde(default)]
pub recent_projects: Vec<PathBuf>,
pub recent_projects: Vec<String>,
#[serde(default)]
pub diff_obj_config: DiffObjConfig,
}
@@ -226,12 +247,12 @@ impl Default for AppConfig {
}
impl AppState {
pub fn set_project_dir(&mut self, path: PathBuf) {
pub fn set_project_dir(&mut self, path: Utf8PlatformPathBuf) {
self.config.recent_projects.retain(|p| p != &path);
if self.config.recent_projects.len() > 9 {
self.config.recent_projects.truncate(9);
}
self.config.recent_projects.insert(0, path.clone());
self.config.recent_projects.insert(0, path.to_string());
self.config.project_dir = Some(path);
self.config.target_obj_dir = None;
self.config.base_obj_dir = None;
@@ -249,7 +270,7 @@ impl AppState {
self.selecting_right = None;
}
pub fn set_target_obj_dir(&mut self, path: PathBuf) {
pub fn set_target_obj_dir(&mut self, path: Utf8PlatformPathBuf) {
self.config.target_obj_dir = Some(path);
self.config.selected_obj = None;
self.obj_change = true;
@@ -258,7 +279,7 @@ impl AppState {
self.selecting_right = None;
}
pub fn set_base_obj_dir(&mut self, path: PathBuf) {
pub fn set_base_obj_dir(&mut self, path: Utf8PlatformPathBuf) {
self.config.base_obj_dir = Some(path);
self.config.selected_obj = None;
self.obj_change = true;
@@ -301,7 +322,7 @@ impl AppState {
let Some(object) = self.config.selected_obj.as_mut() else {
return;
};
object.symbol_mappings.remove_by_right(right);
object.symbol_mappings.retain(|_, r| r != right);
self.selecting_left = Some(right.to_string());
self.queue_reload = true;
self.save_config();
@@ -311,7 +332,7 @@ impl AppState {
let Some(object) = self.config.selected_obj.as_mut() else {
return;
};
object.symbol_mappings.remove_by_left(left);
object.symbol_mappings.retain(|l, _| l != left);
self.selecting_right = Some(left.to_string());
self.queue_reload = true;
self.save_config();
@@ -324,10 +345,8 @@ impl AppState {
};
self.selecting_left = None;
self.selecting_right = None;
if left == right {
object.symbol_mappings.remove_by_left(&left);
object.symbol_mappings.remove_by_right(&right);
} else {
object.symbol_mappings.retain(|l, r| l != &left && r != &right);
if left != right {
object.symbol_mappings.insert(left.clone(), right.clone());
}
self.queue_reload = true;
@@ -371,14 +390,8 @@ impl AppState {
Some(object.symbol_mappings.clone())
};
}
if let Some(existing) =
self.objects.iter_mut().find(|u| u.name.as_ref().is_some_and(|n| n == &object.name))
{
existing.symbol_mappings = if object.symbol_mappings.is_empty() {
None
} else {
Some(object.symbol_mappings.clone())
};
if let Some(existing) = self.objects.iter_mut().find(|u| u.name == object.name) {
existing.symbol_mappings = object.symbol_mappings.clone();
}
}
// Save the updated project config
@@ -400,7 +413,7 @@ pub struct App {
view_state: ViewState,
state: AppStateRef,
modified: Arc<AtomicBool>,
watcher: Option<notify::RecommendedWatcher>,
watcher: Option<Watcher>,
app_path: Option<PathBuf>,
relaunch_path: Rc<Mutex<Option<PathBuf>>>,
should_relaunch: bool,
@@ -475,53 +488,17 @@ impl App {
let ViewState { jobs, diff_state, config_state, .. } = &mut self.view_state;
let mut results = vec![];
for (job, result) in jobs.iter_finished() {
match result {
Ok(result) => {
log::info!("Job {} finished", job.id);
match result {
JobResult::None => {
if let Some(err) = &job.context.status.read().unwrap().error {
log::error!("{:?}", err);
}
}
JobResult::Update(state) => {
if let Ok(mut guard) = self.relaunch_path.lock() {
*guard = Some(state.exe_path);
self.should_relaunch = true;
}
}
_ => results.push(result),
}
}
Err(err) => {
let err = if let Some(msg) = err.downcast_ref::<&'static str>() {
anyhow::Error::msg(*msg)
} else if let Some(msg) = err.downcast_ref::<String>() {
anyhow::Error::msg(msg.clone())
} else {
anyhow::Error::msg("Thread panicked")
};
let result = job.context.status.write();
if let Ok(mut guard) = result {
guard.error = Some(err);
} else {
drop(result);
job.context.status = Arc::new(RwLock::new(JobStatus {
title: "Error".to_string(),
progress_percent: 0.0,
progress_items: None,
status: String::new(),
error: Some(err),
}));
}
jobs.collect_results();
jobs.results.retain(|result| match result {
JobResult::Update(state) => {
if let Ok(mut guard) = self.relaunch_path.lock() {
*guard = Some(state.exe_path.clone());
self.should_relaunch = true;
}
false
}
}
jobs.results.append(&mut results);
jobs.clear_finished();
_ => true,
});
diff_state.pre_update(jobs, &self.state);
config_state.pre_update(jobs, &self.state);
debug_assert!(jobs.results.is_empty());
@@ -577,8 +554,13 @@ impl App {
match build_globset(&state.config.watch_patterns)
.map_err(anyhow::Error::new)
.and_then(|globset| {
create_watcher(ctx.clone(), self.modified.clone(), project_dir, globset)
.map_err(anyhow::Error::new)
create_watcher(
self.modified.clone(),
project_dir.as_ref(),
globset,
egui_waker(ctx),
)
.map_err(anyhow::Error::new)
}) {
Ok(watcher) => self.watcher = Some(watcher),
Err(e) => log::error!("Failed to create watcher: {e}"),
@@ -624,15 +606,15 @@ impl App {
&& state.config.selected_obj.is_some()
&& !jobs.is_running(Job::ObjDiff)
{
jobs.push(start_build(ctx, ObjDiffConfig::from_state(state)));
start_build(ctx, jobs, create_objdiff_config(state));
state.queue_build = false;
state.queue_reload = false;
} else if state.queue_reload && !jobs.is_running(Job::ObjDiff) {
let mut diff_config = ObjDiffConfig::from_state(state);
let mut diff_config = create_objdiff_config(state);
// Don't build, just reload the current files
diff_config.build_base = false;
diff_config.build_target = false;
jobs.push(start_build(ctx, diff_config));
start_build(ctx, jobs, diff_config);
state.queue_reload = false;
}
@@ -695,13 +677,13 @@ impl eframe::App for App {
*show_side_panel = !*show_side_panel;
}
ui.separator();
ui.menu_button(hotkeys::button_alt_text(ui, "_File"), |ui| {
ui.menu_button("File", |ui| {
#[cfg(debug_assertions)]
if ui.button(hotkeys::button_alt_text(ui, "_Debug…")).clicked() {
if ui.button("Debug…").clicked() {
*show_debug = !*show_debug;
ui.close_menu();
}
if ui.button(hotkeys::button_alt_text(ui, "_Project…")).clicked() {
if ui.button("Project…").clicked() {
*show_project_config = !*show_project_config;
ui.close_menu();
}
@@ -710,56 +692,55 @@ impl eframe::App for App {
} else {
vec![]
};
let recent_projects_text = hotkeys::button_alt_text(ui, "_Recent Projects…");
if recent_projects.is_empty() {
ui.add_enabled(false, egui::Button::new(recent_projects_text));
ui.add_enabled(false, egui::Button::new("Recent projects"));
} else {
ui.menu_button(recent_projects_text, |ui| {
ui.menu_button("Recent Projects", |ui| {
if ui.button("Clear").clicked() {
state.write().unwrap().config.recent_projects.clear();
};
ui.separator();
for path in recent_projects {
if ui.button(format!("{}", path.display())).clicked() {
state.write().unwrap().set_project_dir(path);
if ui.button(&path).clicked() {
state
.write()
.unwrap()
.set_project_dir(Utf8PlatformPathBuf::from(path));
ui.close_menu();
}
}
});
}
if ui.button(hotkeys::button_alt_text(ui, "_Appearance…")).clicked() {
if ui.button("Appearance…").clicked() {
*show_appearance_config = !*show_appearance_config;
ui.close_menu();
}
if ui.button(hotkeys::button_alt_text(ui, "_Graphics…")).clicked() {
if ui.button("Graphics…").clicked() {
*show_graphics = !*show_graphics;
ui.close_menu();
}
if ui.button(hotkeys::button_alt_text(ui, "_Quit")).clicked() {
if ui.button("Quit").clicked() {
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
}
});
ui.menu_button(hotkeys::button_alt_text(ui, "_Tools"), |ui| {
if ui.button(hotkeys::button_alt_text(ui, "_Demangle…")).clicked() {
ui.menu_button("Tools", |ui| {
if ui.button("Demangle…").clicked() {
*show_demangle = !*show_demangle;
ui.close_menu();
}
if ui.button(hotkeys::button_alt_text(ui, "_Rlwinm Decoder…")).clicked() {
if ui.button("Rlwinm Decoder…").clicked() {
*show_rlwinm_decode = !*show_rlwinm_decode;
ui.close_menu();
}
});
ui.menu_button(hotkeys::button_alt_text(ui, "_Diff Options"), |ui| {
if ui.button(hotkeys::button_alt_text(ui, "_Arch Settings…")).clicked() {
ui.menu_button("Diff Options", |ui| {
if ui.button("Arch Settings…").clicked() {
*show_arch_config = !*show_arch_config;
ui.close_menu();
}
let mut state = state.write().unwrap();
let response = ui
.checkbox(
&mut state.config.rebuild_on_changes,
hotkeys::button_alt_text(ui, "_Rebuild on changes"),
)
.checkbox(&mut state.config.rebuild_on_changes, "Rebuild on changes")
.on_hover_text("Automatically re-run the build & diff when files change.");
if response.changed() {
state.watcher_change = true;
@@ -768,54 +749,20 @@ impl eframe::App for App {
!diff_state.symbol_state.disable_reverse_fn_order,
egui::Checkbox::new(
&mut diff_state.symbol_state.reverse_fn_order,
hotkeys::button_alt_text(
ui,
"Reverse function order (-inline _deferred)",
),
"Reverse function order (-inline deferred)",
),
)
.on_disabled_hover_text(CONFIG_DISABLED_TEXT);
ui.checkbox(
&mut diff_state.symbol_state.show_hidden_symbols,
hotkeys::button_alt_text(ui, "Show _hidden symbols"),
"Show hidden symbols",
);
if ui
.checkbox(
&mut state.config.diff_obj_config.relax_reloc_diffs,
hotkeys::button_alt_text(ui, "Rela_x relocation diffs"),
)
.on_hover_text(
"Ignores differences in relocation targets. (Address, name, etc)",
)
.changed()
{
state.queue_reload = true;
}
if ui
.checkbox(
&mut state.config.diff_obj_config.space_between_args,
hotkeys::button_alt_text(ui, "_Space between args"),
)
.changed()
{
state.queue_reload = true;
}
if ui
.checkbox(
&mut state.config.diff_obj_config.combine_data_sections,
hotkeys::button_alt_text(ui, "Combine _data sections"),
)
.on_hover_text("Combines data sections with equal names.")
.changed()
{
state.queue_reload = true;
}
if ui
.button(hotkeys::button_alt_text(ui, "_Clear custom symbol mappings"))
.clicked()
{
ui.separator();
general_config_ui(ui, &mut state);
ui.separator();
if ui.button("Clear custom symbol mappings").clicked() {
state.clear_mappings();
diff_state.post_build_nav = Some(DiffViewNavigation::symbol_diff());
diff_state.post_build_nav = Some(ResolvedNavigation::default());
state.queue_reload = true;
}
});
@@ -836,16 +783,8 @@ impl eframe::App for App {
let mut action = None;
egui::CentralPanel::default().show(ctx, |ui| {
let build_success = matches!(&diff_state.build, Some(b) if b.first_status.success && b.second_status.success);
action = if diff_state.current_view == View::FunctionDiff && build_success {
function_diff_ui(ui, diff_state, appearance)
} else if diff_state.current_view == View::DataDiff && build_success {
data_diff_ui(ui, diff_state, appearance)
} else if diff_state.current_view == View::ExtabDiff && build_success {
extab_diff_ui(ui, diff_state, appearance)
} else {
symbol_diff_ui(ui, diff_state, appearance)
};
let state = state.read().unwrap();
action = diff_view_ui(ui, diff_state, appearance, &state.config.diff_obj_config);
});
project_window(ctx, state, show_project_config, config_state, appearance);
@@ -869,43 +808,9 @@ impl eframe::App for App {
}
}
fn create_watcher(
ctx: egui::Context,
modified: Arc<AtomicBool>,
project_dir: &Path,
patterns: GlobSet,
) -> notify::Result<notify::RecommendedWatcher> {
let base_dir = project_dir.to_owned();
let mut watcher =
notify::recommended_watcher(move |res: notify::Result<notify::Event>| match res {
Ok(event) => {
if matches!(
event.kind,
notify::EventKind::Modify(..)
| notify::EventKind::Create(..)
| notify::EventKind::Remove(..)
) {
for path in &event.paths {
let Ok(path) = path.strip_prefix(&base_dir) else {
continue;
};
if patterns.is_match(path) {
log::info!("File modified: {}", path.display());
modified.store(true, Ordering::Relaxed);
ctx.request_repaint();
}
}
}
}
Err(e) => log::error!("watch error: {e:?}"),
})?;
watcher.watch(project_dir, RecursiveMode::Recursive)?;
Ok(watcher)
}
#[inline]
fn file_modified(path: &Path, last_ts: FileTime) -> bool {
if let Ok(metadata) = fs::metadata(path) {
fn file_modified<P: AsRef<Path>>(path: P, last_ts: FileTime) -> bool {
if let Ok(metadata) = fs::metadata(path.as_ref()) {
FileTime::from_last_modification_time(&metadata) != last_ts
} else {
false

View File

@@ -1,13 +1,17 @@
use std::path::PathBuf;
use std::collections::BTreeMap;
use eframe::Storage;
use globset::Glob;
use objdiff_core::{
config::ScratchConfig,
diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig, MipsAbi, MipsInstrCategory, X86Formatter},
diff::{
ArmArchVersion, ArmR9Usage, DiffObjConfig, FunctionRelocDiffs, MipsAbi, MipsInstrCategory,
X86Formatter,
},
};
use typed_path::{Utf8PlatformPathBuf, Utf8UnixPathBuf};
use crate::app::{AppConfig, ObjectConfig, CONFIG_KEY};
use crate::app::{AppConfig, CONFIG_KEY, ObjectConfig};
#[derive(Clone, serde::Deserialize, serde::Serialize)]
pub struct AppConfigVersion {
@@ -15,7 +19,7 @@ pub struct AppConfigVersion {
}
impl Default for AppConfigVersion {
fn default() -> Self { Self { version: 2 } }
fn default() -> Self { Self { version: 3 } }
}
/// Deserialize the AppConfig from storage, handling upgrades from older versions.
@@ -23,7 +27,8 @@ pub fn deserialize_config(storage: &dyn Storage) -> Option<AppConfig> {
let str = storage.get_string(CONFIG_KEY)?;
match ron::from_str::<AppConfigVersion>(&str) {
Ok(version) => match version.version {
2 => from_str::<AppConfig>(&str),
3 => from_str::<AppConfig>(&str),
2 => from_str::<AppConfigV2>(&str).map(|c| c.into_config()),
1 => from_str::<AppConfigV1>(&str).map(|c| c.into_config()),
_ => {
log::warn!("Unknown config version: {}", version.version);
@@ -49,6 +54,120 @@ where T: serde::de::DeserializeOwned {
}
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct ScratchConfigV2 {
#[serde(default)]
pub platform: Option<String>,
#[serde(default)]
pub compiler: Option<String>,
#[serde(default)]
pub c_flags: Option<String>,
#[serde(default)]
pub ctx_path: Option<String>,
#[serde(default)]
pub build_ctx: Option<bool>,
#[serde(default)]
pub preset_id: Option<u32>,
}
impl ScratchConfigV2 {
fn into_config(self) -> ScratchConfig {
ScratchConfig {
platform: self.platform,
compiler: self.compiler,
c_flags: self.c_flags,
ctx_path: self.ctx_path.map(Utf8UnixPathBuf::from),
build_ctx: self.build_ctx,
preset_id: self.preset_id,
}
}
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct ObjectConfigV2 {
pub name: String,
pub target_path: Option<String>,
pub base_path: Option<String>,
pub reverse_fn_order: Option<bool>,
pub complete: Option<bool>,
pub scratch: Option<ScratchConfigV2>,
pub source_path: Option<String>,
#[serde(default)]
pub symbol_mappings: BTreeMap<String, String>,
}
impl ObjectConfigV2 {
fn into_config(self) -> ObjectConfig {
ObjectConfig {
name: self.name,
target_path: self.target_path.map(Utf8PlatformPathBuf::from),
base_path: self.base_path.map(Utf8PlatformPathBuf::from),
reverse_fn_order: self.reverse_fn_order,
complete: self.complete,
hidden: false,
scratch: self.scratch.map(|scratch| scratch.into_config()),
source_path: None,
symbol_mappings: self.symbol_mappings,
}
}
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct AppConfigV2 {
pub version: u32,
#[serde(default)]
pub custom_make: Option<String>,
#[serde(default)]
pub custom_args: Option<Vec<String>>,
#[serde(default)]
pub selected_wsl_distro: Option<String>,
#[serde(default)]
pub project_dir: Option<String>,
#[serde(default)]
pub target_obj_dir: Option<String>,
#[serde(default)]
pub base_obj_dir: Option<String>,
#[serde(default)]
pub selected_obj: Option<ObjectConfigV2>,
#[serde(default = "bool_true")]
pub build_base: bool,
#[serde(default)]
pub build_target: bool,
#[serde(default = "bool_true")]
pub rebuild_on_changes: bool,
#[serde(default)]
pub auto_update_check: bool,
#[serde(default)]
pub watch_patterns: Vec<Glob>,
#[serde(default)]
pub recent_projects: Vec<String>,
#[serde(default)]
pub diff_obj_config: DiffObjConfigV1,
}
impl AppConfigV2 {
fn into_config(self) -> AppConfig {
log::info!("Upgrading configuration from v2");
AppConfig {
custom_make: self.custom_make,
custom_args: self.custom_args,
selected_wsl_distro: self.selected_wsl_distro,
project_dir: self.project_dir.map(Utf8PlatformPathBuf::from),
target_obj_dir: self.target_obj_dir.map(Utf8PlatformPathBuf::from),
base_obj_dir: self.base_obj_dir.map(Utf8PlatformPathBuf::from),
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
build_base: self.build_base,
build_target: self.build_target,
rebuild_on_changes: self.rebuild_on_changes,
auto_update_check: self.auto_update_check,
watch_patterns: self.watch_patterns,
recent_projects: self.recent_projects,
diff_obj_config: self.diff_obj_config.into_config(),
..Default::default()
}
}
}
#[derive(serde::Deserialize, serde::Serialize)]
pub struct ScratchConfigV1 {
#[serde(default)]
@@ -58,7 +177,7 @@ pub struct ScratchConfigV1 {
#[serde(default)]
pub c_flags: Option<String>,
#[serde(default)]
pub ctx_path: Option<PathBuf>,
pub ctx_path: Option<String>,
#[serde(default)]
pub build_ctx: bool,
}
@@ -69,7 +188,7 @@ impl ScratchConfigV1 {
platform: self.platform,
compiler: self.compiler,
c_flags: self.c_flags,
ctx_path: self.ctx_path,
ctx_path: self.ctx_path.map(Utf8UnixPathBuf::from),
build_ctx: self.build_ctx.then_some(true),
preset_id: None,
}
@@ -79,8 +198,8 @@ impl ScratchConfigV1 {
#[derive(serde::Deserialize, serde::Serialize)]
pub struct ObjectConfigV1 {
pub name: String,
pub target_path: Option<PathBuf>,
pub base_path: Option<PathBuf>,
pub target_path: Option<String>,
pub base_path: Option<String>,
pub reverse_fn_order: Option<bool>,
pub complete: Option<bool>,
pub scratch: Option<ScratchConfigV1>,
@@ -91,12 +210,12 @@ impl ObjectConfigV1 {
fn into_config(self) -> ObjectConfig {
ObjectConfig {
name: self.name,
target_path: self.target_path,
base_path: self.base_path,
target_path: self.target_path.map(Utf8PlatformPathBuf::from),
base_path: self.base_path.map(Utf8PlatformPathBuf::from),
reverse_fn_order: self.reverse_fn_order,
complete: self.complete,
scratch: self.scratch.map(|scratch| scratch.into_config()),
source_path: self.source_path,
source_path: None,
..Default::default()
}
}
@@ -147,7 +266,11 @@ impl Default for DiffObjConfigV1 {
impl DiffObjConfigV1 {
fn into_config(self) -> DiffObjConfig {
DiffObjConfig {
relax_reloc_diffs: self.relax_reloc_diffs,
function_reloc_diffs: if self.relax_reloc_diffs {
FunctionRelocDiffs::None
} else {
FunctionRelocDiffs::default()
},
space_between_args: self.space_between_args,
combine_data_sections: self.combine_data_sections,
x86_formatter: self.x86_formatter,
@@ -178,11 +301,11 @@ pub struct AppConfigV1 {
#[serde(default)]
pub selected_wsl_distro: Option<String>,
#[serde(default)]
pub project_dir: Option<PathBuf>,
pub project_dir: Option<String>,
#[serde(default)]
pub target_obj_dir: Option<PathBuf>,
pub target_obj_dir: Option<String>,
#[serde(default)]
pub base_obj_dir: Option<PathBuf>,
pub base_obj_dir: Option<String>,
#[serde(default)]
pub selected_obj: Option<ObjectConfigV1>,
#[serde(default = "bool_true")]
@@ -196,7 +319,7 @@ pub struct AppConfigV1 {
#[serde(default)]
pub watch_patterns: Vec<Glob>,
#[serde(default)]
pub recent_projects: Vec<PathBuf>,
pub recent_projects: Vec<String>,
#[serde(default)]
pub diff_obj_config: DiffObjConfigV1,
}
@@ -208,9 +331,9 @@ impl AppConfigV1 {
custom_make: self.custom_make,
custom_args: self.custom_args,
selected_wsl_distro: self.selected_wsl_distro,
project_dir: self.project_dir,
target_obj_dir: self.target_obj_dir,
base_obj_dir: self.base_obj_dir,
project_dir: self.project_dir.map(Utf8PlatformPathBuf::from),
target_obj_dir: self.target_obj_dir.map(Utf8PlatformPathBuf::from),
base_obj_dir: self.base_obj_dir.map(Utf8PlatformPathBuf::from),
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
build_base: self.build_base,
build_target: self.build_target,
@@ -227,8 +350,8 @@ impl AppConfigV1 {
#[derive(serde::Deserialize, serde::Serialize)]
pub struct ObjectConfigV0 {
pub name: String,
pub target_path: PathBuf,
pub base_path: PathBuf,
pub target_path: String,
pub base_path: String,
pub reverse_fn_order: Option<bool>,
}
@@ -236,8 +359,8 @@ impl ObjectConfigV0 {
fn into_config(self) -> ObjectConfig {
ObjectConfig {
name: self.name,
target_path: Some(self.target_path),
base_path: Some(self.base_path),
target_path: Some(Utf8PlatformPathBuf::from(self.target_path)),
base_path: Some(Utf8PlatformPathBuf::from(self.base_path)),
reverse_fn_order: self.reverse_fn_order,
..Default::default()
}
@@ -248,9 +371,9 @@ impl ObjectConfigV0 {
pub struct AppConfigV0 {
pub custom_make: Option<String>,
pub selected_wsl_distro: Option<String>,
pub project_dir: Option<PathBuf>,
pub target_obj_dir: Option<PathBuf>,
pub base_obj_dir: Option<PathBuf>,
pub project_dir: Option<String>,
pub target_obj_dir: Option<String>,
pub base_obj_dir: Option<String>,
pub selected_obj: Option<ObjectConfigV0>,
pub build_target: bool,
pub auto_update_check: bool,
@@ -263,9 +386,9 @@ impl AppConfigV0 {
AppConfig {
custom_make: self.custom_make,
selected_wsl_distro: self.selected_wsl_distro,
project_dir: self.project_dir,
target_obj_dir: self.target_obj_dir,
base_obj_dir: self.base_obj_dir,
project_dir: self.project_dir.map(Utf8PlatformPathBuf::from),
target_obj_dir: self.target_obj_dir.map(Utf8PlatformPathBuf::from),
base_obj_dir: self.base_obj_dir.map(Utf8PlatformPathBuf::from),
selected_obj: self.selected_obj.map(|obj| obj.into_config()),
build_target: self.build_target,
auto_update_check: self.auto_update_check,

View File

@@ -1,8 +1,7 @@
use std::path::{Component, Path};
use anyhow::Result;
use globset::Glob;
use objdiff_core::config::{try_project_config, ProjectObject, DEFAULT_WATCH_PATTERNS};
use objdiff_core::config::{DEFAULT_WATCH_PATTERNS, try_project_config};
use typed_path::{Utf8UnixComponent, Utf8UnixPath};
use crate::app::{AppState, ObjectConfig};
@@ -47,32 +46,19 @@ fn find_dir<'a>(
unreachable!();
}
fn build_nodes(
units: &mut [ProjectObject],
project_dir: &Path,
target_obj_dir: Option<&Path>,
base_obj_dir: Option<&Path>,
) -> Vec<ProjectObjectNode> {
fn build_nodes(units: &mut [ObjectConfig]) -> Vec<ProjectObjectNode> {
let mut nodes = vec![];
for (idx, unit) in units.iter_mut().enumerate() {
unit.resolve_paths(project_dir, target_obj_dir, base_obj_dir);
let mut out_nodes = &mut nodes;
let path = if let Some(name) = &unit.name {
Path::new(name)
} else if let Some(path) = &unit.path {
path
} else {
continue;
};
let path = Utf8UnixPath::new(&unit.name);
if let Some(parent) = path.parent() {
for component in parent.components() {
if let Component::Normal(name) = component {
let name = name.to_str().unwrap();
if let Utf8UnixComponent::Normal(name) = component {
out_nodes = find_dir(name, out_nodes);
}
}
}
let filename = path.file_name().unwrap().to_str().unwrap().to_string();
let filename = path.file_name().unwrap().to_string();
out_nodes.push(ProjectObjectNode::Unit(filename, idx));
}
// Within the top-level module directories, join paths. Leave the
@@ -90,34 +76,52 @@ pub fn load_project_config(state: &mut AppState) -> Result<()> {
let Some(project_dir) = &state.config.project_dir else {
return Ok(());
};
if let Some((result, info)) = try_project_config(project_dir) {
if let Some((result, info)) = try_project_config(project_dir.as_ref()) {
let project_config = result?;
state.config.custom_make = project_config.custom_make.clone();
state.config.custom_args = project_config.custom_args.clone();
state.config.target_obj_dir =
project_config.target_dir.as_deref().map(|p| project_dir.join(p));
state.config.base_obj_dir = project_config.base_dir.as_deref().map(|p| project_dir.join(p));
state.config.target_obj_dir = project_config
.target_dir
.as_deref()
.map(|p| project_dir.join(p.with_platform_encoding()));
state.config.base_obj_dir = project_config
.base_dir
.as_deref()
.map(|p| project_dir.join(p.with_platform_encoding()));
state.config.build_base = project_config.build_base.unwrap_or(true);
state.config.build_target = project_config.build_target.unwrap_or(false);
state.config.watch_patterns = project_config.watch_patterns.clone().unwrap_or_else(|| {
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
});
if let Some(watch_patterns) = &project_config.watch_patterns {
state.config.watch_patterns = watch_patterns
.iter()
.map(|s| Glob::new(s))
.collect::<Result<Vec<Glob>, globset::Error>>()?;
} else {
state.config.watch_patterns =
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect();
}
state.watcher_change = true;
state.objects = project_config.units.clone().unwrap_or_default();
state.object_nodes = build_nodes(
&mut state.objects,
project_dir,
state.config.target_obj_dir.as_deref(),
state.config.base_obj_dir.as_deref(),
);
state.objects = project_config
.units
.as_deref()
.unwrap_or_default()
.iter()
.map(|o| {
ObjectConfig::new(
o,
project_dir,
state.config.target_obj_dir.as_deref(),
state.config.base_obj_dir.as_deref(),
)
})
.collect::<Vec<_>>();
state.object_nodes = build_nodes(&mut state.objects);
state.current_project_config = Some(project_config);
state.project_config_info = Some(info);
// Reload selected object
if let Some(selected_obj) = &state.config.selected_obj {
if let Some(obj) = state.objects.iter().find(|o| o.name() == selected_obj.name) {
let config = ObjectConfig::from(obj);
state.set_selected_obj(config);
if let Some(obj) = state.objects.iter().find(|o| o.name == selected_obj.name) {
state.set_selected_obj(obj.clone());
} else {
state.clear_selected_obj();
}

View File

@@ -94,9 +94,9 @@ pub fn load_font_if_needed(
// FIXME clean up
let default_font_ref = family.fonts.get(family.default_index).unwrap();
let default_font = family.handles.get(family.default_index).unwrap();
let default_font_data = load_font(default_font).unwrap();
let default_font_data = load_font(default_font)?;
log::info!("Loaded font family '{}'", family.family_name);
fonts.font_data.insert(default_font_ref.full_name(), default_font_data.font_data);
fonts.font_data.insert(default_font_ref.full_name(), Arc::new(default_font_data.font_data));
fonts
.families
.entry(egui::FontFamily::Name(Arc::from(family.family_name)))

View File

@@ -1,6 +1,5 @@
use egui::{
style::ScrollAnimation, text::LayoutJob, vec2, Align, Context, FontSelection, Key,
KeyboardShortcut, Modifiers, PointerButton, RichText, Ui, WidgetText,
Context, Key, KeyboardShortcut, Modifiers, PointerButton, style::ScrollAnimation, vec2,
};
fn any_widget_focused(ctx: &Context) -> bool { ctx.memory(|mem| mem.focused().is_some()) }
@@ -107,122 +106,3 @@ const CHANGE_BASE_SHORTCUT: KeyboardShortcut = KeyboardShortcut::new(Modifiers::
pub fn consume_change_base_shortcut(ctx: &Context) -> bool {
ctx.input_mut(|i| i.consume_shortcut(&CHANGE_BASE_SHORTCUT))
}
fn shortcut_key(text: &str) -> (usize, char, Key) {
let i = text.chars().position(|c| c == '_').expect("No underscore in text");
let key = text.chars().nth(i + 1).expect("No character after underscore");
let shortcut_key = match key {
'a' | 'A' => Key::A,
'b' | 'B' => Key::B,
'c' | 'C' => Key::C,
'd' | 'D' => Key::D,
'e' | 'E' => Key::E,
'f' | 'F' => Key::F,
'g' | 'G' => Key::G,
'h' | 'H' => Key::H,
'i' | 'I' => Key::I,
'j' | 'J' => Key::J,
'k' | 'K' => Key::K,
'l' | 'L' => Key::L,
'm' | 'M' => Key::M,
'n' | 'N' => Key::N,
'o' | 'O' => Key::O,
'p' | 'P' => Key::P,
'q' | 'Q' => Key::Q,
'r' | 'R' => Key::R,
's' | 'S' => Key::S,
't' | 'T' => Key::T,
'u' | 'U' => Key::U,
'v' | 'V' => Key::V,
'w' | 'W' => Key::W,
'x' | 'X' => Key::X,
'y' | 'Y' => Key::Y,
'z' | 'Z' => Key::Z,
_ => panic!("Invalid key {}", key),
};
(i, key, shortcut_key)
}
fn alt_text_ui(ui: &Ui, text: &str, i: usize, key: char, interactive: bool) -> WidgetText {
let color = if interactive {
ui.visuals().widgets.inactive.text_color()
} else {
ui.visuals().widgets.noninteractive.text_color()
};
let mut job = LayoutJob::default();
if i > 0 {
let text = &text[..i];
RichText::new(text).color(color).append_to(
&mut job,
ui.style(),
FontSelection::Default,
Align::Center,
);
}
let mut rt = RichText::new(key).color(color);
if ui.input(|i| i.modifiers.alt) {
rt = rt.underline();
}
rt.append_to(&mut job, ui.style(), FontSelection::Default, Align::Center);
if i < text.len() - 1 {
let text = &text[i + 2..];
RichText::new(text).color(color).append_to(
&mut job,
ui.style(),
FontSelection::Default,
Align::Center,
);
}
job.into()
}
pub fn button_alt_text(ui: &Ui, text: &str) -> WidgetText {
let (n, c, key) = shortcut_key(text);
let result = alt_text_ui(ui, text, n, c, true);
if ui.input_mut(|i| check_alt_key(i, c, key)) {
let btn_id = ui.next_auto_id();
ui.memory_mut(|m| m.request_focus(btn_id));
ui.input_mut(|i| {
i.events.push(egui::Event::Key {
key: Key::Enter,
physical_key: None,
pressed: true,
repeat: false,
modifiers: Default::default(),
})
});
}
result
}
pub fn alt_text(ui: &Ui, text: &str, enter: bool) -> WidgetText {
let (n, c, key) = shortcut_key(text);
let result = alt_text_ui(ui, text, n, c, false);
if ui.input_mut(|i| check_alt_key(i, c, key)) {
let btn_id = ui.next_auto_id();
ui.memory_mut(|m| m.request_focus(btn_id));
if enter {
ui.input_mut(|i| {
i.events.push(egui::Event::Key {
key: Key::Enter,
physical_key: None,
pressed: true,
repeat: false,
modifiers: Default::default(),
})
});
}
}
result
}
fn check_alt_key(i: &mut egui::InputState, c: char, key: Key) -> bool {
if i.consume_key(Modifiers::ALT, key) {
// Remove any text input events that match the key
let cs = c.to_string();
i.events.retain(|e| !matches!(e, egui::Event::Text(t) if *t == cs));
true
} else {
false
}
}

141
objdiff-gui/src/jobs.rs Normal file
View File

@@ -0,0 +1,141 @@
use std::{
sync::Arc,
task::{Wake, Waker},
};
use anyhow::{Result, bail};
use jobs::create_scratch;
use objdiff_core::{
build::BuildConfig,
diff::MappingConfig,
jobs,
jobs::{Job, JobQueue, check_update::CheckUpdateConfig, objdiff, update::UpdateConfig},
};
use crate::{
app::{AppConfig, AppState},
update::{BIN_NAME_NEW, BIN_NAME_OLD, build_updater},
};
struct EguiWaker(egui::Context);
impl Wake for EguiWaker {
fn wake(self: Arc<Self>) { self.0.request_repaint(); }
fn wake_by_ref(self: &Arc<Self>) { self.0.request_repaint(); }
}
pub fn egui_waker(ctx: &egui::Context) -> Waker { Waker::from(Arc::new(EguiWaker(ctx.clone()))) }
pub fn is_create_scratch_available(config: &AppConfig) -> bool {
let Some(selected_obj) = &config.selected_obj else {
return false;
};
selected_obj.target_path.is_some() && selected_obj.scratch.is_some()
}
pub fn start_create_scratch(
ctx: &egui::Context,
jobs: &mut JobQueue,
state: &AppState,
function_name: String,
) {
match create_scratch_config(state, function_name) {
Ok(config) => {
jobs.push_once(Job::CreateScratch, || {
create_scratch::start_create_scratch(egui_waker(ctx), config)
});
}
Err(err) => {
log::error!("Failed to create scratch config: {err}");
}
}
}
fn create_scratch_config(
state: &AppState,
function_name: String,
) -> Result<create_scratch::CreateScratchConfig> {
let Some(selected_obj) = &state.config.selected_obj else {
bail!("No object selected");
};
let Some(target_path) = &selected_obj.target_path else {
bail!("No target path for {}", selected_obj.name);
};
let Some(scratch_config) = &selected_obj.scratch else {
bail!("No scratch configuration for {}", selected_obj.name);
};
Ok(create_scratch::CreateScratchConfig {
build_config: BuildConfig::from(&state.config),
context_path: scratch_config.ctx_path.clone(),
build_context: scratch_config.build_ctx.unwrap_or(false),
compiler: scratch_config.compiler.clone().unwrap_or_default(),
platform: scratch_config.platform.clone().unwrap_or_default(),
compiler_flags: scratch_config.c_flags.clone().unwrap_or_default(),
function_name,
target_obj: target_path.clone(),
preset_id: scratch_config.preset_id,
})
}
impl From<&AppConfig> for BuildConfig {
fn from(config: &AppConfig) -> Self {
Self {
project_dir: config.project_dir.clone(),
custom_make: config.custom_make.clone(),
custom_args: config.custom_args.clone(),
selected_wsl_distro: config.selected_wsl_distro.clone(),
}
}
}
pub fn create_objdiff_config(state: &AppState) -> objdiff::ObjDiffConfig {
objdiff::ObjDiffConfig {
build_config: BuildConfig::from(&state.config),
build_base: state.config.build_base,
build_target: state.config.build_target,
target_path: state
.config
.selected_obj
.as_ref()
.and_then(|obj| obj.target_path.as_ref())
.cloned(),
base_path: state
.config
.selected_obj
.as_ref()
.and_then(|obj| obj.base_path.as_ref())
.cloned(),
diff_obj_config: state.config.diff_obj_config.clone(),
mapping_config: MappingConfig {
mappings: state
.config
.selected_obj
.as_ref()
.map(|obj| &obj.symbol_mappings)
.cloned()
.unwrap_or_default(),
selecting_left: state.selecting_left.clone(),
selecting_right: state.selecting_right.clone(),
},
}
}
pub fn start_build(ctx: &egui::Context, jobs: &mut JobQueue, config: objdiff::ObjDiffConfig) {
jobs.push_once(Job::ObjDiff, || objdiff::start_build(egui_waker(ctx), config));
}
pub fn start_check_update(ctx: &egui::Context, jobs: &mut JobQueue) {
jobs.push_once(Job::Update, || {
jobs::check_update::start_check_update(egui_waker(ctx), CheckUpdateConfig {
build_updater,
bin_names: vec![BIN_NAME_NEW.to_string(), BIN_NAME_OLD.to_string()],
})
});
}
pub fn start_update(ctx: &egui::Context, jobs: &mut JobQueue, bin_name: String) {
jobs.push_once(Job::Update, || {
jobs::update::start_update(egui_waker(ctx), UpdateConfig { build_updater, bin_name })
});
}

View File

@@ -1,39 +0,0 @@
use std::sync::mpsc::Receiver;
use anyhow::{Context, Result};
use self_update::{cargo_crate_version, update::Release};
use crate::{
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState},
update::{build_updater, BIN_NAME_NEW, BIN_NAME_OLD},
};
pub struct CheckUpdateResult {
pub update_available: bool,
pub latest_release: Release,
pub found_binary: Option<String>,
}
fn run_check_update(context: &JobContext, cancel: Receiver<()>) -> Result<Box<CheckUpdateResult>> {
update_status(context, "Fetching latest release".to_string(), 0, 1, &cancel)?;
let updater = build_updater().context("Failed to create release updater")?;
let latest_release = updater.get_latest_release()?;
let update_available =
self_update::version::bump_is_greater(cargo_crate_version!(), &latest_release.version)?;
// Find the binary name in the release assets
let found_binary = latest_release
.assets
.iter()
.find(|a| a.name == BIN_NAME_NEW)
.or_else(|| latest_release.assets.iter().find(|a| a.name == BIN_NAME_OLD))
.map(|a| a.name.clone());
update_status(context, "Complete".to_string(), 1, 1, &cancel)?;
Ok(Box::new(CheckUpdateResult { update_available, latest_release, found_binary }))
}
pub fn start_check_update(ctx: &egui::Context) -> JobState {
start_job(ctx, "Check for updates", Job::CheckUpdate, move |context, cancel| {
run_check_update(&context, cancel).map(|result| JobResult::CheckUpdate(Some(result)))
})
}

View File

@@ -1,328 +0,0 @@
use std::{
path::{Path, PathBuf},
process::Command,
sync::mpsc::Receiver,
};
use anyhow::{anyhow, Error, Result};
use objdiff_core::{
diff::{diff_objs, DiffObjConfig, MappingConfig, ObjDiff},
obj::{read, ObjInfo},
};
use time::OffsetDateTime;
use crate::{
app::{AppConfig, AppState, ObjectConfig},
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState},
};
pub struct BuildStatus {
pub success: bool,
pub cmdline: String,
pub stdout: String,
pub stderr: String,
}
impl Default for BuildStatus {
fn default() -> Self {
BuildStatus {
success: true,
cmdline: String::new(),
stdout: String::new(),
stderr: String::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct BuildConfig {
pub project_dir: Option<PathBuf>,
pub custom_make: Option<String>,
pub custom_args: Option<Vec<String>>,
#[allow(unused)]
pub selected_wsl_distro: Option<String>,
}
impl BuildConfig {
pub(crate) fn from_config(config: &AppConfig) -> Self {
Self {
project_dir: config.project_dir.clone(),
custom_make: config.custom_make.clone(),
custom_args: config.custom_args.clone(),
selected_wsl_distro: config.selected_wsl_distro.clone(),
}
}
}
pub struct ObjDiffConfig {
pub build_config: BuildConfig,
pub build_base: bool,
pub build_target: bool,
pub selected_obj: Option<ObjectConfig>,
pub diff_obj_config: DiffObjConfig,
pub selecting_left: Option<String>,
pub selecting_right: Option<String>,
}
impl ObjDiffConfig {
pub(crate) fn from_state(state: &AppState) -> Self {
Self {
build_config: BuildConfig::from_config(&state.config),
build_base: state.config.build_base,
build_target: state.config.build_target,
selected_obj: state.config.selected_obj.clone(),
diff_obj_config: state.config.diff_obj_config.clone(),
selecting_left: state.selecting_left.clone(),
selecting_right: state.selecting_right.clone(),
}
}
}
pub struct ObjDiffResult {
pub first_status: BuildStatus,
pub second_status: BuildStatus,
pub first_obj: Option<(ObjInfo, ObjDiff)>,
pub second_obj: Option<(ObjInfo, ObjDiff)>,
pub time: OffsetDateTime,
}
pub(crate) fn run_make(config: &BuildConfig, arg: &Path) -> BuildStatus {
let Some(cwd) = &config.project_dir else {
return BuildStatus {
success: false,
stderr: "Missing project dir".to_string(),
..Default::default()
};
};
let make = config.custom_make.as_deref().unwrap_or("make");
let make_args = config.custom_args.as_deref().unwrap_or(&[]);
#[cfg(not(windows))]
let mut command = {
let mut command = Command::new(make);
command.current_dir(cwd).args(make_args).arg(arg);
command
};
#[cfg(windows)]
let mut command = {
use std::os::windows::process::CommandExt;
use path_slash::PathExt;
let mut command = if config.selected_wsl_distro.is_some() {
Command::new("wsl")
} else {
Command::new(make)
};
if let Some(distro) = &config.selected_wsl_distro {
// Strip distro root prefix \\wsl.localhost\{distro}
let wsl_path_prefix = format!("\\\\wsl.localhost\\{}", distro);
let cwd = match cwd.strip_prefix(wsl_path_prefix) {
Ok(new_cwd) => format!("/{}", new_cwd.to_slash_lossy().as_ref()),
Err(_) => cwd.to_string_lossy().to_string(),
};
command
.arg("--cd")
.arg(cwd)
.arg("-d")
.arg(distro)
.arg("--")
.arg(make)
.args(make_args)
.arg(arg.to_slash_lossy().as_ref());
} else {
command.current_dir(cwd).args(make_args).arg(arg.to_slash_lossy().as_ref());
}
command.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW);
command
};
let mut cmdline = shell_escape::escape(command.get_program().to_string_lossy()).into_owned();
for arg in command.get_args() {
cmdline.push(' ');
cmdline.push_str(shell_escape::escape(arg.to_string_lossy()).as_ref());
}
let output = match command.output() {
Ok(output) => output,
Err(e) => {
return BuildStatus {
success: false,
cmdline,
stdout: Default::default(),
stderr: e.to_string(),
};
}
};
// Try from_utf8 first to avoid copying the buffer if it's valid, then fall back to from_utf8_lossy
let stdout = String::from_utf8(output.stdout)
.unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned());
let stderr = String::from_utf8(output.stderr)
.unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned());
BuildStatus { success: output.status.success(), cmdline, stdout, stderr }
}
fn run_build(
context: &JobContext,
cancel: Receiver<()>,
mut config: ObjDiffConfig,
) -> Result<Box<ObjDiffResult>> {
let obj_config = config.selected_obj.ok_or_else(|| Error::msg("Missing obj path"))?;
// Use the per-object symbol mappings, we don't set mappings globally
config.diff_obj_config.symbol_mappings = MappingConfig {
mappings: obj_config.symbol_mappings,
selecting_left: config.selecting_left,
selecting_right: config.selecting_right,
};
let project_dir = config
.build_config
.project_dir
.as_ref()
.ok_or_else(|| Error::msg("Missing project dir"))?;
let target_path_rel = if let Some(target_path) = &obj_config.target_path {
Some(target_path.strip_prefix(project_dir).map_err(|_| {
anyhow!(
"Target path '{}' doesn't begin with '{}'",
target_path.display(),
project_dir.display()
)
})?)
} else {
None
};
let base_path_rel = if let Some(base_path) = &obj_config.base_path {
Some(base_path.strip_prefix(project_dir).map_err(|_| {
anyhow!(
"Base path '{}' doesn't begin with '{}'",
base_path.display(),
project_dir.display()
)
})?)
} else {
None
};
let mut total = 1;
if config.build_target && target_path_rel.is_some() {
total += 1;
}
if config.build_base && base_path_rel.is_some() {
total += 1;
}
if target_path_rel.is_some() {
total += 1;
}
if base_path_rel.is_some() {
total += 1;
}
let mut step_idx = 0;
let mut first_status = match target_path_rel {
Some(target_path_rel) if config.build_target => {
update_status(
context,
format!("Building target {}", target_path_rel.display()),
step_idx,
total,
&cancel,
)?;
step_idx += 1;
run_make(&config.build_config, target_path_rel)
}
_ => BuildStatus::default(),
};
let mut second_status = match base_path_rel {
Some(base_path_rel) if config.build_base => {
update_status(
context,
format!("Building base {}", base_path_rel.display()),
step_idx,
total,
&cancel,
)?;
step_idx += 1;
run_make(&config.build_config, base_path_rel)
}
_ => BuildStatus::default(),
};
let time = OffsetDateTime::now_utc();
let first_obj = match &obj_config.target_path {
Some(target_path) if first_status.success => {
update_status(
context,
format!("Loading target {}", target_path_rel.unwrap().display()),
step_idx,
total,
&cancel,
)?;
step_idx += 1;
match read::read(target_path, &config.diff_obj_config) {
Ok(obj) => Some(obj),
Err(e) => {
first_status = BuildStatus {
success: false,
stdout: format!("Loading object '{}'", target_path.display()),
stderr: format!("{:#}", e),
..Default::default()
};
None
}
}
}
Some(_) => {
step_idx += 1;
None
}
_ => None,
};
let second_obj = match &obj_config.base_path {
Some(base_path) if second_status.success => {
update_status(
context,
format!("Loading base {}", base_path_rel.unwrap().display()),
step_idx,
total,
&cancel,
)?;
step_idx += 1;
match read::read(base_path, &config.diff_obj_config) {
Ok(obj) => Some(obj),
Err(e) => {
second_status = BuildStatus {
success: false,
stdout: format!("Loading object '{}'", base_path.display()),
stderr: format!("{:#}", e),
..Default::default()
};
None
}
}
}
Some(_) => {
step_idx += 1;
None
}
_ => None,
};
update_status(context, "Performing diff".to_string(), step_idx, total, &cancel)?;
step_idx += 1;
let result = diff_objs(&config.diff_obj_config, first_obj.as_ref(), second_obj.as_ref(), None)?;
update_status(context, "Complete".to_string(), step_idx, total, &cancel)?;
Ok(Box::new(ObjDiffResult {
first_status,
second_status,
first_obj: first_obj.and_then(|o| result.left.map(|d| (o, d))),
second_obj: second_obj.and_then(|o| result.right.map(|d| (o, d))),
time,
}))
}
pub fn start_build(ctx: &egui::Context, config: ObjDiffConfig) -> JobState {
start_job(ctx, "Build", Job::ObjDiff, move |context, cancel| {
run_build(&context, cancel, config).map(|result| JobResult::ObjDiff(Some(result)))
})
}

View File

@@ -1,3 +1,4 @@
#![allow(clippy::too_many_arguments)]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
mod app;
@@ -16,16 +17,15 @@ use std::{
sync::{Arc, Mutex},
};
use anyhow::{ensure, Result};
use anyhow::{Result, ensure};
use cfg_if::cfg_if;
use time::UtcOffset;
use tracing_subscriber::EnvFilter;
use crate::views::graphics::{load_graphics_config, GraphicsBackend, GraphicsConfig};
use crate::views::graphics::{GraphicsBackend, GraphicsConfig, load_graphics_config};
fn load_icon() -> Result<egui::IconData> {
use bytes::Buf;
let decoder = png::Decoder::new(include_bytes!("../assets/icon_64.png").reader());
let decoder = png::Decoder::new(include_bytes!("../assets/icon_64.png").as_ref());
let mut reader = decoder.read_info()?;
let mut buf = vec![0; reader.output_buffer_size()];
let info = reader.next_frame(&mut buf)?;
@@ -37,8 +37,6 @@ fn load_icon() -> Result<egui::IconData> {
const APP_NAME: &str = "objdiff";
// When compiling natively:
#[cfg(not(target_arch = "wasm32"))]
fn main() -> ExitCode {
// Log to stdout (if you run with `RUST_LOG=debug`).
tracing_subscriber::fmt()
@@ -88,14 +86,21 @@ fn main() -> ExitCode {
}
#[cfg(feature = "wgpu")]
{
use eframe::egui_wgpu::wgpu::Backends;
use eframe::egui_wgpu::{WgpuSetup, wgpu};
if graphics_config.desired_backend.is_supported() {
native_options.wgpu_options.supported_backends = match graphics_config.desired_backend {
GraphicsBackend::Auto => native_options.wgpu_options.supported_backends,
GraphicsBackend::Dx12 => Backends::DX12,
GraphicsBackend::Metal => Backends::METAL,
GraphicsBackend::Vulkan => Backends::VULKAN,
GraphicsBackend::OpenGL => Backends::GL,
native_options.wgpu_options.wgpu_setup = match native_options.wgpu_options.wgpu_setup {
WgpuSetup::CreateNew(mut setup) => {
setup.instance_descriptor.backends = match graphics_config.desired_backend {
GraphicsBackend::Auto => setup.instance_descriptor.backends,
GraphicsBackend::Dx12 => wgpu::Backends::DX12,
GraphicsBackend::Metal => wgpu::Backends::METAL,
GraphicsBackend::Vulkan => wgpu::Backends::VULKAN,
GraphicsBackend::OpenGL => wgpu::Backends::GL,
};
WgpuSetup::CreateNew(setup)
}
// WgpuConfiguration::Default is CreateNew until we call run_eframe()
_ => unreachable!(),
};
}
}
@@ -112,6 +117,8 @@ fn main() -> ExitCode {
}
#[cfg(feature = "wgpu")]
if let Some(e) = eframe_error {
use eframe::egui_wgpu::WgpuConfiguration;
// Attempt to relaunch using wgpu auto backend if the desired backend failed
#[allow(unused_mut)]
let mut should_relaunch = graphics_config.desired_backend != GraphicsBackend::Auto;
@@ -123,7 +130,7 @@ fn main() -> ExitCode {
if should_relaunch {
log::warn!("Failed to launch application: {e:?}");
log::warn!("Attempting to relaunch using auto-detected backend");
native_options.wgpu_options.supported_backends = Default::default();
native_options.wgpu_options.wgpu_setup = WgpuConfiguration::default().wgpu_setup;
if let Err(e) = run_eframe(
native_options.clone(),
utc_offset,
@@ -212,21 +219,3 @@ fn run_eframe(
}),
)
}
// when compiling to web using trunk.
#[cfg(target_arch = "wasm32")]
fn main() {
// Make sure panics are logged using `console.error`.
console_error_panic_hook::set_once();
// Redirect tracing to console.log and friends:
tracing_wasm::set_as_global_default();
let web_options = eframe::WebOptions::default();
eframe::start_web(
"the_canvas_id", // hardcode it
web_options,
Box::new(|cc| Box::new(eframe_template::TemplateApp::new(cc))),
)
.expect("failed to start eframe");
}

View File

@@ -1,5 +1,7 @@
use anyhow::Result;
use cfg_if::cfg_if;
use const_format::formatcp;
use objdiff_core::jobs::update::self_update;
use self_update::{cargo_crate_version, update::ReleaseUpdate};
pub const OS: &str = std::env::consts::OS;
@@ -26,8 +28,8 @@ pub const BIN_NAME_OLD: &str = formatcp!("objdiff-{}-{}{}", OS, ARCH, std::env::
pub const RELEASE_URL: &str =
formatcp!("https://github.com/{}/{}/releases/latest", GITHUB_USER, GITHUB_REPO);
pub fn build_updater() -> self_update::errors::Result<Box<dyn ReleaseUpdate>> {
self_update::backends::github::Update::configure()
pub fn build_updater() -> Result<Box<dyn ReleaseUpdate>> {
Ok(self_update::backends::github::Update::configure()
.repo_owner(GITHUB_USER)
.repo_name(GITHUB_REPO)
// bin_name is required, but unused?
@@ -35,5 +37,5 @@ pub fn build_updater() -> self_update::errors::Result<Box<dyn ReleaseUpdate>> {
.no_confirm(true)
.show_output(false)
.current_version(cargo_crate_version!())
.build()
.build()?)
}

View File

@@ -1,6 +1,6 @@
use std::sync::Arc;
use egui::{text::LayoutJob, Color32, FontFamily, FontId, TextFormat, TextStyle, Widget};
use egui::{Color32, FontFamily, FontId, TextFormat, TextStyle, Widget, text::LayoutJob};
use time::UtcOffset;
use crate::fonts::load_font_if_needed;

View File

@@ -1,32 +1,29 @@
#[cfg(all(windows, feature = "wsl"))]
use std::string::FromUtf16Error;
use std::{
mem::take,
path::{Path, PathBuf, MAIN_SEPARATOR},
};
use std::{mem::take, path::MAIN_SEPARATOR};
#[cfg(all(windows, feature = "wsl"))]
use anyhow::{Context, Result};
use egui::{
output::OpenUrl, text::LayoutJob, CollapsingHeader, FontFamily, FontId, RichText,
SelectableLabel, TextFormat, Widget,
CollapsingHeader, FontFamily, FontId, RichText, SelectableLabel, TextFormat, Widget,
output::OpenUrl, text::LayoutJob,
};
use globset::Glob;
use objdiff_core::{
config::{ProjectObject, DEFAULT_WATCH_PATTERNS},
diff::{ArmArchVersion, ArmR9Usage, MipsAbi, MipsInstrCategory, X86Formatter},
config::{DEFAULT_WATCH_PATTERNS, path::check_path_buf},
diff::{
CONFIG_GROUPS, ConfigEnum, ConfigEnumVariantInfo, ConfigPropertyId, ConfigPropertyKind,
ConfigPropertyValue,
},
jobs::{Job, JobQueue, JobResult, check_update::CheckUpdateResult},
};
use strum::{EnumMessage, VariantArray};
use typed_path::Utf8PlatformPathBuf;
use crate::{
app::{AppConfig, AppState, AppStateRef, ObjectConfig},
config::ProjectObjectNode,
hotkeys,
jobs::{
check_update::{start_check_update, CheckUpdateResult},
update::start_update,
Job, JobQueue, JobResult,
},
jobs::{start_check_update, start_update},
update::RELEASE_URL,
views::{
appearance::Appearance,
@@ -90,7 +87,7 @@ impl ConfigViewState {
if let Ok(obj_path) = path.strip_prefix(base_dir) {
let target_path = target_dir.join(obj_path);
guard.set_selected_obj(ObjectConfig {
name: obj_path.display().to_string(),
name: obj_path.to_string(),
target_path: Some(target_path),
base_path: Some(path),
..Default::default()
@@ -98,7 +95,7 @@ impl ConfigViewState {
} else if let Ok(obj_path) = path.strip_prefix(target_dir) {
let base_path = base_dir.join(obj_path);
guard.set_selected_obj(ObjectConfig {
name: obj_path.display().to_string(),
name: obj_path.to_string(),
target_path: Some(path),
base_path: Some(base_path),
..Default::default()
@@ -119,11 +116,11 @@ impl ConfigViewState {
if self.queue_check_update {
self.queue_check_update = false;
jobs.push_once(Job::CheckUpdate, || start_check_update(ctx));
start_check_update(ctx, jobs);
}
if let Some(bin_name) = self.queue_update.take() {
jobs.push_once(Job::Update, || start_update(ctx, bin_name));
start_update(ctx, jobs, bin_name);
}
}
}
@@ -170,10 +167,7 @@ pub fn config_ui(
) {
let mut state_guard = state.write().unwrap();
let AppState {
config:
AppConfig {
project_dir, target_obj_dir, base_obj_dir, selected_obj, auto_update_check, ..
},
config: AppConfig { target_obj_dir, base_obj_dir, selected_obj, auto_update_check, .. },
objects,
object_nodes,
..
@@ -207,10 +201,7 @@ pub fn config_ui(
.on_hover_text_at_pointer("Open a link to the latest release on GitHub")
.clicked()
{
ui.output_mut(|output| {
output.open_url =
Some(OpenUrl { url: RELEASE_URL.to_string(), new_tab: true })
});
ui.ctx().open_url(OpenUrl { url: RELEASE_URL.to_string(), new_tab: true });
}
});
}
@@ -219,14 +210,14 @@ pub fn config_ui(
ui.horizontal(|ui| {
ui.heading("Project");
if ui.button("Settings").clicked() {
if ui.button(RichText::new("Settings")).clicked() {
*show_config_window = true;
}
});
let selected_index = selected_obj.as_ref().and_then(|selected_obj| {
objects.iter().position(|obj| obj.name.as_ref() == Some(&selected_obj.name))
});
let selected_index = selected_obj
.as_ref()
.and_then(|selected_obj| objects.iter().position(|obj| obj.name == selected_obj.name));
let mut new_selected_index = selected_index;
if objects.is_empty() {
if let (Some(_base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) {
@@ -255,9 +246,8 @@ pub fn config_ui(
}
} else {
let had_search = !config_state.object_search.is_empty();
let response = egui::TextEdit::singleline(&mut config_state.object_search)
.hint_text(hotkeys::alt_text(ui, "Filter _objects", false))
.ui(ui);
let response =
egui::TextEdit::singleline(&mut config_state.object_search).hint_text("Filter").ui(ui);
if hotkeys::consume_object_filter_shortcut(ui.ctx()) {
response.request_focus();
}
@@ -326,22 +316,14 @@ pub fn config_ui(
config_state.show_hidden,
)
}) {
display_node(
ui,
&mut new_selected_index,
project_dir.as_deref(),
objects,
&node,
appearance,
node_open,
);
display_node(ui, &mut new_selected_index, objects, &node, appearance, node_open);
}
});
}
if new_selected_index != selected_index {
if let Some(idx) = new_selected_index {
// Will set obj_changed, which will trigger a rebuild
let config = ObjectConfig::from(&objects[idx]);
let config = objects[idx].clone();
state_guard.set_selected_obj(config);
}
}
@@ -355,9 +337,8 @@ pub fn config_ui(
fn display_unit(
ui: &mut egui::Ui,
selected_obj: &mut Option<usize>,
project_dir: Option<&Path>,
name: &str,
units: &[ProjectObject],
units: &[ObjectConfig],
index: usize,
appearance: &Appearance,
) {
@@ -365,12 +346,8 @@ fn display_unit(
let selected = *selected_obj == Some(index);
let color = if selected {
appearance.emphasized_text_color
} else if let Some(complete) = object.complete() {
if complete {
appearance.insert_color
} else {
appearance.delete_color
}
} else if let Some(complete) = object.complete {
if complete { appearance.insert_color } else { appearance.delete_color }
} else {
appearance.text_color
};
@@ -384,27 +361,23 @@ fn display_unit(
.color(color),
)
.ui(ui);
if get_source_path(project_dir, object).is_some() {
response.context_menu(|ui| object_context_ui(ui, object, project_dir));
if object.source_path.is_some() {
response.context_menu(|ui| object_context_ui(ui, object));
}
if response.clicked() {
*selected_obj = Some(index);
}
}
fn get_source_path(project_dir: Option<&Path>, object: &ProjectObject) -> Option<PathBuf> {
project_dir.and_then(|dir| object.source_path().map(|path| dir.join(path)))
}
fn object_context_ui(ui: &mut egui::Ui, object: &ProjectObject, project_dir: Option<&Path>) {
if let Some(source_path) = get_source_path(project_dir, object) {
fn object_context_ui(ui: &mut egui::Ui, object: &ObjectConfig) {
if let Some(source_path) = &object.source_path {
if ui
.button("Open source file")
.on_hover_text("Open the source file in the default editor")
.clicked()
{
log::info!("Opening file {}", source_path.display());
if let Err(e) = open::that_detached(&source_path) {
log::info!("Opening file {}", source_path);
if let Err(e) = open::that_detached(source_path.as_str()) {
log::error!("Failed to open source file: {e}");
}
ui.close_menu();
@@ -424,15 +397,14 @@ enum NodeOpen {
fn display_node(
ui: &mut egui::Ui,
selected_obj: &mut Option<usize>,
project_dir: Option<&Path>,
units: &[ProjectObject],
units: &[ObjectConfig],
node: &ProjectObjectNode,
appearance: &Appearance,
node_open: NodeOpen,
) {
match node {
ProjectObjectNode::Unit(name, idx) => {
display_unit(ui, selected_obj, project_dir, name, units, *idx, appearance);
display_unit(ui, selected_obj, name, units, *idx, appearance);
}
ProjectObjectNode::Dir(name, children) => {
let contains_obj = selected_obj.map(|idx| contains_node(node, idx));
@@ -458,7 +430,7 @@ fn display_node(
.open(open)
.show(ui, |ui| {
for node in children {
display_node(ui, selected_obj, project_dir, units, node, appearance, node_open);
display_node(ui, selected_obj, units, node, appearance, node_open);
}
});
}
@@ -475,7 +447,7 @@ fn contains_node(node: &ProjectObjectNode, selected_obj: usize) -> bool {
}
fn filter_node(
units: &[ProjectObject],
units: &[ObjectConfig],
node: &ProjectObjectNode,
search: &str,
filter_diffable: bool,
@@ -487,8 +459,8 @@ fn filter_node(
let unit = &units[*idx];
if (search.is_empty() || name.to_ascii_lowercase().contains(search))
&& (!filter_diffable || (unit.base_path.is_some() && unit.target_path.is_some()))
&& (!filter_incomplete || matches!(unit.complete(), None | Some(false)))
&& (show_hidden || !unit.hidden())
&& (!filter_incomplete || matches!(unit.complete, None | Some(false)))
&& (show_hidden || !unit.hidden)
{
Some(node.clone())
} else {
@@ -526,13 +498,16 @@ fn subheading(ui: &mut egui::Ui, text: &str, appearance: &Appearance) {
);
}
fn format_path(path: &Option<PathBuf>, appearance: &Appearance) -> RichText {
fn format_path(path: &Option<Utf8PlatformPathBuf>, appearance: &Appearance) -> RichText {
let mut color = appearance.replace_color;
let text = if let Some(dir) = path {
if let Some(rel) = dirs::home_dir().and_then(|home| dir.strip_prefix(&home).ok()) {
format!("~{}{}", MAIN_SEPARATOR, rel.display())
if let Some(rel) = dirs::home_dir()
.and_then(|home| check_path_buf(home).ok())
.and_then(|home| dir.strip_prefix(&home).ok())
{
format!("~{}{}", MAIN_SEPARATOR, rel)
} else {
format!("{}", dir.display())
dir.to_string()
}
} else {
color = appearance.delete_color;
@@ -546,7 +521,7 @@ pub const CONFIG_DISABLED_TEXT: &str =
fn pick_folder_ui(
ui: &mut egui::Ui,
dir: &Option<PathBuf>,
dir: &Option<Utf8PlatformPathBuf>,
label: &str,
tooltip: impl FnOnce(&mut egui::Ui),
appearance: &Appearance,
@@ -878,121 +853,102 @@ pub fn arch_config_window(
});
}
fn config_property_ui(
ui: &mut egui::Ui,
state: &mut AppState,
property_id: ConfigPropertyId,
) -> bool {
let mut changed = false;
let current_value = state.config.diff_obj_config.get_property_value(property_id);
match (property_id.kind(), current_value) {
(ConfigPropertyKind::Boolean, ConfigPropertyValue::Boolean(mut checked)) => {
let mut response = ui.checkbox(&mut checked, property_id.name());
if let Some(description) = property_id.description() {
response = response.on_hover_text(description);
}
if response.changed() {
state
.config
.diff_obj_config
.set_property_value(property_id, ConfigPropertyValue::Boolean(checked))
.expect("Failed to set property value");
changed = true;
}
}
(ConfigPropertyKind::Choice(variants), ConfigPropertyValue::Choice(selected)) => {
fn variant_name(variant: &ConfigEnumVariantInfo) -> String {
if variant.is_default {
format!("{} (default)", variant.name)
} else {
variant.name.to_string()
}
}
let selected_variant = variants
.iter()
.find(|v| v.value == selected)
.or_else(|| variants.iter().find(|v| v.is_default))
.expect("Invalid choice variant");
let response = egui::ComboBox::new(property_id.name(), property_id.name())
.selected_text(variant_name(selected_variant))
.show_ui(ui, |ui| {
for variant in variants {
let mut response =
ui.selectable_label(selected == variant.value, variant_name(variant));
if let Some(description) = variant.description {
response = response.on_hover_text(description);
}
if response.clicked() {
state
.config
.diff_obj_config
.set_property_value(
property_id,
ConfigPropertyValue::Choice(variant.value),
)
.expect("Failed to set property value");
changed = true;
}
}
})
.response;
if let Some(description) = property_id.description() {
response.on_hover_text(description);
}
}
_ => panic!("Incompatible property kind and value"),
}
changed
}
fn arch_config_ui(ui: &mut egui::Ui, state: &mut AppState, _appearance: &Appearance) {
ui.heading("x86");
egui::ComboBox::new("x86_formatter", "Format")
.selected_text(state.config.diff_obj_config.x86_formatter.get_message().unwrap())
.show_ui(ui, |ui| {
for &formatter in X86Formatter::VARIANTS {
if ui
.selectable_label(
state.config.diff_obj_config.x86_formatter == formatter,
formatter.get_message().unwrap(),
)
.clicked()
{
state.config.diff_obj_config.x86_formatter = formatter;
state.queue_reload = true;
}
}
});
ui.separator();
ui.heading("MIPS");
egui::ComboBox::new("mips_abi", "ABI")
.selected_text(state.config.diff_obj_config.mips_abi.get_message().unwrap())
.show_ui(ui, |ui| {
for &abi in MipsAbi::VARIANTS {
if ui
.selectable_label(
state.config.diff_obj_config.mips_abi == abi,
abi.get_message().unwrap(),
)
.clicked()
{
state.config.diff_obj_config.mips_abi = abi;
state.queue_reload = true;
}
}
});
egui::ComboBox::new("mips_instr_category", "Instruction Category")
.selected_text(state.config.diff_obj_config.mips_instr_category.get_message().unwrap())
.show_ui(ui, |ui| {
for &category in MipsInstrCategory::VARIANTS {
if ui
.selectable_label(
state.config.diff_obj_config.mips_instr_category == category,
category.get_message().unwrap(),
)
.clicked()
{
state.config.diff_obj_config.mips_instr_category = category;
state.queue_reload = true;
}
}
});
ui.separator();
ui.heading("ARM");
egui::ComboBox::new("arm_arch_version", "Architecture Version")
.selected_text(state.config.diff_obj_config.arm_arch_version.get_message().unwrap())
.show_ui(ui, |ui| {
for &version in ArmArchVersion::VARIANTS {
if ui
.selectable_label(
state.config.diff_obj_config.arm_arch_version == version,
version.get_message().unwrap(),
)
.clicked()
{
state.config.diff_obj_config.arm_arch_version = version;
state.queue_reload = true;
}
}
});
let response = ui
.checkbox(&mut state.config.diff_obj_config.arm_unified_syntax, "Unified syntax")
.on_hover_text("Disassemble as unified assembly language (UAL).");
if response.changed() {
state.queue_reload = true;
let mut first = true;
let mut changed = false;
for group in CONFIG_GROUPS {
if group.id == "general" {
continue;
}
if first {
first = false;
} else {
ui.separator();
}
ui.heading(group.name);
for property_id in group.properties.iter().cloned() {
changed |= config_property_ui(ui, state, property_id);
}
}
let response = ui
.checkbox(&mut state.config.diff_obj_config.arm_av_registers, "Use A/V registers")
.on_hover_text("Display R0-R3 as A1-A4 and R4-R11 as V1-V8");
if response.changed() {
state.queue_reload = true;
}
egui::ComboBox::new("arm_r9_usage", "Display R9 as")
.selected_text(state.config.diff_obj_config.arm_r9_usage.get_message().unwrap())
.show_ui(ui, |ui| {
for &usage in ArmR9Usage::VARIANTS {
if ui
.selectable_label(
state.config.diff_obj_config.arm_r9_usage == usage,
usage.get_message().unwrap(),
)
.on_hover_text(usage.get_detailed_message().unwrap())
.clicked()
{
state.config.diff_obj_config.arm_r9_usage = usage;
state.queue_reload = true;
}
}
});
let response = ui
.checkbox(&mut state.config.diff_obj_config.arm_sl_usage, "Display R10 as SL")
.on_hover_text("Used for explicit stack limits.");
if response.changed() {
state.queue_reload = true;
}
let response = ui
.checkbox(&mut state.config.diff_obj_config.arm_fp_usage, "Display R11 as FP")
.on_hover_text("Used for frame pointers.");
if response.changed() {
state.queue_reload = true;
}
let response = ui
.checkbox(&mut state.config.diff_obj_config.arm_ip_usage, "Display R12 as IP")
.on_hover_text("Used for interworking and long branches.");
if response.changed() {
if changed {
state.queue_reload = true;
}
}
pub fn general_config_ui(ui: &mut egui::Ui, state: &mut AppState) {
let mut changed = false;
let group = CONFIG_GROUPS.iter().find(|group| group.id == "general").unwrap();
for property_id in group.properties.iter().cloned() {
changed |= config_property_ui(ui, state, property_id);
}
if changed {
state.queue_reload = true;
}
}

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