Compare commits

...

39 Commits

Author SHA1 Message Date
LagoLunatic
f2a591356e Improve automatic symbol pairing for nameless literals (#247)
* Improve automatic symbol pairing for nameless literals

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

* Only pair up literals that match perfectly

* Clippy

* Do two separate passes when pairing up literals

* Fix partially-matching literal pairups not working right

* Remove duplicate $ splitting code

* Implement $ splitting for section names too

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

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

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

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

Resolves #229
2025-08-02 10:56:26 -06:00
1205e8ceb4 Version v3.0.0-beta.12 2025-07-29 21:31:12 -06:00
c917cad5f0 Strip zeros from end of inferred function sizes
Resolves #3
2025-07-29 21:20:47 -06:00
dd653329f5 Fix reading IMAGE_REL_PPC_REFHI/REFLO without PAIR 2025-07-29 20:58:34 -06:00
f5d3d5f10a gui: Split OpenGL into OpenGL (glow), OpenGL ES (wgpu) 2025-07-29 20:49:09 -06:00
c327ed3ea8 Update to egui 0.32 (& update all deps) 2025-07-28 17:30:52 -06:00
57 changed files with 2006 additions and 2330 deletions

View File

@@ -36,9 +36,9 @@ jobs:
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Cargo check
run: cargo check --all-targets --all-features
run: cargo check --all-targets --all-features --workspace
- name: Cargo clippy
run: cargo clippy --all-targets --all-features
run: cargo clippy --all-targets --all-features --workspace
fmt:
name: Format
@@ -92,7 +92,7 @@ jobs:
- name: Cache Rust workspace
uses: Swatinem/rust-cache@v2
- name: Cargo test
run: cargo test --release --features all
run: cargo test --release --all-features --workspace
build-cli:
name: Build objdiff-cli
@@ -146,13 +146,14 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install uv
if: matrix.build == 'zigbuild'
uses: astral-sh/setup-uv@v6
- 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
uv tool install cargo-zigbuild==0.20.1 --with-executables-from ziglang==0.15.1
echo "CARGO_ZIGBUILD_ZIG_PATH=$(uv tool dir)/cargo-zigbuild/bin/python-zig" >> $GITHUB_ENV
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
@@ -213,13 +214,14 @@ jobs:
sudo apt-get -y install ${{ matrix.packages }}
- name: Checkout
uses: actions/checkout@v4
- name: Install uv
if: matrix.build == 'zigbuild'
uses: astral-sh/setup-uv@v6
- 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
uv tool install cargo-zigbuild==0.20.1 --with-executables-from ziglang==0.15.1
echo "CARGO_ZIGBUILD_ZIG_PATH=$(uv tool dir)/cargo-zigbuild/bin/python-zig" >> $GITHUB_ENV
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:

View File

@@ -24,7 +24,7 @@ repos:
description: Run cargo clippy on all project files.
language: system
entry: cargo
args: ["+nightly", "clippy", "--all-targets", "--all-features"]
args: ["+nightly", "clippy", "--all-targets", "--all-features", "--workspace"]
pass_filenames: false
- id: cargo-deny
name: cargo deny

624
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,18 +5,28 @@ members = [
"objdiff-gui",
"objdiff-wasm",
]
default-members = [
"objdiff-cli",
"objdiff-core",
"objdiff-gui",
# Exclude objdiff-wasm by default
]
resolver = "3"
[workspace.package]
version = "3.0.1"
authors = ["Luke Street <luke@street.dev>"]
edition = "2024"
license = "MIT OR Apache-2.0"
repository = "https://github.com/encounter/objdiff"
rust-version = "1.88"
[profile.release-lto]
inherits = "release"
lto = "fat"
strip = "debuginfo"
codegen-units = 1
[workspace.package]
version = "3.0.0-beta.11"
authors = ["Luke Street <luke@street.dev>"]
edition = "2024"
license = "MIT OR Apache-2.0"
repository = "https://github.com/encounter/objdiff"
rust-version = "1.85"
[profile.release-min]
inherits = "release-lto"
opt-level = "z"

View File

@@ -90,22 +90,31 @@ file as well. You can then add `objdiff.json` to your `.gitignore` to prevent it
"build_base": true,
"watch_patterns": [
"*.c",
"*.cc",
"*.cp",
"*.cpp",
"*.cxx",
"*.c++",
"*.h",
"*.hh",
"*.hp",
"*.hpp",
"*.hxx",
"*.h++",
"*.pch",
"*.pch++",
"*.inc",
"*.s",
"*.S",
"*.asm",
"*.inc",
"*.py",
"*.yml",
"*.txt",
"*.json"
],
"ignore_patterns": [
"build/**/*"
],
"units": [
{
"name": "main/MetroTRK/mslsupp",
@@ -141,6 +150,10 @@ It's unlikely you'll want to disable this, unless you're using an external tool
If any of these files change, objdiff will automatically rebuild the objects and re-compare them.
If not specified, objdiff will use the default patterns listed above.
`ignore_patterns` _(optional)_: A list of glob patterns to explicitly ignore when watching for changes.
([Supported syntax](https://docs.rs/globset/latest/globset/#syntax))
If not specified, objdiff will use the default patterns listed above.
`units` _(optional)_: If specified, objdiff will display a list of objects in the sidebar for easy navigation.
> `name` _(optional)_: The name of the object in the UI. If not specified, the object's `path` will be used.

View File

@@ -57,23 +57,39 @@
},
"default": [
"*.c",
"*.cc",
"*.cp",
"*.cpp",
"*.cxx",
"*.c++",
"*.h",
"*.hh",
"*.hp",
"*.hpp",
"*.hxx",
"*.h++",
"*.pch",
"*.pch++",
"*.inc",
"*.s",
"*.S",
"*.asm",
"*.inc",
"*.py",
"*.yml",
"*.txt",
"*.json"
]
},
"ignore_patterns": {
"type": "array",
"description": "List of glob patterns to explicitly ignore when watching for changes.\nFiles matching these patterns will not trigger a rebuild.\nSupported syntax: https://docs.rs/globset/latest/globset/#syntax",
"items": {
"type": "string"
},
"default": [
"build/**/*"
]
},
"objects": {
"type": "array",
"description": "Use units instead.",

View File

@@ -74,6 +74,7 @@ ignore = [
#"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-0436", reason = "Unmaintained paste crate is an indirect dependency" },
{ id = "RUSTSEC-2025-0052", reason = "Unmaintained async-std 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.
@@ -241,8 +242,8 @@ allow-git = []
[sources.allow-org]
# github.com organizations to allow git sources for
github = [
"enarx", # flagset
"encounter",
"gimli-rs", # gimli
]
# gitlab.com organizations to allow git sources for
gitlab = []

View File

@@ -15,11 +15,11 @@ publish = false
[dependencies]
anyhow = "1.0"
argp = "0.4"
crossterm = "0.28"
crossterm = "0.29"
enable-ansi-support = "0.2"
memmap2 = "0.9"
objdiff-core = { path = "../objdiff-core", features = ["all"] }
prost = "0.13"
prost = "0.14"
ratatui = "0.29"
rayon = "1.10"
serde = { version = "1.0", features = ["derive"] }

View File

@@ -19,7 +19,6 @@ use crossterm::{
},
};
use objdiff_core::{
bindings::diff::DiffResult,
build::{
BuildConfig, BuildStatus,
watcher::{Watcher, create_watcher},
@@ -28,7 +27,7 @@ use objdiff_core::{
ProjectConfig, ProjectObject, ProjectObjectMetadata, build_globset,
path::{check_path_buf, platform_path, platform_path_serde_option},
},
diff::{self, DiffObjConfig, MappingConfig, ObjectDiff},
diff::{DiffObjConfig, MappingConfig, ObjectDiff},
jobs::{
Job, JobQueue, JobResult,
objdiff::{ObjDiffConfig, start_build},
@@ -40,10 +39,7 @@ use typed_path::{Utf8PlatformPath, Utf8PlatformPathBuf};
use crate::{
cmd::apply_config_args,
util::{
output::{OutputFormat, write_output},
term::crossterm_panic_handler,
},
util::term::crossterm_panic_handler,
views::{EventControlFlow, EventResult, UiView, function_diff::FunctionDiffUi},
};
@@ -63,12 +59,6 @@ pub struct Args {
#[argp(option, short = 'u')]
/// Unit name within project
unit: Option<String>,
#[argp(option, short = 'o', from_str_fn(platform_path))]
/// Output file (one-shot mode) ("-" for stdout)
output: Option<Utf8PlatformPathBuf>,
#[argp(option)]
/// Output format (json, json-pretty, proto) (default: json)
format: Option<String>,
#[argp(positional)]
/// Function symbol to diff
symbol: Option<String>,
@@ -171,12 +161,8 @@ pub fn run(args: Args) -> Result<()> {
_ => bail!("Either target and base or project and unit must be specified"),
};
if let Some(output) = &args.output {
run_oneshot(&args, output, target_path.as_deref(), base_path.as_deref())
} else {
run_interactive(args, target_path, base_path, project_config)
}
}
fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)> {
let mut diff_config = DiffObjConfig::default();
@@ -194,28 +180,6 @@ fn build_config_from_args(args: &Args) -> Result<(DiffObjConfig, MappingConfig)>
Ok((diff_config, mapping_config))
}
fn run_oneshot(
args: &Args,
output: &Utf8PlatformPath,
target_path: Option<&Utf8PlatformPath>,
base_path: Option<&Utf8PlatformPath>,
) -> Result<()> {
let output_format = OutputFormat::from_option(args.format.as_deref())?;
let (diff_config, mapping_config) = build_config_from_args(args)?;
let target = target_path
.map(|p| obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {p}")))
.transpose()?;
let base = base_path
.map(|p| obj::read::read(p.as_ref(), &diff_config).with_context(|| format!("Loading {p}")))
.transpose()?;
let result =
diff::diff_objs(target.as_ref(), base.as_ref(), None, &diff_config, &mapping_config)?;
let left = target.as_ref().and_then(|o| result.left.as_ref().map(|d| (o, d)));
let right = base.as_ref().and_then(|o| result.right.as_ref().map(|d| (o, d)));
write_output(&DiffResult::new(left, right), Some(output), output_format)?;
Ok(())
}
pub struct AppState {
pub jobs: JobQueue,
pub waker: Arc<TermWaker>,
@@ -378,10 +342,12 @@ fn run_interactive(
};
if let (Some(project_dir), Some(project_config)) = (&state.project_dir, &state.project_config) {
let watch_patterns = project_config.build_watch_patterns()?;
let ignore_patterns = project_config.build_ignore_patterns()?;
state.watcher = Some(create_watcher(
state.modified.clone(),
project_dir.as_ref(),
build_globset(&watch_patterns)?,
build_globset(&ignore_patterns)?,
Waker::from(state.waker.clone()),
)?);
}

View File

@@ -248,14 +248,13 @@ fn report_object(
{
continue;
}
if let Some(existing_functions) = &mut existing_functions {
if (symbol.flags.contains(SymbolFlag::Global)
if let Some(existing_functions) = &mut existing_functions
&& (symbol.flags.contains(SymbolFlag::Global)
|| symbol.flags.contains(SymbolFlag::Weak))
&& !existing_functions.insert(symbol.name.clone())
{
continue;
}
}
let match_percent = symbol_diff.match_percent.unwrap_or_else(|| {
// Support cases where we don't have a target object,
// assume complete means 100% match

View File

@@ -170,8 +170,8 @@ impl UiView for FunctionDiffUi {
let mut prev_text = None;
let mut prev_margin_text = None;
if self.three_way {
if let Some((obj, symbol_idx, symbol_diff)) =
if self.three_way
&& let Some((obj, symbol_idx, symbol_diff)) =
get_symbol(state.prev_obj.as_ref(), self.prev_sym)
{
let mut text = Text::default();
@@ -196,7 +196,6 @@ impl UiView for FunctionDiffUi {
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);
@@ -561,11 +560,13 @@ impl FunctionDiffUi {
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 {
if let Some((cx, cy)) = result.click_xy
&& 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,

View File

@@ -62,7 +62,10 @@ config = [
"dep:semver",
"dep:typed-path",
]
dwarf = ["dep:gimli"]
dwarf = [
"dep:gimli",
"dep:typed-arena",
]
serde = [
"dep:pbjson",
"dep:pbjson-build",
@@ -78,6 +81,7 @@ std = [
"prost?/std",
"serde?/std",
"similar?/std",
"typed-arena?/std",
"typed-path?/std",
"dep:filetime",
"dep:memmap2",
@@ -123,17 +127,17 @@ features = ["all"]
[dependencies]
anyhow = { version = "1.0", default-features = false }
filetime = { version = "0.2", optional = true }
flagset = { version = "0.4", default-features = false, optional = true, git = "https://github.com/enarx/flagset.git", rev = "a1fe9369b3741e43fec45da1998e83b9d78966a2" }
flagset = { version = "0.4", default-features = false, optional = true }
itertools = { version = "0.14", default-features = false, features = ["use_alloc"] }
log = { version = "0.4", default-features = false, optional = true }
memmap2 = { version = "0.9", optional = true }
num-traits = { version = "0.2", default-features = false, optional = true }
object = { git = "https://github.com/gimli-rs/object", rev = "16ff70aa6fbd97d6bb7b92375929f4d72414c32b", default-features = false, features = ["read_core", "elf", "coff"] }
pbjson = { version = "0.7", default-features = false, optional = true }
prost = { version = "0.13", default-features = false, features = ["prost-derive"], optional = true }
object = { version = "0.37", default-features = false, features = ["read_core", "elf", "coff"] }
pbjson = { version = "0.8", default-features = false, optional = true }
prost = { version = "0.14", default-features = false, features = ["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, features = ["hashbrown"], optional = true, git = "https://github.com/encounter/similar.git", branch = "no_std" }
similar = { git = "https://github.com/encounter/similar.git", branch = "no_std", default-features = false, features = ["hashbrown"], optional = true }
typed-path = { version = "0.11", default-features = false, optional = true }
# config
@@ -142,16 +146,17 @@ 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"], optional = true }
gimli = { git = "https://github.com/gimli-rs/gimli", rev = "7335f00e7c39fd501511584fefb0ba974117c950", default-features = false, features = ["read"], optional = true }
typed-arena = { version = "2.0", default-features = false, optional = true }
# ppc
cwdemangle = { version = "1.0", optional = true }
cwextab = { version = "1.0", optional = true }
cwextab = { version = "1.1", optional = true }
powerpc = { version = "0.4", optional = true }
rlwinmdec = { version = "1.1", optional = true }
# mips
rabbitizer = { version = "2.0.0-alpha.1", default-features = false, features = ["all_extensions"], optional = true }
rabbitizer = { version = "2.0.0-alpha.4", default-features = false, features = ["all_extensions"], optional = true }
# x86
cpp_demangle = { version = "0.4", default-features = false, features = ["alloc"], optional = true }
@@ -159,7 +164,7 @@ iced-x86 = { version = "1.21", default-features = false, features = ["decoder",
msvc-demangler = { version = "0.11", optional = true }
# arm
unarm = { version = "1.8", optional = true }
unarm = { version = "1.9", optional = true }
arm-attr = { version = "0.2", optional = true }
# arm64
@@ -167,10 +172,10 @@ 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 = { version = "8.1.0", optional = true }
notify-debouncer-full = { version = "0.5.0", optional = true }
shell-escape = { version = "0.1", optional = true }
tempfile = { version = "3.19", optional = true }
tempfile = { version = "3.20", optional = true }
time = { version = "0.3", optional = true }
encoding_rs = { version = "0.8.35", optional = true }
@@ -189,10 +194,10 @@ self_update = { version = "0.42", optional = true }
[build-dependencies]
heck = { version = "0.5", optional = true }
pbjson-build = { version = "0.7", optional = true }
pbjson-build = { version = "0.8", optional = true }
prettyplease = { version = "0.2", optional = true }
proc-macro2 = { version = "1.0", optional = true }
prost-build = { version = "0.13", optional = true }
prost-build = { version = "0.14", optional = true }
quote = { version = "1.0", optional = true }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" }

View File

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

View File

@@ -464,6 +464,22 @@ impl Arch for ArchArm {
}
flags
}
fn infer_function_size(
&self,
symbol: &Symbol,
section: &Section,
mut next_address: u64,
) -> Result<u64> {
// Trim any trailing 4-byte zeroes from the end (padding)
while next_address >= symbol.address + 4
&& let Some(data) = section.data_range(next_address - 4, 4)
&& data == [0u8; 4]
{
next_address -= 4;
}
Ok(next_address.saturating_sub(symbol.address))
}
}
#[derive(Clone, Copy, Debug)]

View File

@@ -509,8 +509,9 @@ where Cb: FnMut(InstructionPart<'static>) {
return "ubfx";
}
Opcode::SBFM => {
if let Operand::Immediate(63) = ins.operands[3] {
if let Operand::Register(SizeCode::X, _) = ins.operands[0] {
if let Operand::Immediate(63) = ins.operands[3]
&& let Operand::Register(SizeCode::X, _) = ins.operands[0]
{
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[1], ctx);
@@ -518,9 +519,9 @@ where Cb: FnMut(InstructionPart<'static>) {
push_operand(args, &ins.operands[2], ctx);
return "asr";
}
}
if let Operand::Immediate(31) = ins.operands[3] {
if let Operand::Register(SizeCode::W, _) = ins.operands[0] {
if let Operand::Immediate(31) = ins.operands[3]
&& let Operand::Register(SizeCode::W, _) = ins.operands[0]
{
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[1], ctx);
@@ -528,7 +529,6 @@ where Cb: FnMut(InstructionPart<'static>) {
push_operand(args, &ins.operands[2], ctx);
return "asr";
}
}
if let Operand::Immediate(0) = ins.operands[2] {
let newsrc = if let Operand::Register(_size, srcnum) = ins.operands[1] {
Operand::Register(SizeCode::W, srcnum)
@@ -554,8 +554,8 @@ where Cb: FnMut(InstructionPart<'static>) {
}
if let (Operand::Immediate(imms), Operand::Immediate(immr)) =
(ins.operands[2], ins.operands[3])
&& immr < imms
{
if immr < imms {
let size = if let Operand::Register(size, _) = ins.operands[0] {
if size == SizeCode::W { 32 } else { 64 }
} else {
@@ -570,7 +570,6 @@ where Cb: FnMut(InstructionPart<'static>) {
push_unsigned(args, (immr + 1) as u64);
return "sbfiz";
}
}
// `sbfm` is never actually displayed: in the remaining case, it is always aliased to `sbfx`
let width = if let (Operand::Immediate(lsb), Operand::Immediate(width)) =
(ins.operands[2], ins.operands[3])
@@ -593,8 +592,8 @@ where Cb: FnMut(InstructionPart<'static>) {
Opcode::EXTR => {
if let (Operand::Register(_, rn), Operand::Register(_, rm)) =
(ins.operands[1], ins.operands[2])
&& rn == rm
{
if rn == rm {
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
@@ -602,7 +601,6 @@ where Cb: FnMut(InstructionPart<'static>) {
push_operand(args, &ins.operands[3], ctx);
return "ror";
}
}
"extr"
}
Opcode::LDAR => "ldar",
@@ -804,13 +802,11 @@ where Cb: FnMut(InstructionPart<'static>) {
"csneg"
}
Opcode::CSINC => {
if let (
Operand::Register(_, n),
Operand::Register(_, m),
Operand::ConditionCode(cond),
) = (ins.operands[1], ins.operands[2], ins.operands[3])
if let (Operand::Register(_, n), Operand::Register(_, m), Operand::ConditionCode(cond)) =
(ins.operands[1], ins.operands[2], ins.operands[3])
&& n == m
&& cond < 0b1110
{
if n == m && cond < 0b1110 {
return if n == 31 {
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
@@ -825,7 +821,6 @@ where Cb: FnMut(InstructionPart<'static>) {
"cinc"
};
}
}
"csinc"
}
Opcode::CSINV => {
@@ -1200,16 +1195,14 @@ where Cb: FnMut(InstructionPart<'static>) {
Operand::Register(reg_sz, _),
Operand::SIMDRegisterElementsLane(_, _, elem_sz, _),
) = (ins.operands[0], ins.operands[1])
{
if (reg_sz == SizeCode::W && elem_sz == SIMDSizeCode::S)
|| (reg_sz == SizeCode::X && elem_sz == SIMDSizeCode::D)
&& ((reg_sz == SizeCode::W && elem_sz == SIMDSizeCode::S)
|| (reg_sz == SizeCode::X && elem_sz == SIMDSizeCode::D))
{
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[1], ctx);
return "mov";
}
}
"umov"
}
Opcode::SQSHRN2 => "sqshrn2",
@@ -1308,15 +1301,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDADDB(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "staddb" } else { "staddlb" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldaddb"
} else if ar == 0b01 {
@@ -1328,15 +1322,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDCLRB(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "stclrb" } else { "stclrlb" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldclrb"
} else if ar == 0b01 {
@@ -1348,15 +1343,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDEORB(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "steorb" } else { "steorlb" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldeorb"
} else if ar == 0b01 {
@@ -1368,15 +1364,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDSETB(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "stsetb" } else { "stsetlb" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldsetb"
} else if ar == 0b01 {
@@ -1388,15 +1385,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDSMAXB(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "stsmaxb" } else { "stsmaxlb" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldsmaxb"
} else if ar == 0b01 {
@@ -1408,15 +1406,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDSMINB(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "stsminb" } else { "stsminlb" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldsminb"
} else if ar == 0b01 {
@@ -1428,15 +1427,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDUMAXB(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "stumaxb" } else { "stumaxlb" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldumaxb"
} else if ar == 0b01 {
@@ -1448,15 +1448,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDUMINB(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "stuminb" } else { "stuminlb" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
// write!(fmt, "{}", self.opcode)?;
if ar == 0 {
"lduminb"
@@ -1469,15 +1470,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDADDH(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "staddh" } else { "staddlh" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldaddh"
} else if ar == 0b01 {
@@ -1489,15 +1491,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDCLRH(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "stclrh" } else { "stclrlh" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldclrh"
} else if ar == 0b01 {
@@ -1509,15 +1512,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDEORH(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "steorh" } else { "steorlh" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldeorh"
} else if ar == 0b01 {
@@ -1529,15 +1533,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDSETH(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "stseth" } else { "stsetlh" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldseth"
} else if ar == 0b01 {
@@ -1549,15 +1554,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDSMAXH(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "stsmaxh" } else { "stsmaxlh" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldsmaxh"
} else if ar == 0b01 {
@@ -1569,15 +1575,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDSMINH(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "stsminh" } else { "stsminlh" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldsminh"
} else if ar == 0b01 {
@@ -1589,15 +1596,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDUMAXH(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "stumaxh" } else { "stumaxlh" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldumaxh"
} else if ar == 0b01 {
@@ -1609,15 +1617,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDUMINH(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "stuminh" } else { "stuminlh" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"lduminh"
} else if ar == 0b01 {
@@ -1629,15 +1638,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDADD(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "stadd" } else { "staddl" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldadd"
} else if ar == 0b01 {
@@ -1649,15 +1659,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDCLR(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "stclr" } else { "stclrl" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldclr"
} else if ar == 0b01 {
@@ -1669,15 +1680,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDEOR(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "steor" } else { "steorl" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldeor"
} else if ar == 0b01 {
@@ -1689,15 +1701,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDSET(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "stset" } else { "stsetl" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldset"
} else if ar == 0b01 {
@@ -1709,15 +1722,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDSMAX(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "stsmax" } else { "stsmaxl" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldsmax"
} else if ar == 0b01 {
@@ -1729,15 +1743,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDSMIN(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "stsmin" } else { "stsminl" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldsmin"
} else if ar == 0b01 {
@@ -1749,15 +1764,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDUMAX(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "stumax" } else { "stumaxl" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldumax"
} else if ar == 0b01 {
@@ -1769,15 +1785,16 @@ where Cb: FnMut(InstructionPart<'static>) {
}
}
Opcode::LDUMIN(ar) => {
if let Operand::Register(_, rt) = ins.operands[1] {
if rt == 31 && ar & 0b10 == 0b00 {
if let Operand::Register(_, rt) = ins.operands[1]
&& rt == 31
&& ar & 0b10 == 0b00
{
let inst = if ar & 0b01 == 0b00 { "stumin" } else { "stuminl" };
push_operand(args, &ins.operands[0], ctx);
push_separator(args);
push_operand(args, &ins.operands[2], ctx);
return inst;
}
}
if ar == 0 {
"ldumin"
} else if ar == 0b01 {
@@ -2067,8 +2084,8 @@ where Cb: FnMut(InstructionPart<'static>) {
/// Relocations that appear in Operand::PCOffset.
fn is_pc_offset_reloc(reloc: Option<ResolvedRelocation>) -> Option<ResolvedRelocation> {
if let Some(resolved) = reloc {
if let RelocationFlags::Elf(
if let Some(resolved) = reloc
&& let RelocationFlags::Elf(
elf::R_AARCH64_ADR_PREL_PG_HI21
| elf::R_AARCH64_JUMP26
| elf::R_AARCH64_CALL26
@@ -2077,7 +2094,6 @@ fn is_pc_offset_reloc(reloc: Option<ResolvedRelocation>) -> Option<ResolvedReloc
{
return Some(resolved);
}
}
None
}

View File

@@ -15,7 +15,7 @@ use crate::{
diff::{DiffObjConfig, MipsAbi, MipsInstrCategory, display::InstructionPart},
obj::{
InstructionArg, InstructionArgValue, InstructionRef, Relocation, RelocationFlags,
ResolvedInstructionRef, ResolvedRelocation, SymbolFlag, SymbolFlagSet,
ResolvedInstructionRef, ResolvedRelocation, Section, Symbol, SymbolFlag, SymbolFlagSet,
},
};
@@ -140,6 +140,14 @@ impl ArchMips {
})
}
fn default_instruction_flags(&self) -> rabbitizer::InstructionFlags {
match self.isa_extension {
Some(extension) => rabbitizer::InstructionFlags::new_extension(extension),
None => rabbitizer::InstructionFlags::new(IsaVersion::MIPS_III),
}
.with_abi(self.abi)
}
fn instruction_flags(&self, diff_config: &DiffObjConfig) -> rabbitizer::InstructionFlags {
let isa_extension = match diff_config.mips_instr_category {
MipsInstrCategory::Auto => self.isa_extension,
@@ -151,7 +159,7 @@ impl ArchMips {
};
match isa_extension {
Some(extension) => rabbitizer::InstructionFlags::new_extension(extension),
None => rabbitizer::InstructionFlags::new_isa(IsaVersion::MIPS_III, None),
None => rabbitizer::InstructionFlags::new(IsaVersion::MIPS_III),
}
.with_abi(match diff_config.mips_abi {
MipsAbi::Auto => self.abi,
@@ -234,8 +242,8 @@ impl Arch for ArchMips {
object::RelocationFlags::Elf { r_type } => {
if relocation.has_implicit_addend() {
// Check for paired R_MIPS_HI16 and R_MIPS_LO16 relocations.
if let elf::R_MIPS_HI16 | elf::R_MIPS_LO16 = r_type {
if let Some(addend) = self
if let elf::R_MIPS_HI16 | elf::R_MIPS_LO16 = r_type
&& let Some(addend) = self
.paired_relocations
.get(section.index().0)
.and_then(|m| m.get(&address).copied())
@@ -245,7 +253,6 @@ impl Arch for ArchMips {
addend,
}));
}
}
let data = section.data()?;
let code = self
@@ -331,6 +338,36 @@ impl Arch for ArchMips {
}
flags
}
fn infer_function_size(
&self,
symbol: &Symbol,
section: &Section,
next_address: u64,
) -> Result<u64> {
// Trim any trailing 4-byte zeroes from the end (nops)
let mut new_address = next_address;
while new_address >= symbol.address + 4
&& let Some(data) = section.data_range(new_address - 4, 4)
&& data == [0u8; 4]
{
new_address -= 4;
}
// Check if the last instruction has a delay slot, if so, include the delay slot nop
if new_address + 4 <= next_address
&& new_address >= symbol.address + 4
&& let Some(data) = section.data_range(new_address - 4, 4)
&& let instruction = rabbitizer::Instruction::new(
self.endianness.read_u32_bytes(data.try_into().unwrap()),
Vram::new((new_address - 4) as u32),
self.default_instruction_flags(),
)
&& instruction.opcode().has_delay_slot()
{
new_address += 4;
}
Ok(new_address.saturating_sub(symbol.address))
}
}
fn push_args(

View File

@@ -6,6 +6,7 @@ use alloc::{
vec::Vec,
};
use core::{
any::Any,
ffi::CStr,
fmt::{self, Debug},
};
@@ -215,12 +216,12 @@ impl dyn Arch {
// Remove any branch destinations that are outside the function range
for ins in result.iter_mut() {
if let Some(branch_dest) = ins.branch_dest {
if branch_dest < function_start || branch_dest >= function_end {
if let Some(branch_dest) = ins.branch_dest
&& (branch_dest < function_start || branch_dest >= function_end)
{
ins.branch_dest = None;
}
}
}
// Resolve relocation targets within the same function to branch destinations
let mut ins_iter = result.iter_mut().peekable();
@@ -305,7 +306,7 @@ impl dyn Arch {
}
}
pub trait Arch: Send + Sync + Debug {
pub trait Arch: Any + Debug + Send + Sync {
/// Finishes arch-specific initialization that must be done after sections have been combined.
fn post_init(&mut self, _sections: &[Section], _symbols: &[Symbol]) {}
@@ -406,6 +407,15 @@ pub trait Arch: Send + Sync + Debug {
) -> Vec<ContextItem> {
Vec::new()
}
fn infer_function_size(
&self,
symbol: &Symbol,
_section: &Section,
next_address: u64,
) -> Result<u64> {
Ok(next_address.saturating_sub(symbol.address))
}
}
pub fn new_arch(object: &object::File) -> Result<Box<dyn Arch>> {
@@ -462,6 +472,7 @@ impl Arch for ArchDummy {
fn data_reloc_size(&self, _flags: RelocationFlags) -> usize { 0 }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RelocationOverrideTarget {
Keep,
Skip,
@@ -469,6 +480,7 @@ pub enum RelocationOverrideTarget {
Section(object::SectionIndex),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RelocationOverride {
pub target: RelocationOverrideTarget,
pub addend: i64,

View File

@@ -514,16 +514,16 @@ pub fn ppc_data_flow_analysis(
}
fn get_string_data(obj: &Object, symbol_index: usize, offset: Simm) -> Option<&str> {
if let Some(sym) = obj.symbols.get(symbol_index) {
if sym.name.starts_with("@stringBase") && offset.0 != 0 {
if let Some(data) = obj.symbol_data(symbol_index) {
if let Some(sym) = obj.symbols.get(symbol_index)
&& sym.name.starts_with("@stringBase")
&& offset.0 != 0
&& let Some(data) = obj.symbol_data(symbol_index)
{
let bytes = &data[offset.0 as usize..];
if let Ok(Ok(str)) = CStr::from_bytes_until_nul(bytes).map(|x| x.to_str()) {
return Some(str);
}
}
}
}
None
}
@@ -577,9 +577,9 @@ fn generate_flow_analysis_result(
let registers = register_state_at.get(index as usize).unwrap_or(&default_register_state);
if let (powerpc::Opcode::Addi, Argument::GPR(rel), Argument::Simm(offset)) =
(ins.op, args[1], args[2])
&& let RegisterContent::Symbol(sym_index) = registers[rel]
&& let Some(str) = get_string_data(obj, sym_index, offset)
{
if let RegisterContent::Symbol(sym_index) = registers[rel] {
if let Some(str) = get_string_data(obj, sym_index, offset) {
// Show the string constant in the analysis result
let formatted = format!("\"{str}\"");
analysis_result.set_argument_value_at_address(
@@ -589,8 +589,6 @@ fn generate_flow_analysis_result(
);
// Don't continue, we want to show the stringbase value as well
}
}
}
let is_store = is_store_instruction(ins.op);
for (arg_index, arg) in args.into_iter().enumerate() {

View File

@@ -20,7 +20,7 @@ use crate::{
},
obj::{
FlowAnalysisResult, InstructionRef, Object, Relocation, RelocationFlags,
ResolvedInstructionRef, ResolvedRelocation, Symbol, SymbolFlag, SymbolFlagSet,
ResolvedInstructionRef, ResolvedRelocation, Section, Symbol, SymbolFlag, SymbolFlagSet,
},
};
@@ -66,7 +66,9 @@ impl ArchPpc {
if file.is_64() {
powerpc::Extension::Ppc64 | powerpc::Extension::AltiVec
} else {
powerpc::Extension::AltiVec.into()
// Gekko/Broadway objects often use the EF_PPC_EMB flag,
// but ProDG in particular does not emit it.
powerpc::Extensions::gekko_broadway()
}
}
};
@@ -229,13 +231,19 @@ impl Arch for ArchPpc {
typ: pe::IMAGE_REL_PPC_PAIR
})
})
.map_or(Ok(None), |(_, reloc)| match reloc.target() {
.map_or(
Ok(Some(RelocationOverride {
target: RelocationOverrideTarget::Keep,
addend: 0,
})),
|(_, reloc)| match reloc.target() {
object::RelocationTarget::Symbol(index) => Ok(Some(RelocationOverride {
target: RelocationOverrideTarget::Keep,
addend: index.0 as u16 as i16 as i64,
})),
target => Err(anyhow!("Unsupported IMAGE_REL_PPC_PAIR target {target:?}")),
}),
},
),
// Skip PAIR relocations as they are handled by the previous case
object::RelocationFlags::Coff { typ: pe::IMAGE_REL_PPC_PAIR } => {
Ok(Some(RelocationOverride { target: RelocationOverrideTarget::Skip, addend: 0 }))
@@ -449,6 +457,22 @@ impl Arch for ArchPpc {
}
out
}
fn infer_function_size(
&self,
symbol: &Symbol,
section: &Section,
mut next_address: u64,
) -> Result<u64> {
// Trim any trailing 4-byte zeroes from the end (padding)
while next_address >= symbol.address + 4
&& let Some(data) = section.data_range(next_address - 4, 4)
&& data == [0u8; 4]
{
next_address -= 4;
}
Ok(next_address.saturating_sub(symbol.address))
}
}
impl ArchPpc {
@@ -651,7 +675,7 @@ fn make_symbol_ref(symbol: &object::Symbol) -> Result<ExtabSymbolRef> {
#[derive(Debug)]
struct PoolReference {
addr_src_gpr: powerpc::GPR,
addr_offset: i16,
addr_offset: i64,
addr_dst_gpr: Option<powerpc::GPR>,
}
@@ -659,16 +683,20 @@ struct PoolReference {
// 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: powerpc::Opcode,
ins: powerpc::Ins,
simplified: &powerpc::ParsedIns,
) -> Option<PoolReference> {
use powerpc::{Argument, Opcode};
let args = &simplified.args;
if flow_analysis::guess_data_type_from_load_store_inst_op(opcode).is_some() {
if flow_analysis::guess_data_type_from_load_store_inst_op(ins.op).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 })
Some(PoolReference {
addr_src_gpr,
addr_offset: offset.0 as i64,
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.
@@ -688,17 +716,51 @@ fn get_pool_reference_for_inst(
// 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]) {
match (ins.op, args[0], args[1], args[2]) {
(
// `addi` or `subi`
Opcode::Addi,
Argument::GPR(addr_dst_gpr),
Argument::GPR(addr_src_gpr),
Argument::Simm(simm),
) => Some(PoolReference {
) => {
let offset = if simplified.mnemonic == "addi" { simm.0 } else { -simm.0 };
Some(PoolReference {
addr_src_gpr,
addr_offset: simm.0,
addr_offset: offset as i64,
addr_dst_gpr: Some(addr_dst_gpr),
}),
})
}
(
// `addis`
Opcode::Addis,
Argument::GPR(addr_dst_gpr),
Argument::GPR(addr_src_gpr),
Argument::Uimm(uimm), // Note: `addis` uses UIMM, unlike `addi`, `subi`, and `subis`
) => {
assert_eq!(simplified.mnemonic, "addis");
let offset = (uimm.0 as i64) << 16;
Some(PoolReference {
addr_src_gpr,
addr_offset: offset,
addr_dst_gpr: Some(addr_dst_gpr),
})
}
(
// `subis`
Opcode::Addis,
Argument::GPR(addr_dst_gpr),
Argument::GPR(addr_src_gpr),
Argument::Simm(simm),
) => {
assert_eq!(simplified.mnemonic, "subis");
let offset = (simm.0 as i64) << 16;
Some(PoolReference {
addr_src_gpr,
addr_offset: offset,
addr_dst_gpr: Some(addr_dst_gpr),
})
}
(
// `mr` or `mr.`
Opcode::Or,
@@ -753,13 +815,13 @@ fn clear_overwritten_gprs(ins: powerpc::Ins, gpr_pool_relocs: &mut BTreeMap<u8,
// 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,
offset: i64,
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 offset_from_pool = pool_reloc.relocation.addend + offset;
let target_address = pool_reloc.symbol.address.checked_add_signed(offset_from_pool)?;
let target_symbol;
let addend;
@@ -844,8 +906,8 @@ fn generate_fake_pool_relocations_for_function(
break;
}
}
if let Some(branch_dest) = branch_dest {
if branch_dest >= func_address as u32
if let Some(branch_dest) = branch_dest
&& 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;
@@ -875,14 +937,13 @@ fn generate_fake_pool_relocations_for_function(
_ => unreachable!(),
}
}
}
if let Opcode::Bcctr = ins.op {
if simplified.mnemonic == "bctr" {
if let Opcode::Bcctr = ins.op
&& 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);
@@ -923,7 +984,7 @@ fn generate_fake_pool_relocations_for_function(
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);
}
}
} else if let Some(pool_ref) = get_pool_reference_for_inst(ins.op, &simplified) {
} else if let Some(pool_ref) = get_pool_reference_for_inst(ins, &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) {
@@ -942,7 +1003,7 @@ fn generate_fake_pool_relocations_for_function(
// 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;
new_reloc.addend += pool_ref.addr_offset;
gpr_pool_relocs.insert(addr_dst_gpr.0, new_reloc);
} else {
clear_overwritten_gprs(ins, &mut gpr_pool_relocs);

View File

@@ -11,7 +11,7 @@ use object::{Endian as _, Object as _, ObjectSection as _, elf, pe};
use crate::{
arch::{Arch, RelocationOverride, RelocationOverrideTarget},
diff::{DiffObjConfig, X86Formatter, display::InstructionPart},
obj::{InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef},
obj::{InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, Section, Symbol},
};
#[derive(Debug)]
@@ -303,6 +303,52 @@ impl Arch for ArchX86 {
fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
self.reloc_size(flags).unwrap_or(1)
}
fn infer_function_size(
&self,
symbol: &Symbol,
section: &Section,
next_address: u64,
) -> Result<u64> {
let Ok(size) = (next_address - symbol.address).try_into() else {
return Ok(next_address.saturating_sub(symbol.address));
};
let Some(code) = section.data_range(symbol.address, size) else {
return Ok(0);
};
// Decode instructions to find the last non-NOP instruction
let mut decoder = self.decoder(code, symbol.address);
let mut instruction = Instruction::default();
let mut new_address = 0;
let mut reloc_iter = section.relocations.iter().peekable();
'outer: while decoder.can_decode() {
let address = decoder.ip();
while let Some(reloc) = reloc_iter.peek() {
match reloc.address.cmp(&address) {
Ordering::Less => {
reloc_iter.next();
}
Ordering::Equal => {
// If the instruction starts at a relocation, it's inline data
let reloc_size = self.reloc_size(reloc.flags).with_context(|| {
format!("Unsupported inline x86 relocation {:?}", reloc.flags)
})?;
if decoder.set_position(decoder.position() + reloc_size).is_ok() {
new_address = address + reloc_size as u64;
decoder.set_ip(new_address);
continue 'outer;
}
}
Ordering::Greater => break,
}
}
decoder.decode_out(&mut instruction);
if instruction.mnemonic() != iced_x86::Mnemonic::Nop {
new_address = instruction.next_ip();
}
}
Ok(new_address.saturating_sub(symbol.address))
}
}
struct InstructionFormatterOutput<'a> {

View File

@@ -1,242 +0,0 @@
#![allow(clippy::needless_lifetimes)] // Generated serde code
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<(&obj::Object, &diff::ObjectDiff)>,
_right: Option<(&obj::Object, &diff::ObjectDiff)>,
) -> Self {
Self {
// 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: &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,3 +1 @@
#[cfg(feature = "any-arch")]
pub mod diff;
pub mod report;

View File

@@ -49,6 +49,7 @@ pub fn run_make(config: &BuildConfig, arg: &Utf8UnixPath) -> BuildStatus {
};
#[cfg(windows)]
let mut command = {
use alloc::borrow::Cow;
use std::os::windows::process::CommandExt;
let mut command = if config.selected_wsl_distro.is_some() {
@@ -60,13 +61,17 @@ pub fn run_make(config: &BuildConfig, arg: &Utf8UnixPath) -> BuildStatus {
// 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(),
// Convert to absolute Unix path
Ok(new_cwd) => Cow::Owned(
Utf8UnixPath::new("/").join(new_cwd.with_unix_encoding()).into_string(),
),
// Otherwise, use the Windows path as is
Err(_) => Cow::Borrowed(cwd.as_str()),
};
command
.arg("--cd")
.arg(cwd.as_str())
.arg::<&str>(cwd.as_ref())
.arg("-d")
.arg(distro)
.arg("--")

View File

@@ -29,6 +29,7 @@ pub fn create_watcher(
modified: Arc<AtomicBool>,
project_dir: &Path,
patterns: GlobSet,
ignore_patterns: GlobSet,
waker: Waker,
) -> notify::Result<Watcher> {
let base_dir = fs::canonicalize(project_dir)?;
@@ -54,8 +55,8 @@ pub fn create_watcher(
let Ok(path) = path.strip_prefix(&base_dir_clone) else {
continue;
};
if patterns.is_match(path) {
// log::info!("File modified: {}", path.display());
if patterns.is_match(path) && !ignore_patterns.is_match(path) {
log::debug!("File modified: {}", path.display());
any_match = true;
}
}

View File

@@ -36,6 +36,8 @@ pub struct ProjectConfig {
pub build_target: Option<bool>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub watch_patterns: Option<Vec<String>>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub ignore_patterns: Option<Vec<String>>,
#[cfg_attr(
feature = "serde",
serde(alias = "objects", skip_serializing_if = "Option::is_none")
@@ -66,7 +68,18 @@ impl ProjectConfig {
.map(|s| Glob::new(s))
.collect::<Result<Vec<Glob>, globset::Error>>()?
} else {
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
default_watch_patterns()
})
}
pub fn build_ignore_patterns(&self) -> Result<Vec<Glob>, globset::Error> {
Ok(if let Some(ignore_patterns) = &self.ignore_patterns {
ignore_patterns
.iter()
.map(|s| Glob::new(s))
.collect::<Result<Vec<Glob>, globset::Error>>()?
} else {
default_ignore_patterns()
})
}
}
@@ -191,14 +204,21 @@ pub struct ScratchConfig {
pub const CONFIG_FILENAMES: [&str; 3] = ["objdiff.json", "objdiff.yml", "objdiff.yaml"];
pub const DEFAULT_WATCH_PATTERNS: &[&str] = &[
"*.c", "*.cp", "*.cpp", "*.cxx", "*.h", "*.hp", "*.hpp", "*.hxx", "*.s", "*.S", "*.asm",
"*.inc", "*.py", "*.yml", "*.txt", "*.json",
"*.c", "*.cc", "*.cp", "*.cpp", "*.cxx", "*.c++", "*.h", "*.hh", "*.hp", "*.hpp", "*.hxx",
"*.h++", "*.pch", "*.pch++", "*.inc", "*.s", "*.S", "*.asm", "*.py", "*.yml", "*.txt",
"*.json",
];
pub const DEFAULT_IGNORE_PATTERNS: &[&str] = &["build/**/*"];
pub fn default_watch_patterns() -> Vec<Glob> {
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
}
pub fn default_ignore_patterns() -> Vec<Glob> {
DEFAULT_IGNORE_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
}
#[cfg(feature = "std")]
#[derive(Clone, Eq, PartialEq)]
pub struct ProjectConfigInfo {

View File

@@ -496,6 +496,14 @@ fn diff_instruction(
result.right_args_diff.push(InstructionArgDiffIndex::new(b_diff));
}
}
if result.kind == InstructionDiffKind::None
&& left_resolved.code.len() != right_resolved.code.len()
{
// If everything else matches but the raw code length differs (e.g. x86 instructions
// with same disassembly but different encoding), mark as op mismatch
result.kind = InstructionDiffKind::OpMismatch;
state.diff_score += PENALTY_REG_DIFF;
}
return Ok(result);
}

View File

@@ -35,6 +35,20 @@ pub fn diff_bss_symbol(
))
}
pub fn symbol_name_matches(left_name: &str, right_name: &str) -> bool {
// Match Metrowerks symbol$1234 against symbol$2345
if let Some((prefix, suffix)) = left_name.split_once('$') {
if !suffix.chars().all(char::is_numeric) {
return false;
}
right_name
.split_once('$')
.is_some_and(|(p, s)| p == prefix && s.chars().all(char::is_numeric))
} else {
left_name == right_name
}
}
fn reloc_eq(
left_obj: &Object,
right_obj: &Object,
@@ -45,8 +59,8 @@ fn reloc_eq(
return false;
}
let symbol_name_addend_matches =
left.symbol.name == right.symbol.name && left.relocation.addend == right.relocation.addend;
let symbol_name_addend_matches = symbol_name_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
@@ -127,6 +141,28 @@ fn diff_data_relocs_for_range<'left, 'right>(
diffs
}
pub fn no_diff_data_section(obj: &Object, section_idx: usize) -> Result<SectionDiff> {
let section = &obj.sections[section_idx];
let len = section.data.len();
let data = &section.data[0..len];
let data_diff =
vec![DataDiff { data: data.to_vec(), kind: DataDiffKind::None, len, ..Default::default() }];
let mut reloc_diffs = Vec::new();
for reloc in section.relocations.iter() {
let reloc_len = obj.arch.data_reloc_size(reloc.flags);
let range = reloc.address as usize..reloc.address as usize + reloc_len;
reloc_diffs.push(DataRelocationDiff {
reloc: reloc.clone(),
kind: DataDiffKind::None,
range,
});
}
Ok(SectionDiff { match_percent: Some(0.0), data_diff, reloc_diff: reloc_diffs })
}
/// Compare the data sections of two object files.
pub fn diff_data_section(
left_obj: &Object,
@@ -415,6 +451,10 @@ pub fn diff_generic_section(
))
}
pub fn no_diff_bss_section() -> Result<SectionDiff> {
Ok(SectionDiff { match_percent: Some(0.0), data_diff: vec![], reloc_diff: vec![] })
}
/// Compare the addresses and sizes of each symbol in the BSS sections.
pub fn diff_bss_section(
left_obj: &Object,

View File

@@ -385,14 +385,14 @@ pub fn symbol_context(obj: &Object, symbol_index: usize) -> Vec<ContextItem> {
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 {
if symbol.section.is_some()
&& let Some(address) = symbol.virtual_address
{
out.push(ContextItem::Copy {
value: format!("{address:x}"),
label: Some("virtual address".to_string()),
});
}
}
out.append(&mut obj.arch.symbol_context(obj, symbol_index));
out
}

View File

@@ -13,7 +13,7 @@ use crate::{
code::{diff_code, no_diff_code},
data::{
diff_bss_section, diff_bss_symbol, diff_data_section, diff_data_symbol,
diff_generic_section,
diff_generic_section, no_diff_bss_section, no_diff_data_section, symbol_name_matches,
},
},
obj::{InstructionRef, Object, Relocation, SectionKind, Symbol, SymbolFlag},
@@ -288,12 +288,12 @@ pub fn diff_objs(
}
for section_match in section_matches {
if let SectionMatch {
match section_match {
SectionMatch {
left: Some(left_section_idx),
right: Some(right_section_idx),
section_kind,
} = section_match
{
} => {
let (left_obj, left_out) = left.as_mut().unwrap();
let (right_obj, right_out) = right.as_mut().unwrap();
match section_kind {
@@ -336,6 +336,38 @@ pub fn diff_objs(
SectionKind::Unknown => unreachable!(),
}
}
SectionMatch { left: Some(left_section_idx), right: None, section_kind } => {
let (left_obj, left_out) = left.as_mut().unwrap();
match section_kind {
SectionKind::Code => {}
SectionKind::Data => {
left_out.sections[left_section_idx] =
no_diff_data_section(left_obj, left_section_idx)?;
}
SectionKind::Bss | SectionKind::Common => {
left_out.sections[left_section_idx] = no_diff_bss_section()?;
}
SectionKind::Unknown => unreachable!(),
}
}
SectionMatch { left: None, right: Some(right_section_idx), section_kind } => {
let (right_obj, right_out) = right.as_mut().unwrap();
match section_kind {
SectionKind::Code => {}
SectionKind::Data => {
right_out.sections[right_section_idx] =
no_diff_data_section(right_obj, right_section_idx)?;
}
SectionKind::Bss | SectionKind::Common => {
right_out.sections[right_section_idx] = no_diff_bss_section()?;
}
SectionKind::Unknown => unreachable!(),
}
}
SectionMatch { left: None, right: None, .. } => {
// Should not happen
}
}
}
if let (Some((right_obj, right_out)), Some((left_obj, left_out))) =
@@ -467,16 +499,16 @@ fn apply_symbol_mappings(
) -> Result<()> {
// If we're selecting a symbol to use as a comparison, mark it as used
// This ensures that we don't match it to another symbol at any point
if let Some(left_name) = &mapping_config.selecting_left {
if let Some(left_symbol) = left.symbol_by_name(left_name) {
if let Some(left_name) = &mapping_config.selecting_left
&& let Some(left_symbol) = left.symbol_by_name(left_name)
{
left_used.insert(left_symbol);
}
}
if let Some(right_name) = &mapping_config.selecting_right {
if let Some(right_symbol) = right.symbol_by_name(right_name) {
if let Some(right_name) = &mapping_config.selecting_right
&& let Some(right_symbol) = right.symbol_by_name(right_name)
{
right_used.insert(right_symbol);
}
}
// Apply manual symbol mappings
for (left_name, right_name) in &mapping_config.mappings {
@@ -543,6 +575,9 @@ fn matching_symbols(
&mut matches,
)?;
}
// Do two passes for nameless literals. The first only pairs up perfect matches to ensure
// those are correct first, while the second pass catches near matches.
for fuzzy_literals in [false, true] {
for (symbol_idx, symbol) in left.symbols.iter().enumerate() {
if symbol.size == 0 || symbol.flags.contains(SymbolFlag::Ignored) {
continue;
@@ -556,17 +591,22 @@ fn matching_symbols(
}
let symbol_match = SymbolMatch {
left: Some(symbol_idx),
right: find_symbol(right, left, symbol, Some(&right_used)),
prev: find_symbol(prev, left, symbol, None),
right: find_symbol(right, left, symbol_idx, Some(&right_used), fuzzy_literals),
prev: find_symbol(prev, left, symbol_idx, None, fuzzy_literals),
section_kind,
};
matches.push(symbol_match);
if let Some(right) = symbol_match.right {
left_used.insert(symbol_idx);
right_used.insert(right);
}
}
}
}
if let Some(right) = right {
// Do two passes for nameless literals. The first only pairs up perfect matches to ensure
// those are correct first, while the second pass catches near matches.
for fuzzy_literals in [false, true] {
for (symbol_idx, symbol) in right.symbols.iter().enumerate() {
if symbol.size == 0 || symbol.flags.contains(SymbolFlag::Ignored) {
continue;
@@ -578,12 +618,17 @@ fn matching_symbols(
if right_used.contains(&symbol_idx) {
continue;
}
matches.push(SymbolMatch {
let symbol_match = SymbolMatch {
left: None,
right: Some(symbol_idx),
prev: find_symbol(prev, right, symbol, None),
prev: find_symbol(prev, right, symbol_idx, None, fuzzy_literals),
section_kind,
});
};
matches.push(symbol_match);
if symbol_match.prev.is_some() {
right_used.insert(symbol_idx);
}
}
}
}
Ok(matches)
@@ -605,7 +650,10 @@ where
fn symbol_section<'obj>(obj: &'obj Object, symbol: &Symbol) -> Option<(&'obj str, SectionKind)> {
if let Some(section) = symbol.section.and_then(|section_idx| obj.sections.get(section_idx)) {
Some((section.name.as_str(), section.kind))
// Match x86 .rdata$r against .rdata$rs
let section_name =
section.name.split_once('$').map_or(section.name.as_str(), |(prefix, _)| prefix);
Some((section_name, section.kind))
} else if symbol.flags.contains(SymbolFlag::Common) {
Some((".comm", SectionKind::Common))
} else {
@@ -621,53 +669,94 @@ fn symbol_section_kind(obj: &Object, symbol: &Symbol) -> SectionKind {
}
}
/// Check if a symbol is a compiler-generated literal like @1234.
fn is_symbol_compiler_generated_literal(symbol: &Symbol) -> bool {
if !symbol.name.starts_with('@') {
return false;
}
if !symbol.name[1..].chars().all(char::is_numeric) {
// Exclude @stringBase0, @GUARD@, etc.
return false;
}
true
}
fn find_symbol(
obj: Option<&Object>,
in_obj: &Object,
in_symbol: &Symbol,
in_symbol_idx: usize,
used: Option<&BTreeSet<usize>>,
fuzzy_literals: bool,
) -> Option<usize> {
let in_symbol = &in_obj.symbols[in_symbol_idx];
let obj = obj?;
let (section_name, section_kind) = symbol_section(in_obj, in_symbol)?;
// Match compiler-generated symbols against each other (e.g. @251 -> @60)
// If they are in the same section and have the same value
if is_symbol_compiler_generated_literal(in_symbol)
&& matches!(section_kind, SectionKind::Data | SectionKind::Bss)
{
let mut closest_match_symbol_idx = None;
let mut closest_match_percent = 0.0;
for (symbol_idx, symbol) in unmatched_symbols(obj, used) {
let Some(section_index) = symbol.section else {
continue;
};
if obj.sections[section_index].name != section_name {
continue;
}
if !is_symbol_compiler_generated_literal(symbol) {
continue;
}
match section_kind {
SectionKind::Data => {
// For data, pick the first symbol with exactly matching bytes and relocations.
// If no symbols match exactly, and `fuzzy_literals` is true, pick the closest
// plausible match instead.
if let Ok((left_diff, _right_diff)) =
diff_data_symbol(in_obj, obj, in_symbol_idx, symbol_idx)
&& let Some(match_percent) = left_diff.match_percent
&& (match_percent == 100.0
|| (fuzzy_literals
&& match_percent >= 50.0
&& match_percent > closest_match_percent))
{
closest_match_symbol_idx = Some(symbol_idx);
closest_match_percent = match_percent;
if match_percent == 100.0 {
break;
}
}
}
SectionKind::Bss => {
// For BSS, pick the first symbol that has the exact matching size.
if in_symbol.size == symbol.size {
closest_match_symbol_idx = Some(symbol_idx);
break;
}
}
_ => unreachable!(),
}
}
return closest_match_symbol_idx;
}
// Try to find an exact name match
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|(_, symbol)| {
symbol.name == in_symbol.name && symbol_section_kind(obj, symbol) == section_kind
}) {
return Some(symbol_idx);
}
// Match compiler-generated symbols against each other (e.g. @251 -> @60)
// If they are at the same address in the same section
if in_symbol.name.starts_with('@')
&& matches!(section_kind, SectionKind::Data | SectionKind::Bss)
{
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|(_, symbol)| {
let Some(section_index) = symbol.section else {
return false;
};
symbol.name.starts_with('@')
&& symbol.address == in_symbol.address
&& obj.sections[section_index].name == section_name
}) {
return Some(symbol_idx);
}
}
// Match Metrowerks symbol$1234 against symbol$2345
if let Some((prefix, suffix)) = in_symbol.name.split_once('$') {
if !suffix.chars().all(char::is_numeric) {
return None;
}
if let Some((symbol_idx, _)) = unmatched_symbols(obj, used).find(|&(_, symbol)| {
if let Some((p, s)) = symbol.name.split_once('$') {
prefix == p
&& s.chars().all(char::is_numeric)
symbol_name_matches(&in_symbol.name, &symbol.name)
&& symbol_section_kind(obj, symbol) == section_kind
} else {
false
}
&& symbol_section(obj, symbol).is_some_and(|(name, _)| name == section_name)
}) {
return Some(symbol_idx);
}
}
None
}

View File

@@ -170,13 +170,6 @@ pub enum JobResult {
CreateScratch(Option<Box<CreateScratchResult>>),
}
fn should_cancel(rx: &Receiver<()>) -> bool {
match rx.try_recv() {
Ok(_) | Err(TryRecvError::Disconnected) => true,
Err(_) => false,
}
}
fn start_job(
waker: Waker,
title: &str,
@@ -203,7 +196,6 @@ fn start_job(
}
});
let id = JOB_ID.fetch_add(1, Ordering::Relaxed);
// log::info!("Started job {}", id); TODO
JobState { id, kind, handle: Some(handle), context, cancel: tx }
}
@@ -228,3 +220,10 @@ fn update_status(
context.waker.wake_by_ref();
Ok(())
}
fn should_cancel(rx: &Receiver<()>) -> bool {
match rx.try_recv() {
Ok(_) | Err(TryRecvError::Disconnected) => true,
Err(_) => false,
}
}

View File

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

View File

@@ -1,3 +1,5 @@
#[cfg(feature = "dwarf")]
mod dwarf2;
pub mod read;
pub mod split_meta;

View File

@@ -122,7 +122,7 @@ fn map_symbols(
}
// Infer symbol sizes for 0-size symbols
infer_symbol_sizes(&mut symbols, sections);
infer_symbol_sizes(arch, &mut symbols, sections)?;
Ok((symbols, symbol_indices))
}
@@ -130,13 +130,13 @@ fn map_symbols(
/// When inferring a symbol's size, we ignore symbols that start with specific prefixes. They are
/// usually emitted as branch targets and do not represent the start of a function or object.
fn is_local_label(symbol: &Symbol) -> bool {
const LABEL_PREFIXES: &[&str] = &[".L", "LAB_"];
const LABEL_PREFIXES: &[&str] = &[".L", "LAB_", "switchD_"];
symbol.size == 0
&& symbol.flags.contains(SymbolFlag::Local)
&& LABEL_PREFIXES.iter().any(|p| symbol.name.starts_with(p))
}
fn infer_symbol_sizes(symbols: &mut [Symbol], sections: &[Section]) {
fn infer_symbol_sizes(arch: &dyn Arch, symbols: &mut [Symbol], sections: &[Section]) -> Result<()> {
// Create a sorted list of symbol indices by section
let mut symbols_with_section = Vec::<usize>::with_capacity(symbols.len());
for (i, symbol) in symbols.iter().enumerate() {
@@ -205,11 +205,14 @@ fn infer_symbol_sizes(symbols: &mut [Symbol], sections: &[Section]) {
}
iter_idx += 1;
};
let next_address = next_symbol.map(|s| s.address).unwrap_or_else(|| {
let section = &sections[section_idx];
section.address + section.size
});
let new_size = next_address.saturating_sub(symbol.address);
let next_address =
next_symbol.map(|s| s.address).unwrap_or_else(|| section.address + section.size);
let new_size = if section.kind == SectionKind::Code {
arch.infer_function_size(symbol, section, next_address)?
} else {
next_address.saturating_sub(symbol.address)
};
if new_size > 0 {
let symbol = &mut symbols[symbol_idx];
symbol.size = new_size;
@@ -218,7 +221,7 @@ fn infer_symbol_sizes(symbols: &mut [Symbol], sections: &[Section]) {
}
// Set symbol kind if unknown and size is non-zero
if symbol.kind == SymbolKind::Unknown {
symbol.kind = match sections[section_idx].kind {
symbol.kind = match section.kind {
SectionKind::Code => SymbolKind::Function,
SectionKind::Data | SectionKind::Bss => SymbolKind::Object,
_ => SymbolKind::Unknown,
@@ -226,6 +229,7 @@ fn infer_symbol_sizes(symbols: &mut [Symbol], sections: &[Section]) {
}
}
}
Ok(())
}
fn map_sections(
@@ -361,7 +365,8 @@ fn map_section_relocations(
None => {
ensure!(
!reloc.has_implicit_addend(),
"Unsupported implicit relocation {:?}",
"Unsupported {:?} implicit relocation {:?}",
obj_file.architecture(),
reloc.flags()
);
}
@@ -545,15 +550,14 @@ fn perform_data_flow_analysis(obj: &mut Object, config: &DiffObjConfig) -> Resul
}
// Optional full data flow analysis
if config.analyze_data_flow {
if let Some(flow_result) =
if config.analyze_data_flow
&& let Some(flow_result) =
obj.arch.data_flow_analysis(obj, symbol, code, &section.relocations)
{
generated_flow_results.push((symbol.clone(), flow_result));
}
}
}
}
for (symbol, flow_result) in generated_flow_results {
obj.add_flow_analysis_result(&symbol, flow_result);
}
@@ -573,6 +577,28 @@ fn parse_line_info(
obj_data: &[u8],
) -> Result<()> {
// DWARF 1.1
if let Err(e) = parse_line_info_dwarf1(obj_file, sections) {
log::warn!("Failed to parse DWARF 1.1 line info: {e}");
}
// DWARF 2+
#[cfg(feature = "dwarf")]
if let Err(e) = super::dwarf2::parse_line_info_dwarf2(obj_file, sections) {
log::warn!("Failed to parse DWARF 2+ line info: {e}");
}
// COFF
if let object::File::Coff(coff) = obj_file
&& let Err(e) = parse_line_info_coff(coff, sections, section_indices, obj_data)
{
log::warn!("Failed to parse COFF line info: {e}");
}
Ok(())
}
/// Parse .line section from DWARF 1.1 format.
fn parse_line_info_dwarf1(obj_file: &object::File, sections: &mut [Section]) -> Result<()> {
if let Some(section) = obj_file.section_by_name(".line") {
let data = section.uncompressed_data()?;
let mut reader: &[u8] = data.as_ref();
@@ -600,55 +626,6 @@ fn parse_line_info(
}
}
}
// DWARF 2+
#[cfg(feature = "dwarf")]
{
fn gimli_error(e: gimli::Error) -> anyhow::Error { anyhow::anyhow!("DWARF error: {e:?}") }
let dwarf_cow = gimli::DwarfSections::load(|id| {
Ok::<_, gimli::Error>(
obj_file
.section_by_name(id.name())
.and_then(|section| section.uncompressed_data().ok())
.unwrap_or(alloc::borrow::Cow::Borrowed(&[][..])),
)
})
.map_err(gimli_error)?;
let endian = match obj_file.endianness() {
object::Endianness::Little => gimli::RunTimeEndian::Little,
object::Endianness::Big => gimli::RunTimeEndian::Big,
};
let dwarf = dwarf_cow.borrow(|section| gimli::EndianSlice::new(section, endian));
let mut iter = dwarf.units();
if let Some(header) = iter.next().map_err(gimli_error)? {
let unit = dwarf.unit(header).map_err(gimli_error)?;
if let Some(program) = unit.line_program.clone() {
let mut text_sections = sections.iter_mut().filter(|s| s.kind == SectionKind::Code);
let mut lines = text_sections.next().map(|section| &mut section.line_info);
let mut rows = program.rows();
while let Some((_header, row)) = rows.next_row().map_err(gimli_error)? {
if let (Some(line), Some(lines)) = (row.line(), &mut lines) {
lines.insert(row.address(), line.get() as u32);
}
if row.end_sequence() {
// The next row is the start of a new sequence, which means we must
// advance to the next .text section.
lines = text_sections.next().map(|section| &mut section.line_info);
}
}
}
}
if iter.next().map_err(gimli_error)?.is_some() {
log::warn!("Multiple units found in DWARF data, only processing the first");
}
}
// COFF
if let object::File::Coff(coff) = obj_file {
parse_line_info_coff(coff, sections, section_indices, obj_data)?;
}
Ok(())
}

View File

@@ -48,7 +48,7 @@ pub fn align_data_to_4<W: std::io::Write + ?Sized>(
len: usize,
) -> std::io::Result<()> {
const ALIGN_BYTES: &[u8] = &[0; 4];
if len % 4 != 0 {
if !len.is_multiple_of(4) {
writer.write_all(&ALIGN_BYTES[..4 - len % 4])?;
}
Ok(())

View File

@@ -507,116 +507,4 @@ expression: diff.instruction_rows
),
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 88,
size: 1,
opcode: 465,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 89,
size: 1,
opcode: 465,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 90,
size: 1,
opcode: 465,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 91,
size: 1,
opcode: 465,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 92,
size: 1,
opcode: 465,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 93,
size: 1,
opcode: 465,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 94,
size: 1,
opcode: 465,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
InstructionDiffRow {
ins_ref: Some(
InstructionRef {
address: 95,
size: 1,
opcode: 465,
branch_dest: None,
},
),
kind: None,
branch_from: None,
branch_to: None,
arg_diff: [],
},
]

View File

@@ -29,11 +29,3 @@ expression: output
[(Address(76), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".dword", 65534), Normal, 10), (BranchDest(41), Normal, 0), (Basic(" ~>"), Rotating(6), 0), (Eol, Normal, 0)]
[(Address(80), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".dword", 65534), Normal, 10), (BranchDest(47), Normal, 0), (Basic(" ~>"), Rotating(7), 0), (Eol, Normal, 0)]
[(Address(84), Normal, 5), (Spacing(4), Normal, 0), (Opcode(".dword", 65534), Normal, 10), (BranchDest(53), Normal, 0), (Basic(" ~>"), Rotating(8), 0), (Eol, Normal, 0)]
[(Address(88), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 465), Normal, 10), (Eol, Normal, 0)]
[(Address(89), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 465), Normal, 10), (Eol, Normal, 0)]
[(Address(90), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 465), Normal, 10), (Eol, Normal, 0)]
[(Address(91), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 465), Normal, 10), (Eol, Normal, 0)]
[(Address(92), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 465), Normal, 10), (Eol, Normal, 0)]
[(Address(93), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 465), Normal, 10), (Eol, Normal, 0)]
[(Address(94), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 465), Normal, 10), (Eol, Normal, 0)]
[(Address(95), Normal, 5), (Spacing(4), Normal, 0), (Opcode("nop", 465), Normal, 10), (Eol, Normal, 0)]

View File

@@ -63,7 +63,7 @@ Object {
"int __cdecl test(int)",
),
address: 0,
size: 96,
size: 88,
kind: Function,
section: Some(
1,

View File

@@ -25,12 +25,13 @@ wsl = []
[dependencies]
anyhow = "1.0"
argp = "0.4"
cfg-if = "1.0"
const_format = "0.2"
cwdemangle = "1.0"
dirs = "6.0"
egui = "0.31"
egui_extras = "0.31"
egui = "0.32"
egui_extras = "0.32"
filetime = "0.2"
float-ord = "0.3"
font-kit = "0.14"
@@ -43,18 +44,19 @@ pollster = "0.4"
regex = "1.11"
rfd = { version = "0.15" } #, default-features = false, features = ['xdg-portal']
rlwinmdec = "1.1"
ron = "0.8"
ron = "0.10"
serde = { version = "1.0", features = ["derive"] }
time = { version = "0.3", features = ["formatting", "local-offset"] }
typed-path = "0.11"
winit = { version = "0.30", features = ["wayland-csd-adwaita"] }
winit = { version = "0.30", features = ["default"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Keep version in sync with egui
[dependencies.eframe]
version = "0.31"
version = "0.32"
features = [
"default_fonts",
"glow",
"persistence",
"wayland",
"x11",
@@ -63,10 +65,12 @@ default-features = false
# Keep version in sync with eframe
[dependencies.wgpu]
version = "24.0"
version = "25.0"
features = [
"dx12",
"metal",
"gles",
"vulkan",
"webgpu",
]
optional = true

View File

@@ -16,8 +16,8 @@ use globset::Glob;
use objdiff_core::{
build::watcher::{Watcher, create_watcher},
config::{
DEFAULT_WATCH_PATTERNS, ProjectConfig, ProjectConfigInfo, ProjectObject, ScratchConfig,
build_globset, default_watch_patterns, path::platform_path_serde_option,
ProjectConfig, ProjectConfigInfo, ProjectObject, ScratchConfig, build_globset,
default_ignore_patterns, default_watch_patterns, path::platform_path_serde_option,
save_project_config,
},
diff::DiffObjConfig,
@@ -219,6 +219,8 @@ pub struct AppConfig {
#[serde(default = "default_watch_patterns")]
pub watch_patterns: Vec<Glob>,
#[serde(default)]
pub ignore_patterns: Vec<Glob>,
#[serde(default)]
pub recent_projects: Vec<String>,
#[serde(default)]
pub diff_obj_config: DiffObjConfig,
@@ -239,7 +241,8 @@ impl Default for AppConfig {
build_target: false,
rebuild_on_changes: true,
auto_update_check: true,
watch_patterns: DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect(),
watch_patterns: default_watch_patterns(),
ignore_patterns: default_ignore_patterns(),
recent_projects: vec![],
diff_obj_config: Default::default(),
}
@@ -431,6 +434,7 @@ impl App {
app_path: Option<PathBuf>,
graphics_config: GraphicsConfig,
graphics_config_path: Option<PathBuf>,
project_dir: Option<Utf8PlatformPathBuf>,
) -> Self {
// Load previous app state (if any).
// Note that you must enable the `persistence` feature for this to work.
@@ -440,7 +444,17 @@ impl App {
app.appearance = appearance;
}
if let Some(config) = deserialize_config(storage) {
let mut state = AppState { config, ..Default::default() };
let state = AppState { config, ..Default::default() };
app.state = Arc::new(RwLock::new(state));
}
}
{
let mut state = app.state.write().unwrap();
if let Some(project_dir) = project_dir
&& state.config.project_dir.as_ref().is_none_or(|p| *p != project_dir)
{
state.set_project_dir(project_dir);
}
if state.config.project_dir.is_some() {
state.config_change = true;
state.watcher_change = true;
@@ -449,8 +463,6 @@ impl App {
state.queue_build = true;
}
app.view_state.config_state.queue_check_update = state.config.auto_update_check;
app.state = Arc::new(RwLock::new(state));
}
}
app.appearance.init_fonts(&cc.egui_ctx);
app.appearance.utc_offset = utc_offset;
@@ -461,11 +473,11 @@ impl App {
use eframe::egui_wgpu::wgpu::Backend;
let info = wgpu_render_state.adapter.get_info();
app.view_state.graphics_state.active_backend = match info.backend {
Backend::Empty => "Unknown",
Backend::Noop => "None",
Backend::Vulkan => "Vulkan",
Backend::Metal => "Metal",
Backend::Dx12 => "DirectX 12",
Backend::Gl => "OpenGL",
Backend::Gl => "OpenGL ES",
Backend::BrowserWebGpu => "WebGPU",
}
.to_string();
@@ -474,7 +486,7 @@ impl App {
#[cfg(feature = "glow")]
if let Some(gl) = &cc.gl {
use eframe::glow::HasContext;
app.view_state.graphics_state.active_backend = "OpenGL (Fallback)".to_string();
app.view_state.graphics_state.active_backend = "OpenGL".to_string();
app.view_state.graphics_state.active_device =
unsafe { gl.get_parameter_string(0x1F01) }; // GL_RENDERER
}
@@ -526,15 +538,13 @@ impl App {
mod_check = true;
}
if mod_check {
if let Some(info) = &state.project_config_info {
if let Some(last_ts) = info.timestamp {
if file_modified(&info.path, last_ts) {
if mod_check
&& let Some(info) = &state.project_config_info
&& let Some(last_ts) = info.timestamp
&& file_modified(&info.path, last_ts)
{
state.config_change = true;
}
}
}
}
if state.config_change {
state.config_change = false;
@@ -553,11 +563,17 @@ impl App {
if let Some(project_dir) = &state.config.project_dir {
match build_globset(&state.config.watch_patterns)
.map_err(anyhow::Error::new)
.and_then(|globset| {
.and_then(|patterns| {
build_globset(&state.config.ignore_patterns)
.map(|ignore_patterns| (patterns, ignore_patterns))
.map_err(anyhow::Error::new)
})
.and_then(|(patterns, ignore_patterns)| {
create_watcher(
self.modified.clone(),
project_dir.as_ref(),
globset,
patterns,
ignore_patterns,
egui_waker(ctx),
)
.map_err(anyhow::Error::new)
@@ -581,24 +597,22 @@ impl App {
state.queue_build = true;
}
if let Some(result) = &diff_state.build {
if mod_check {
if let Some((obj, _)) = &result.first_obj {
if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) {
if file_modified(path, timestamp) {
if let Some(result) = &diff_state.build
&& mod_check
{
if let Some((obj, _)) = &result.first_obj
&& let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp)
&& file_modified(path, timestamp)
{
state.queue_reload = true;
}
}
}
if let Some((obj, _)) = &result.second_obj {
if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) {
if file_modified(path, timestamp) {
if let Some((obj, _)) = &result.second_obj
&& let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp)
&& file_modified(path, timestamp)
{
state.queue_reload = true;
}
}
}
}
}
// Don't clear `queue_build` if a build is running. A file may have been modified during
// the build, so we'll start another build after the current one finishes.
@@ -618,16 +632,15 @@ impl App {
state.queue_reload = false;
}
if graphics_state.should_relaunch {
if let Some(app_path) = &self.app_path {
if let Ok(mut guard) = self.relaunch_path.lock() {
if graphics_state.should_relaunch
&& let Some(app_path) = &self.app_path
&& let Ok(mut guard) = self.relaunch_path.lock()
{
*guard = Some(app_path.clone());
self.should_relaunch = true;
}
}
}
}
}
impl eframe::App for App {
/// Called each time the UI needs repainting, which may be many times per second.
@@ -665,6 +678,9 @@ impl eframe::App for App {
let side_panel_available = diff_state.current_view == View::SymbolDiff;
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
// Temporarily use pre-egui 0.32 menu. ComboBox within menu
// is currently broken. Issue TBD
#[allow(deprecated)]
egui::menu::bar(ui, |ui| {
if ui
.add_enabled(
@@ -677,15 +693,16 @@ impl eframe::App for App {
*show_side_panel = !*show_side_panel;
}
ui.separator();
ui.menu_button("File", |ui| {
let bar_state = egui::menu::BarState::load(ui.ctx(), ui.id());
egui::menu::menu_button(ui, "File", |ui| {
#[cfg(debug_assertions)]
if ui.button("Debug…").clicked() {
*show_debug = !*show_debug;
ui.close_menu();
ui.close();
}
if ui.button("Project…").clicked() {
*show_project_config = !*show_project_config;
ui.close_menu();
ui.close();
}
let recent_projects = if let Ok(guard) = state.read() {
guard.config.recent_projects.clone()
@@ -694,8 +711,12 @@ impl eframe::App for App {
};
if recent_projects.is_empty() {
ui.add_enabled(false, egui::Button::new("Recent projects…"));
} else {
ui.menu_button("Recent Projects…", |ui| {
} else if let Some(menu_root) = bar_state.as_ref() {
egui::menu::submenu_button(
ui,
menu_root.menu_state.clone(),
"Recent Projects…",
|ui| {
if ui.button("Clear").clicked() {
state.write().unwrap().config.recent_projects.clear();
};
@@ -706,37 +727,40 @@ impl eframe::App for App {
.write()
.unwrap()
.set_project_dir(Utf8PlatformPathBuf::from(path));
ui.close_menu();
ui.close();
}
}
});
},
);
} else {
ui.add_enabled(false, egui::Button::new("Recent projects…"));
}
if ui.button("Appearance…").clicked() {
*show_appearance_config = !*show_appearance_config;
ui.close_menu();
ui.close();
}
if ui.button("Graphics…").clicked() {
*show_graphics = !*show_graphics;
ui.close_menu();
ui.close();
}
if ui.button("Quit").clicked() {
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
}
});
ui.menu_button("Tools", |ui| {
egui::menu::menu_button(ui, "Tools", |ui| {
if ui.button("Demangle…").clicked() {
*show_demangle = !*show_demangle;
ui.close_menu();
ui.close();
}
if ui.button("Rlwinm Decoder…").clicked() {
*show_rlwinm_decode = !*show_rlwinm_decode;
ui.close_menu();
ui.close();
}
});
ui.menu_button("Diff Options", |ui| {
egui::menu::menu_button(ui, "Diff Options", |ui| {
if ui.button("Arch Settings…").clicked() {
*show_arch_config = !*show_arch_config;
ui.close_menu();
ui.close();
}
let mut state = state.write().unwrap();
let response = ui

View File

@@ -0,0 +1,63 @@
// Originally from https://gist.github.com/suluke/e0c672492126be0a4f3b4f0e1115d77c
//! Extend `argp` to be better integrated with the `cargo` ecosystem
//!
//! For now, this only adds a --version/-V option which causes early-exit.
use std::ffi::OsStr;
use argp::{EarlyExit, FromArgs, TopLevelCommand, parser::ParseGlobalOptions};
struct ArgsOrVersion<T>(T)
where T: FromArgs;
impl<T> TopLevelCommand for ArgsOrVersion<T> where T: FromArgs {}
impl<T> FromArgs for ArgsOrVersion<T>
where T: FromArgs
{
fn _from_args(
command_name: &[&str],
args: &[&OsStr],
parent: Option<&mut dyn ParseGlobalOptions>,
) -> Result<Self, EarlyExit> {
/// Also use argp for catching `--version`-only invocations
#[derive(FromArgs)]
struct Version {
/// Print version information and exit.
#[argp(switch, short = 'V')]
pub version: bool,
}
match Version::from_args(command_name, args) {
Ok(v) => {
if v.version {
println!(
"{} {}",
command_name.first().unwrap_or(&""),
env!("CARGO_PKG_VERSION"),
);
std::process::exit(0);
} else {
// Pass through empty arguments
T::_from_args(command_name, args, parent).map(Self)
}
}
Err(exit) => match exit {
EarlyExit::Help(_help) => {
// TODO: Chain help info from Version
// For now, we just put the switch on T as well
T::from_args(command_name, &["--help"]).map(Self)
}
EarlyExit::Err(_) => T::_from_args(command_name, args, parent).map(Self),
},
}
}
}
/// Create a `FromArgs` type from the current processs `env::args`.
///
/// This function will exit early from the current process if argument parsing was unsuccessful or if information like `--help` was requested.
/// Error messages will be printed to stderr, and `--help` output to stdout.
pub fn from_env<T>() -> T
where T: TopLevelCommand {
argp::parse_args_or_exit::<ArgsOrVersion<T>>(argp::DEFAULT).0
}

View File

@@ -1,6 +1,6 @@
use anyhow::Result;
use globset::Glob;
use objdiff_core::config::{DEFAULT_WATCH_PATTERNS, try_project_config};
use objdiff_core::config::{default_ignore_patterns, default_watch_patterns, try_project_config};
use typed_path::{Utf8UnixComponent, Utf8UnixPath};
use crate::app::{AppState, ObjectConfig};
@@ -96,8 +96,15 @@ pub fn load_project_config(state: &mut AppState) -> Result<()> {
.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.config.watch_patterns = default_watch_patterns();
}
if let Some(ignore_patterns) = &project_config.ignore_patterns {
state.config.ignore_patterns = ignore_patterns
.iter()
.map(|s| Glob::new(s))
.collect::<Result<Vec<Glob>, globset::Error>>()?;
} else {
state.config.ignore_patterns = default_ignore_patterns();
}
state.watcher_change = true;
state.objects = project_config

View File

@@ -3,6 +3,7 @@
mod app;
mod app_config;
mod argp_version;
mod config;
mod fonts;
mod hotkeys;
@@ -11,19 +12,83 @@ mod update;
mod views;
use std::{
ffi::OsStr,
fmt::Display,
path::PathBuf,
process::ExitCode,
rc::Rc,
str::FromStr,
sync::{Arc, Mutex},
};
use anyhow::{Result, ensure};
use argp::{FromArgValue, FromArgs};
use cfg_if::cfg_if;
use objdiff_core::config::path::check_path_buf;
use time::UtcOffset;
use tracing_subscriber::EnvFilter;
use tracing_subscriber::{EnvFilter, filter::LevelFilter};
use typed_path::Utf8PlatformPathBuf;
use crate::views::graphics::{GraphicsBackend, GraphicsConfig, load_graphics_config};
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
enum LogLevel {
Error,
Warn,
Info,
Debug,
Trace,
}
impl FromStr for LogLevel {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"error" => Self::Error,
"warn" => Self::Warn,
"info" => Self::Info,
"debug" => Self::Debug,
"trace" => Self::Trace,
_ => return Err(()),
})
}
}
impl Display for LogLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
LogLevel::Error => "error",
LogLevel::Warn => "warn",
LogLevel::Info => "info",
LogLevel::Debug => "debug",
LogLevel::Trace => "trace",
})
}
}
impl FromArgValue for LogLevel {
fn from_arg_value(value: &OsStr) -> Result<Self, String> {
String::from_arg_value(value)
.and_then(|s| Self::from_str(&s).map_err(|_| "Invalid log level".to_string()))
}
}
#[derive(FromArgs, PartialEq, Debug)]
/// A local diffing tool for decompilation projects.
struct TopLevel {
#[argp(option, short = 'L')]
/// Minimum logging level. (Default: info)
/// Possible values: error, warn, info, debug, trace
log_level: Option<LogLevel>,
#[argp(option, short = 'p')]
/// Path to the project directory.
project_dir: Option<PathBuf>,
/// Print version information and exit.
#[argp(switch, short = 'V')]
version: bool,
}
fn load_icon() -> Result<egui::IconData> {
let decoder = png::Decoder::new(include_bytes!("../assets/icon_64.png").as_ref());
let mut reader = decoder.read_info()?;
@@ -38,23 +103,63 @@ fn load_icon() -> Result<egui::IconData> {
const APP_NAME: &str = "objdiff";
fn main() -> ExitCode {
// Log to stdout (if you run with `RUST_LOG=debug`).
tracing_subscriber::fmt()
let args: TopLevel = argp_version::from_env();
let builder = tracing_subscriber::fmt();
if let Some(level) = args.log_level {
builder
.with_max_level(match level {
LogLevel::Error => LevelFilter::ERROR,
LogLevel::Warn => LevelFilter::WARN,
LogLevel::Info => LevelFilter::INFO,
LogLevel::Debug => LevelFilter::DEBUG,
LogLevel::Trace => LevelFilter::TRACE,
})
.init();
} else {
builder
.with_env_filter(
EnvFilter::builder()
// Default to info level
.with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into())
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy()
// This module is noisy at info level
.add_directive("wgpu_core::device::resource=warn".parse().unwrap()),
)
.init();
}
// Because localtime_r is unsound in multithreaded apps,
// we must call this before initializing eframe.
// https://github.com/time-rs/time/issues/293
let utc_offset = UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC);
// Resolve project directory if provided
let project_dir = if let Some(path) = args.project_dir {
match path.canonicalize() {
Ok(path) => {
// Ensure the path is a directory
if path.is_dir() {
match check_path_buf(path) {
Ok(path) => Some(path),
Err(e) => {
log::error!("Failed to convert project directory to UTF-8 path: {}", e);
None
}
}
} else {
log::error!("Project directory is not a directory: {}", path.display());
None
}
}
Err(e) => {
log::error!("Failed to canonicalize project directory: {}", e);
None
}
}
} else {
None
};
let app_path = std::env::current_exe().ok();
let exec_path: Rc<Mutex<Option<PathBuf>>> = Rc::new(Mutex::new(None));
let mut native_options = eframe::NativeOptions {
@@ -95,7 +200,8 @@ fn main() -> ExitCode {
GraphicsBackend::Dx12 => wgpu::Backends::DX12,
GraphicsBackend::Metal => wgpu::Backends::METAL,
GraphicsBackend::Vulkan => wgpu::Backends::VULKAN,
GraphicsBackend::OpenGL => wgpu::Backends::GL,
GraphicsBackend::OpenGLES => wgpu::Backends::GL,
GraphicsBackend::OpenGL => wgpu::Backends::empty(),
};
WgpuSetup::CreateNew(setup)
}
@@ -112,6 +218,7 @@ fn main() -> ExitCode {
app_path.clone(),
graphics_config.clone(),
graphics_config_path.clone(),
project_dir.clone(),
) {
eframe_error = Some(e);
}
@@ -138,6 +245,7 @@ fn main() -> ExitCode {
app_path.clone(),
graphics_config.clone(),
graphics_config_path.clone(),
project_dir.clone(),
) {
eframe_error = Some(e);
} else {
@@ -160,6 +268,7 @@ fn main() -> ExitCode {
app_path,
graphics_config,
graphics_config_path,
project_dir,
) {
eframe_error = Some(e);
} else {
@@ -172,8 +281,9 @@ fn main() -> ExitCode {
}
// Attempt to relaunch application from the updated path
if let Ok(mut guard) = exec_path.lock() {
if let Some(path) = guard.take() {
if let Ok(mut guard) = exec_path.lock()
&& let Some(path) = guard.take()
{
cfg_if! {
if #[cfg(unix)] {
let e = exec::Command::new(path)
@@ -191,7 +301,6 @@ fn main() -> ExitCode {
}
}
}
}
};
ExitCode::SUCCESS
}
@@ -203,6 +312,7 @@ fn run_eframe(
app_path: Option<PathBuf>,
graphics_config: GraphicsConfig,
graphics_config_path: Option<PathBuf>,
project_dir: Option<Utf8PlatformPathBuf>,
) -> Result<(), eframe::Error> {
eframe::run_native(
APP_NAME,
@@ -215,6 +325,7 @@ fn run_eframe(
app_path,
graphics_config,
graphics_config_path,
project_dir,
)))
}),
)

View File

@@ -11,6 +11,7 @@ pub struct Appearance {
pub ui_font: FontId,
pub code_font: FontId,
pub diff_colors: Vec<Color32>,
pub diff_bg_color: Option<Color32>,
pub theme: egui::Theme,
// Applied by theme
@@ -67,6 +68,7 @@ impl Default for Appearance {
replace_color: Color32::LIGHT_BLUE,
insert_color: Color32::GREEN,
delete_color: Color32::from_rgb(200, 40, 41),
diff_bg_color: None,
utc_offset: UtcOffset::UTC,
fonts: FontState::default(),
next_ui_font: None,
@@ -103,6 +105,9 @@ impl Appearance {
match self.theme {
egui::Theme::Dark => {
style.visuals = egui::Visuals::dark();
if let Some(diff_bg_color) = self.diff_bg_color {
style.visuals.faint_bg_color = diff_bg_color;
}
self.text_color = Color32::GRAY;
self.emphasized_text_color = Color32::LIGHT_GRAY;
self.deemphasized_text_color = Color32::DARK_GRAY;
@@ -114,6 +119,9 @@ impl Appearance {
}
egui::Theme::Light => {
style.visuals = egui::Visuals::light();
if let Some(diff_bg_color) = self.diff_bg_color {
style.visuals.faint_bg_color = diff_bg_color;
}
self.text_color = Color32::GRAY;
self.emphasized_text_color = Color32::DARK_GRAY;
self.deemphasized_text_color = Color32::LIGHT_GRAY;
@@ -294,6 +302,21 @@ pub fn appearance_window(ctx: &egui::Context, show: &mut bool, appearance: &mut
appearance,
);
ui.separator();
ui.horizontal(|ui| {
ui.label("Diff fill color:");
let mut diff_bg_color =
appearance.diff_bg_color.unwrap_or_else(|| match appearance.theme {
egui::Theme::Dark => egui::Visuals::dark().faint_bg_color,
egui::Theme::Light => egui::Visuals::light().faint_bg_color,
});
if ui.color_edit_button_srgba(&mut diff_bg_color).changed() {
appearance.diff_bg_color = Some(diff_bg_color);
}
if ui.button("Reset").clicked() {
appearance.diff_bg_color = None;
}
});
ui.separator();
ui.label("Diff colors:");
if ui.button("Reset").clicked() {
appearance.diff_colors = DEFAULT_COLOR_ROTATION.to_vec();

View File

@@ -5,12 +5,12 @@ use std::{mem::take, path::MAIN_SEPARATOR};
#[cfg(all(windows, feature = "wsl"))]
use anyhow::{Context, Result};
use egui::{
CollapsingHeader, FontFamily, FontId, RichText, SelectableLabel, TextFormat, Widget,
output::OpenUrl, text::LayoutJob,
CollapsingHeader, FontFamily, FontId, RichText, TextFormat, Widget, output::OpenUrl,
text::LayoutJob,
};
use globset::Glob;
use objdiff_core::{
config::{DEFAULT_WATCH_PATTERNS, path::check_path_buf},
config::{default_ignore_patterns, default_watch_patterns, path::check_path_buf},
diff::{
CONFIG_GROUPS, ConfigEnum, ConfigEnumVariantInfo, ConfigPropertyId, ConfigPropertyKind,
ConfigPropertyValue,
@@ -41,6 +41,7 @@ pub struct ConfigViewState {
pub build_running: bool,
pub queue_build: bool,
pub watch_pattern_text: String,
pub ignore_pattern_text: String,
pub object_search: String,
pub filter_diffable: bool,
pub filter_incomplete: bool,
@@ -185,8 +186,8 @@ pub fn config_ui(
if result.update_available {
ui.colored_label(appearance.insert_color, "Update available");
ui.horizontal(|ui| {
if let Some(bin_name) = &result.found_binary {
if ui
if let Some(bin_name) = &result.found_binary
&& ui
.add_enabled(!config_state.update_running, egui::Button::new("Automatic"))
.on_hover_text_at_pointer(
"Automatically download and replace the current build",
@@ -195,7 +196,6 @@ pub fn config_ui(
{
config_state.queue_update = Some(bin_name.clone());
}
}
if ui
.button("Manual")
.on_hover_text_at_pointer("Open a link to the latest release on GitHub")
@@ -278,7 +278,7 @@ pub fn config_ui(
{
filters_text = filters_text.color(appearance.replace_color);
}
egui::menu::menu_button(ui, filters_text, |ui| {
egui::containers::menu::MenuButton::new(filters_text).ui(ui, |ui| {
ui.checkbox(&mut config_state.filter_diffable, "Diffable")
.on_hover_text_at_pointer("Only show objects with a source file");
ui.checkbox(&mut config_state.filter_incomplete, "Incomplete")
@@ -329,14 +329,14 @@ pub fn config_ui(
});
});
}
if new_selected_index != selected_index {
if let Some(idx) = new_selected_index {
if new_selected_index != selected_index
&& let Some(idx) = new_selected_index
{
// Will set obj_changed, which will trigger a rebuild
let config = objects[idx].clone();
state_guard.set_selected_obj(config);
}
}
}
fn display_unit(
ui: &mut egui::Ui,
@@ -355,7 +355,7 @@ fn display_unit(
} else {
appearance.text_color
};
let response = SelectableLabel::new(
let response = egui::Button::selectable(
selected,
RichText::new(name)
.font(FontId {
@@ -374,8 +374,8 @@ fn display_unit(
}
fn object_context_ui(ui: &mut egui::Ui, object: &ObjectConfig) {
if let Some(source_path) = &object.source_path {
if ui
if let Some(source_path) = &object.source_path
&& ui
.button("Open source file")
.on_hover_text("Open the source file in the default editor")
.clicked()
@@ -384,8 +384,7 @@ fn object_context_ui(ui: &mut egui::Ui, object: &ObjectConfig) {
if let Err(e) = open::that_detached(source_path.as_str()) {
log::error!("Failed to open source file: {e}");
}
ui.close_menu();
}
ui.close();
}
}
@@ -792,20 +791,49 @@ fn split_obj_config_ui(
state.watcher_change = true;
};
state.watcher_change |= patterns_ui(
ui,
"File patterns",
&mut state.config.watch_patterns,
&mut config_state.watch_pattern_text,
appearance,
state.project_config_info.is_some(),
default_watch_patterns,
);
state.watcher_change |= patterns_ui(
ui,
"Ignore patterns",
&mut state.config.ignore_patterns,
&mut config_state.ignore_pattern_text,
appearance,
state.project_config_info.is_some(),
default_ignore_patterns,
);
}
fn patterns_ui(
ui: &mut egui::Ui,
text: &str,
patterns: &mut Vec<Glob>,
pattern_text: &mut String,
appearance: &Appearance,
has_project_config: bool,
on_reset: impl FnOnce() -> Vec<Glob>,
) -> bool {
let mut change = false;
ui.horizontal(|ui| {
ui.label(RichText::new("File patterns").color(appearance.text_color));
ui.label(RichText::new(text).color(appearance.text_color));
if ui
.add_enabled(state.project_config_info.is_none(), egui::Button::new("Reset"))
.add_enabled(!has_project_config, egui::Button::new("Reset"))
.on_disabled_hover_text(CONFIG_DISABLED_TEXT)
.clicked()
{
state.config.watch_patterns =
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect();
state.watcher_change = true;
*patterns = on_reset();
change = true;
}
});
let mut remove_at: Option<usize> = None;
for (idx, glob) in state.config.watch_patterns.iter().enumerate() {
for (idx, glob) in patterns.iter().enumerate() {
ui.horizontal(|ui| {
ui.label(
RichText::new(glob.to_string())
@@ -813,7 +841,7 @@ fn split_obj_config_ui(
.family(FontFamily::Monospace),
);
if ui
.add_enabled(state.project_config_info.is_none(), egui::Button::new("-").small())
.add_enabled(!has_project_config, egui::Button::new("-").small())
.on_disabled_hover_text(CONFIG_DISABLED_TEXT)
.clicked()
{
@@ -822,27 +850,27 @@ fn split_obj_config_ui(
});
}
if let Some(idx) = remove_at {
state.config.watch_patterns.remove(idx);
state.watcher_change = true;
patterns.remove(idx);
change = true;
}
ui.horizontal(|ui| {
ui.add_enabled(
state.project_config_info.is_none(),
egui::TextEdit::singleline(&mut config_state.watch_pattern_text).desired_width(100.0),
!has_project_config,
egui::TextEdit::singleline(pattern_text).desired_width(100.0),
)
.on_disabled_hover_text(CONFIG_DISABLED_TEXT);
if ui
.add_enabled(state.project_config_info.is_none(), egui::Button::new("+").small())
.add_enabled(!has_project_config, egui::Button::new("+").small())
.on_disabled_hover_text(CONFIG_DISABLED_TEXT)
.clicked()
&& let Ok(glob) = Glob::new(pattern_text)
{
if let Ok(glob) = Glob::new(&config_state.watch_pattern_text) {
state.config.watch_patterns.push(glob);
state.watcher_change = true;
config_state.watch_pattern_text.clear();
}
patterns.push(glob);
change = true;
pattern_text.clear();
}
});
change
}
pub fn arch_config_window(

View File

@@ -164,7 +164,7 @@ pub(crate) fn data_row_ui(
write_text(byte_text.as_str(), byte_color, &mut job, appearance.code_font.clone());
cur_addr += 1;
cur_addr_actual += 1;
if cur_addr % 8 == 0 {
if cur_addr.is_multiple_of(8) {
write_text(" ", base_color, &mut job, appearance.code_font.clone());
}
}

View File

@@ -128,11 +128,11 @@ pub fn diff_view_ui(
let mut navigation = current_navigation.clone();
if let Some((_symbol, symbol_diff, _symbol_idx)) = left_ctx.symbol {
// If a matching symbol appears, select it
if !right_ctx.has_symbol() {
if let Some(target_symbol_ref) = symbol_diff.target_symbol {
if !right_ctx.has_symbol()
&& let Some(target_symbol_ref) = symbol_diff.target_symbol
{
navigation.right_symbol = Some(target_symbol_ref);
}
}
} else if navigation.left_symbol.is_some()
&& left_ctx.obj.is_some()
&& left_ctx.section.is_none()
@@ -142,11 +142,11 @@ pub fn diff_view_ui(
}
if let Some((_symbol, symbol_diff, _symbol_idx)) = right_ctx.symbol {
// If a matching symbol appears, select it
if !left_ctx.has_symbol() {
if let Some(target_symbol_ref) = symbol_diff.target_symbol {
if !left_ctx.has_symbol()
&& let Some(target_symbol_ref) = symbol_diff.target_symbol
{
navigation.left_symbol = Some(target_symbol_ref);
}
}
} else if navigation.right_symbol.is_some()
&& right_ctx.obj.is_some()
&& right_ctx.section.is_none()
@@ -247,17 +247,16 @@ pub fn diff_view_ui(
// Third row
if left_ctx.has_symbol() && right_ctx.has_symbol() {
if state.current_view == View::FunctionDiff
if (state.current_view == View::FunctionDiff
&& ui
.button("Change target")
.on_hover_text_at_pointer("Choose a different symbol to use as the target")
.clicked()
|| hotkeys::consume_change_target_shortcut(ui.ctx())
|| hotkeys::consume_change_target_shortcut(ui.ctx()))
&& let Some(symbol_ref) = state.symbol_state.right_symbol.as_ref()
{
if let Some(symbol_ref) = state.symbol_state.right_symbol.as_ref() {
ret = Some(DiffViewAction::SelectingLeft(symbol_ref.clone()));
}
}
} else if left_ctx.status.success && !left_ctx.has_symbol() {
ui.horizontal(|ui| {
let mut search = state.search.clone();
@@ -409,19 +408,18 @@ pub fn diff_view_ui(
if needs_separator {
ui.separator();
}
if ui
if (ui
.button("Change base")
.on_hover_text_at_pointer(
"Choose a different symbol to use as the base",
)
.clicked()
|| hotkeys::consume_change_base_shortcut(ui.ctx())
|| hotkeys::consume_change_base_shortcut(ui.ctx()))
&& let Some(symbol_ref) = state.symbol_state.left_symbol.as_ref()
{
if let Some(symbol_ref) = state.symbol_state.left_symbol.as_ref() {
ret = Some(DiffViewAction::SelectingRight(symbol_ref.clone()));
}
}
}
} else if right_ctx.status.success && !right_ctx.has_symbol() {
let mut search = state.search.clone();
let response =
@@ -583,8 +581,8 @@ pub fn diff_view_ui(
) {
ret = Some(action);
}
} else if column == 1 {
if let Some(action) = diff_col_ui(
} else if column == 1
&& let Some(action) = diff_col_ui(
ui,
state,
appearance,
@@ -594,10 +592,10 @@ pub fn diff_view_ui(
available_width,
open_sections.1,
diff_config,
) {
)
{
ret = Some(action);
}
}
});
}
});
@@ -621,9 +619,7 @@ fn symbol_label_ui(
.font(appearance.code_font.clone())
.color(appearance.highlight_color),
)
.selectable(false)
// TODO .show_tooltip_when_elided(false)
// https://github.com/emilk/egui/commit/071e090e2b2601e5ed4726a63a753188503dfaf2
.show_tooltip_when_elided(false)
.ui(ui)
.on_hover_ui_at_pointer(|ui| symbol_hover_ui(ui, ctx, symbol_idx, appearance))
.context_menu(|ui| {
@@ -873,7 +869,7 @@ pub fn context_menu_items_ui(
}
if ui.button(job).clicked() {
ui.ctx().copy_text(value);
ui.close_menu();
ui.close();
}
}
ContextItem::Navigate { label, symbol_index, kind } => {
@@ -883,7 +879,7 @@ pub fn context_menu_items_ui(
symbol_index,
column,
)));
ui.close_menu();
ui.close();
}
}
ContextItem::Separator => {

View File

@@ -1,8 +1,7 @@
use core::any::Any;
use egui::ScrollArea;
use objdiff_core::{
arch::ppc::ExceptionInfo,
obj::{Object, Symbol},
};
use objdiff_core::{arch::ppc::ExceptionInfo, obj::Object};
use crate::views::{appearance::Appearance, function_diff::FunctionDiffContext};
@@ -26,19 +25,19 @@ fn decode_extab(extab: &ExceptionInfo) -> String {
text
}
fn find_extab_entry<'a>(_obj: &'a Object, _symbol: &Symbol) -> Option<&'a ExceptionInfo> {
// TODO
// obj.arch.ppc().and_then(|ppc| ppc.extab_for_symbol(symbol))
None
fn find_extab_entry(obj: &Object, symbol_index: usize) -> Option<&ExceptionInfo> {
(obj.arch.as_ref() as &dyn Any)
.downcast_ref::<objdiff_core::arch::ppc::ArchPpc>()
.and_then(|ppc| ppc.extab_for_symbol(symbol_index))
}
fn extab_text_ui(
ui: &mut egui::Ui,
ctx: FunctionDiffContext<'_>,
symbol: &Symbol,
symbol_index: usize,
appearance: &Appearance,
) -> Option<()> {
if let Some(extab_entry) = find_extab_entry(ctx.obj, symbol) {
if let Some(extab_entry) = find_extab_entry(ctx.obj, symbol_index) {
let text = decode_extab(extab_entry);
ui.colored_label(appearance.replace_color, &text);
return Some(());
@@ -58,10 +57,8 @@ pub(crate) fn extab_ui(
ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace);
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
if let Some(symbol) =
ctx.symbol_ref.and_then(|symbol_ref| ctx.obj.symbols.get(symbol_ref))
{
extab_text_ui(ui, ctx, symbol, appearance);
if let Some(symbol_index) = ctx.symbol_ref {
extab_text_ui(ui, ctx, symbol_index, appearance);
}
});
});

View File

@@ -26,7 +26,8 @@ pub enum GraphicsBackend {
Vulkan,
Metal,
Dx12,
OpenGL,
OpenGL, // glow
OpenGLES, // wgpu
}
static ALL_BACKENDS: &[GraphicsBackend] = &[
@@ -35,6 +36,7 @@ static ALL_BACKENDS: &[GraphicsBackend] = &[
GraphicsBackend::Metal,
GraphicsBackend::Dx12,
GraphicsBackend::OpenGL,
GraphicsBackend::OpenGLES,
];
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
@@ -54,7 +56,7 @@ pub fn load_graphics_config(path: &Path) -> Result<Option<GraphicsConfig>> {
pub fn save_graphics_config(path: &Path, config: &GraphicsConfig) -> Result<()> {
let file = BufWriter::new(File::create(path)?);
ron::ser::to_writer(file, config)?;
ron::Options::default().to_io_writer(file, config)?;
Ok(())
}
@@ -67,7 +69,8 @@ impl GraphicsBackend {
}
GraphicsBackend::Metal => cfg!(all(feature = "wgpu", target_os = "macos")),
GraphicsBackend::Dx12 => cfg!(all(feature = "wgpu", target_os = "windows")),
GraphicsBackend::OpenGL => true,
GraphicsBackend::OpenGL => cfg!(feature = "glow"),
GraphicsBackend::OpenGLES => cfg!(all(feature = "wgpu", target_os = "windows")),
}
}
@@ -78,6 +81,7 @@ impl GraphicsBackend {
GraphicsBackend::Metal => "Metal",
GraphicsBackend::Dx12 => "DirectX 12",
GraphicsBackend::OpenGL => "OpenGL",
GraphicsBackend::OpenGLES => "OpenGL ES",
}
}
}

View File

@@ -1,8 +1,8 @@
use std::mem::take;
use egui::{
CollapsingHeader, Color32, Id, OpenUrl, ScrollArea, SelectableLabel, Ui, Widget,
style::ScrollAnimation, text::LayoutJob,
CollapsingHeader, Color32, Id, OpenUrl, ScrollArea, Ui, Widget, style::ScrollAnimation,
text::LayoutJob,
};
use objdiff_core::{
diff::{
@@ -142,6 +142,11 @@ impl DiffViewState {
JobResult::ObjDiff(result) => {
self.build = take(result);
// Clear reload flag so that we don't reload the view immediately
if let Ok(mut state) = state.write() {
state.queue_reload = false;
}
// TODO: where should this go?
if let Some(result) = self.post_build_nav.take() {
self.current_view = result.view;
@@ -211,20 +216,20 @@ impl DiffViewState {
let mut resolved_left = self.resolve_symbol(nav.left_symbol, 0);
let mut resolved_right = self.resolve_symbol(nav.right_symbol, 1);
if let Some(resolved_right) = &resolved_right {
if resolved_left.is_none() {
if let Some(resolved_right) = &resolved_right
&& resolved_left.is_none()
{
resolved_left = resolved_right
.target_symbol
.and_then(|idx| self.resolve_symbol(Some(idx), 0));
}
}
if let Some(resolved_left) = &resolved_left {
if resolved_right.is_none() {
if let Some(resolved_left) = &resolved_left
&& resolved_right.is_none()
{
resolved_right = resolved_left
.target_symbol
.and_then(|idx| self.resolve_symbol(Some(idx), 1));
}
}
let resolved_nav = resolve_navigation(nav.kind, resolved_left, resolved_right);
if (resolved_nav.left_symbol.is_some() && resolved_nav.right_symbol.is_some())
|| (resolved_nav.left_symbol.is_none() && resolved_nav.right_symbol.is_none())
@@ -500,16 +505,16 @@ pub fn symbol_context_menu_ui(
ret = Some(action);
}
if let Some(section) = section {
if ui.button("Map symbol").clicked() {
if let Some(section) = section
&& ui.button("Map symbol").clicked()
{
let symbol_ref = SymbolRefByName::new(symbol, Some(section));
if column == 0 {
ret = Some(DiffViewAction::SelectingRight(symbol_ref));
} else {
ret = Some(DiffViewAction::SelectingLeft(symbol_ref));
}
ui.close_menu();
}
ui.close();
}
});
ret
@@ -586,7 +591,7 @@ fn symbol_ui(
write_text(") ", appearance.text_color, &mut job, appearance.code_font.clone());
}
write_text(name, appearance.highlight_color, &mut job, appearance.code_font.clone());
let response = SelectableLabel::new(selected, job)
let response = egui::Button::selectable(selected, job)
.ui(ui)
.on_hover_ui_at_pointer(|ui| symbol_hover_ui(ui, ctx, symbol_idx, appearance));
response.context_menu(|ui| {
@@ -664,11 +669,11 @@ pub fn symbol_list_ui(
let mut ret = None;
ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| {
let mut show_mapped_symbols = state.show_mapped_symbols;
if let SymbolFilter::Mapping(_, _) = filter {
if ui.checkbox(&mut show_mapped_symbols, "Show mapped symbols").changed() {
if let SymbolFilter::Mapping(_, _) = filter
&& ui.checkbox(&mut show_mapped_symbols, "Show mapped symbols").changed()
{
ret = Some(DiffViewAction::SetShowMappedSymbols(show_mapped_symbols));
}
}
let section_display = display_sections(
ctx.obj,
ctx.diff,

View File

@@ -0,0 +1,6 @@
[build]
target = "wasm32-wasip2"
[unstable]
build-std = ["panic_abort", "core", "alloc"]
build-std-features = ["compiler-builtins-mem"]

View File

@@ -17,12 +17,13 @@ build = "build.rs"
crate-type = ["cdylib"]
[features]
default = ["std"]
default = []
std = ["objdiff-core/std"]
[dependencies]
log = { version = "0.4", default-features = false }
regex = { version = "1.11", default-features = false, features = ["unicode-case"] }
wit-bindgen = { version = "0.44", default-features = false, features = ["macros"] }
xxhash-rust = { version = "0.8", default-features = false, features = ["xxh3"] }
[dependencies.objdiff-core]
@@ -33,8 +34,5 @@ features = ["arm", "arm64", "mips", "ppc", "superh", "x86", "dwarf"]
[target.'cfg(target_family = "wasm")'.dependencies]
talc = { version = "4.4", default-features = false, features = ["lock_api"] }
[target.'cfg(target_os = "wasi")'.dependencies]
wit-bindgen = { version = "0.42", default-features = false, features = ["macros"] }
[build-dependencies]
wit-deps = "0.5"

View File

@@ -1,12 +1,12 @@
{
"name": "objdiff-wasm",
"version": "3.0.0-beta.11",
"version": "3.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "objdiff-wasm",
"version": "3.0.0-beta.11",
"version": "3.0.1",
"license": "MIT OR Apache-2.0",
"devDependencies": {
"@biomejs/biome": "^1.9.3",
@@ -461,115 +461,6 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@microsoft/api-extractor": {
"version": "7.50.0",
"resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.50.0.tgz",
"integrity": "sha512-Ds/PHTiVzuENQsmXrJKkSdfgNkr/SDG/2rDef0AWl3BchAnXdO7gXaYsAkNx4gWiC4OngNA3fQfd3+BcQxP1DQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@microsoft/api-extractor-model": "7.30.3",
"@microsoft/tsdoc": "~0.15.1",
"@microsoft/tsdoc-config": "~0.17.1",
"@rushstack/node-core-library": "5.11.0",
"@rushstack/rig-package": "0.5.3",
"@rushstack/terminal": "0.15.0",
"@rushstack/ts-command-line": "4.23.5",
"lodash": "~4.17.15",
"minimatch": "~3.0.3",
"resolve": "~1.22.1",
"semver": "~7.5.4",
"source-map": "~0.6.1",
"typescript": "5.7.2"
},
"bin": {
"api-extractor": "bin/api-extractor"
}
},
"node_modules/@microsoft/api-extractor-model": {
"version": "7.30.3",
"resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.30.3.tgz",
"integrity": "sha512-yEAvq0F78MmStXdqz9TTT4PZ05Xu5R8nqgwI5xmUmQjWBQ9E6R2n8HB/iZMRciG4rf9iwI2mtuQwIzDXBvHn1w==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@microsoft/tsdoc": "~0.15.1",
"@microsoft/tsdoc-config": "~0.17.1",
"@rushstack/node-core-library": "5.11.0"
}
},
"node_modules/@microsoft/api-extractor/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/@microsoft/api-extractor/node_modules/minimatch": {
"version": "3.0.8",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz",
"integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==",
"dev": true,
"license": "ISC",
"optional": true,
"peer": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/@microsoft/api-extractor/node_modules/typescript": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
"integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/@microsoft/tsdoc": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz",
"integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/@microsoft/tsdoc-config": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.17.1.tgz",
"integrity": "sha512-UtjIFe0C6oYgTnad4q1QP4qXwLhe6tIpNTRStJ2RZEPIkqQPREAwE5spzVxsdn9UaEMUqhh0AqSx3X4nWAKXWw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@microsoft/tsdoc": "0.15.1",
"ajv": "~8.12.0",
"jju": "~1.4.0",
"resolve": "~1.22.2"
}
},
"node_modules/@module-federation/error-codes": {
"version": "0.8.4",
"resolved": "https://registry.npmjs.org/@module-federation/error-codes/-/error-codes-0.8.4.tgz",
@@ -1186,101 +1077,6 @@
"node": ">=16.0.0"
}
},
"node_modules/@rushstack/node-core-library": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.11.0.tgz",
"integrity": "sha512-I8+VzG9A0F3nH2rLpPd7hF8F7l5Xb7D+ldrWVZYegXM6CsKkvWc670RlgK3WX8/AseZfXA/vVrh0bpXe2Y2UDQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"ajv": "~8.13.0",
"ajv-draft-04": "~1.0.0",
"ajv-formats": "~3.0.1",
"fs-extra": "~11.3.0",
"import-lazy": "~4.0.0",
"jju": "~1.4.0",
"resolve": "~1.22.1",
"semver": "~7.5.4"
},
"peerDependencies": {
"@types/node": "*"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@rushstack/node-core-library/node_modules/ajv": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz",
"integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.4.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@rushstack/rig-package": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.5.3.tgz",
"integrity": "sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"resolve": "~1.22.1",
"strip-json-comments": "~3.1.1"
}
},
"node_modules/@rushstack/terminal": {
"version": "0.15.0",
"resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.15.0.tgz",
"integrity": "sha512-vXQPRQ+vJJn4GVqxkwRe+UGgzNxdV8xuJZY2zem46Y0p3tlahucH9/hPmLGj2i9dQnUBFiRnoM9/KW7PYw8F4Q==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@rushstack/node-core-library": "5.11.0",
"supports-color": "~8.1.1"
},
"peerDependencies": {
"@types/node": "*"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@rushstack/ts-command-line": {
"version": "4.23.5",
"resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-4.23.5.tgz",
"integrity": "sha512-jg70HfoK44KfSP3MTiL5rxsZH7X1ktX3cZs9Sl8eDu1/LxJSbPsh0MOFRC710lIuYYSgxWjI5AjbCBAl7u3RxA==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@rushstack/terminal": "0.15.0",
"@types/argparse": "1.0.38",
"argparse": "~1.0.9",
"string-argv": "~0.3.1"
}
},
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -1302,15 +1098,6 @@
"tslib": "^2.4.0"
}
},
"node_modules/@types/argparse": {
"version": "1.0.38",
"resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz",
"integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/acorn": {
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
@@ -1324,62 +1111,6 @@
"node": ">=0.4.0"
}
},
"node_modules/ajv": {
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-draft-04": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz",
"integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"peerDependencies": {
"ajv": "^8.5.0"
},
"peerDependenciesMeta": {
"ajv": {
"optional": true
}
}
},
"node_modules/ajv-formats": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
"integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"ajv": "^8.0.0"
},
"peerDependencies": {
"ajv": "^8.0.0"
},
"peerDependenciesMeta": {
"ajv": {
"optional": true
}
}
},
"node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
@@ -1393,27 +1124,6 @@
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -1620,15 +1330,6 @@
"node": ">=18"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/core-js": {
"version": "3.40.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz",
@@ -1775,15 +1476,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
@@ -1811,35 +1503,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/fs-extra": {
"version": "11.3.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
"integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
},
"engines": {
"node": ">=14.14"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-east-asian-width": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz",
@@ -1874,33 +1537,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=8"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -1922,18 +1558,6 @@
],
"license": "BSD-3-Clause"
},
"node_modules/import-lazy": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz",
"integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=8"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@@ -1941,24 +1565,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/is-core-module": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-interactive": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
@@ -2019,48 +1625,6 @@
"node": ">=14.17.6"
}
},
"node_modules/jju": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz",
"integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/jsonfile": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"universalify": "^2.0.0"
},
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/log-symbols": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz",
@@ -2091,21 +1655,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"license": "ISC",
"optional": true,
"peer": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/magic-string": {
"version": "0.30.17",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
@@ -2228,15 +1777,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true
},
"node_modules/pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
@@ -2291,18 +1831,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=6"
}
},
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
@@ -2326,41 +1854,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/resolve": {
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"is-core-module": "^2.16.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/restore-cursor": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
@@ -2448,24 +1941,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"dev": true,
"license": "ISC",
"optional": true,
"peer": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
@@ -2500,15 +1975,6 @@
"source-map": "^0.6.0"
}
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"dev": true,
"license": "BSD-3-Clause",
"optional": true,
"peer": true
},
"node_modules/stdin-discarder": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz",
@@ -2539,18 +2005,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/string-argv": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
"integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=0.6.19"
}
},
"node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
@@ -2595,54 +2049,6 @@
"is-natural-number": "^4.0.1"
}
},
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tar-stream": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz",
@@ -2779,30 +2185,6 @@
"through": "^2.3.8"
}
},
"node_modules/universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dev": true,
"license": "BSD-2-Clause",
"optional": true,
"peer": true,
"dependencies": {
"punycode": "^2.1.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -2827,15 +2209,6 @@
"node": ">=0.4"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true,
"license": "ISC",
"optional": true,
"peer": true
},
"node_modules/yauzl": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "objdiff-wasm",
"version": "3.0.0-beta.11",
"version": "3.0.1",
"description": "A local diffing tool for decompilation projects.",
"author": {
"name": "Luke Street",
@@ -19,8 +19,8 @@
"types": "dist/objdiff.d.ts",
"scripts": {
"build": "npm run build:wasm && npm run build:transpile && npm run build:lib",
"build:wasm": "cargo +nightly -Zbuild-std=panic_abort,core,alloc -Zbuild-std-features=compiler-builtins-mem build --target wasm32-wasip2 --release --no-default-features",
"build:transpile": "jco transpile ../target/wasm32-wasip2/release/objdiff_wasm.wasm --no-nodejs-compat --no-wasi-shim --no-namespaced-exports --map wasi:logging/logging=./wasi-logging.js --optimize -o pkg --name objdiff",
"build:wasm": "cargo build --profile release-min --no-default-features",
"build:transpile": "jco transpile ../target/wasm32-wasip2/release-min/objdiff_wasm.wasm --no-nodejs-compat --no-wasi-shim --no-namespaced-exports --map wasi:logging/logging=./wasi-logging.js --optimize -o pkg --name objdiff",
"build:lib": "rslib build"
},
"devDependencies": {

View File

@@ -0,0 +1,4 @@
[toolchain]
channel = "nightly"
components = ["rust-src"]
targets = ["wasm32-wasip2"]

View File

@@ -1,3 +1,4 @@
#![allow(clippy::derivable_impls)]
use alloc::{
format,
rc::{Rc, Weak},
@@ -223,7 +224,7 @@ impl GuestDisplay for Component {
let symbol_display = from_symbol_ref(symbol_ref);
diff::display::symbol_context(obj, symbol_display.symbol as usize)
.into_iter()
.map(|item| ContextItem::from(item))
.map(ContextItem::from)
.collect()
}
@@ -235,7 +236,7 @@ impl GuestDisplay for Component {
let symbol_display = from_symbol_ref(symbol_ref);
diff::display::symbol_hover(obj, symbol_display.symbol as usize, addend, override_color)
.into_iter()
.map(|item| HoverItem::from(item))
.map(HoverItem::from)
.collect()
}
@@ -282,7 +283,7 @@ impl GuestDisplay for Component {
};
diff::display::instruction_context(obj, resolved, &ins)
.into_iter()
.map(|item| ContextItem::from(item))
.map(ContextItem::from)
.collect()
}
@@ -331,7 +332,7 @@ impl GuestDisplay for Component {
};
diff::display::instruction_hover(obj, resolved, &ins)
.into_iter()
.map(|item| HoverItem::from(item))
.map(HoverItem::from)
.collect()
}
}
@@ -527,9 +528,7 @@ impl GuestObjectDiff for ResourceObjectDiff {
fn get_symbol(&self, symbol_ref: SymbolRef) -> Option<SymbolInfo> {
let obj = self.0.as_ref();
let symbol_display = from_symbol_ref(symbol_ref);
let Some(symbol) = obj.symbols.get(symbol_display.symbol) else {
return None;
};
let symbol = obj.symbols.get(symbol_display.symbol)?;
Some(SymbolInfo {
id: to_symbol_ref(symbol_display),
name: symbol.name.clone(),

View File

@@ -1,9 +1,7 @@
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
#[cfg(target_os = "wasi")]
mod api;
#[cfg(target_os = "wasi")]
mod logging;
#[cfg(all(target_os = "wasi", not(feature = "std")))]